diff --git a/Cargo.lock b/Cargo.lock index cc4c47c69..a3357cd28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,12 +34,6 @@ dependencies = [ name = "aarch64_sysreg" version = "0.1.1" -[[package]] -name = "aarch64_sysreg" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2c929f5025d9b8a0f549b187c4d3a39671f44015ff6ccddd0b134c874b3c1a" - [[package]] name = "acpi" version = "6.1.1" @@ -213,7 +207,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -224,7 +218,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -435,31 +429,13 @@ dependencies = [ [[package]] name = "arm_vcpu" -version = "0.2.2" -dependencies = [ - "aarch64-cpu 10.0.0", - "axaddrspace 0.1.5", - "axdevice_base 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axerrno 0.1.2", - "axvcpu 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "axvisor_api", - "log", - "numeric-enum-macro", - "percpu", - "spin 0.10.0", -] - -[[package]] -name = "arm_vcpu" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8581cf4d84a33f95aa43d39c0a25cabeeaddd65c97a790a0830e37da6e5d871" +version = "0.3.0" dependencies = [ - "aarch64-cpu 10.0.0", - "axaddrspace 0.1.5", - "axdevice_base 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axerrno 0.1.2", - "axvcpu 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "aarch64-cpu 11.2.0", + "axaddrspace", + "axdevice_base", + "axerrno 0.2.2", + "axvcpu", "axvisor_api", "log", "numeric-enum-macro", @@ -469,37 +445,18 @@ dependencies = [ [[package]] name = "arm_vgic" -version = "0.2.1" -dependencies = [ - "aarch64-cpu 10.0.0", - "aarch64_sysreg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axaddrspace 0.1.5", - "axdevice_base 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axerrno 0.1.2", - "axvisor_api", - "bitmaps", - "log", - "memory_addr", - "spin 0.9.8", - "tock-registers 0.10.1", -] - -[[package]] -name = "arm_vgic" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e2c4d90852cad20bbe1e5ee6e6d1b05468b98787a8344ffea58537eb54f375" +version = "0.2.2" dependencies = [ - "aarch64-cpu 10.0.0", - "aarch64_sysreg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axaddrspace 0.1.5", - "axdevice_base 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axerrno 0.1.2", + "aarch64-cpu 11.2.0", + "aarch64_sysreg", + "axaddrspace", + "axdevice_base", + "axerrno 0.2.2", "axvisor_api", "bitmaps", "log", "memory_addr", - "spin 0.9.8", + "spin 0.10.0", "tock-registers 0.10.1", ] @@ -587,26 +544,6 @@ dependencies = [ "buddy_system_allocator 0.12.0", ] -[[package]] -name = "axaddrspace" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91962ea80ef137c2986b6802d664900e73aa7a014272c13dd9172cc948d5c94e" -dependencies = [ - "axerrno 0.1.2", - "bit_field", - "bitflags 2.11.0", - "cfg-if", - "lazyinit 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log", - "memory_addr", - "memory_set", - "numeric-enum-macro", - "page_table_entry", - "page_table_multiarch", - "x86", -] - [[package]] name = "axaddrspace" version = "0.3.0" @@ -697,7 +634,7 @@ name = "axbuild" version = "0.3.0-preview.3" dependencies = [ "anyhow", - "axvmconfig 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "axvmconfig", "cargo_metadata", "chrono", "clap", @@ -773,61 +710,28 @@ dependencies = [ [[package]] name = "axdevice" -version = "0.2.1" -dependencies = [ - "arm_vgic 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axaddrspace 0.1.5", - "axdevice_base 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axerrno 0.1.2", - "axvmconfig 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if", - "log", - "memory_addr", - "range-alloc-arceos 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "riscv_vplic 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "spin 0.9.8", -] - -[[package]] -name = "axdevice" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473e2bedf3a04bede7ab6e05909d56f8aed538c56507ce172aaaf0d14dc3be36" +version = "0.2.2" dependencies = [ - "arm_vgic 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axaddrspace 0.1.5", - "axdevice_base 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axerrno 0.1.2", - "axvmconfig 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "arm_vgic", + "axaddrspace", + "axdevice_base", + "axerrno 0.2.2", + "axvmconfig", "cfg-if", "log", "memory_addr", - "range-alloc-arceos 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "riscv_vplic 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "spin 0.9.8", -] - -[[package]] -name = "axdevice_base" -version = "0.2.1" -dependencies = [ - "axaddrspace 0.1.5", - "axerrno 0.1.2", - "axvmconfig 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "memory_addr", - "serde", + "range-alloc-arceos", + "riscv_vplic", + "spin 0.10.0", ] [[package]] name = "axdevice_base" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2707beb1e7bb6557406d77f3968a9ed7ec3d0476691022eafa550c266ec5c9" +version = "0.2.2" dependencies = [ - "axaddrspace 0.1.5", - "axerrno 0.1.2", - "axvmconfig 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "memory_addr", + "axaddrspace", + "axerrno 0.2.2", + "axvmconfig", "serde", ] @@ -875,7 +779,7 @@ dependencies = [ "axhal", "axplat-dyn", "cfg-if", - "crate_interface 0.1.4", + "crate_interface", "log", "smallvec", ] @@ -1122,15 +1026,6 @@ dependencies = [ "axerrno 0.2.2", ] -[[package]] -name = "axhvc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6262e1b84fe6c912c07cc7d9dfa45d8f4511870d79a2886ce6217278bfb8c0dc" -dependencies = [ - "axerrno 0.2.2", -] - [[package]] name = "axin" version = "0.1.0" @@ -1184,17 +1079,6 @@ dependencies = [ "trait-ffi", ] -[[package]] -name = "axklib" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03bf328ee0dd583179ce7108584ec69da10e06bd4beb4d6acac7f6cb33754dab" -dependencies = [ - "axerrno 0.2.2", - "memory_addr", - "trait-ffi", -] - [[package]] name = "axlibc" version = "0.3.0-preview.3" @@ -1213,7 +1097,7 @@ dependencies = [ "axlog", "cfg-if", "chrono", - "crate_interface 0.1.4", + "crate_interface", "kspin", "log", ] @@ -1285,7 +1169,7 @@ dependencies = [ "axplat-macros", "bitflags 2.11.0", "const-str", - "crate_interface 0.3.0", + "crate_interface", "handler_table 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "kspin", "memory_addr", @@ -1372,7 +1256,7 @@ dependencies = [ "axdriver_block", "axdriver_virtio", "axerrno 0.2.2", - "axklib 0.3.0", + "axklib", "axplat", "dma-api 0.7.1", "fdt-edit", @@ -1518,7 +1402,7 @@ dependencies = [ "axhal", "axinput", "axipi", - "axklib 0.3.0", + "axklib", "axlog", "axmm", "axnet", @@ -1527,7 +1411,7 @@ dependencies = [ "axtask", "cfg-if", "chrono", - "crate_interface 0.1.4", + "crate_interface", "ctor_bare", "indoc", "percpu", @@ -1587,7 +1471,7 @@ dependencies = [ "axtask", "cfg-if", "cpumask 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crate_interface 0.1.4", + "crate_interface", "event-listener", "extern-trait", "futures-util", @@ -1654,23 +1538,10 @@ dependencies = [ [[package]] name = "axvcpu" -version = "0.2.2" -dependencies = [ - "axaddrspace 0.1.5", - "axerrno 0.1.2", - "axvisor_api", - "memory_addr", - "percpu", -] - -[[package]] -name = "axvcpu" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70880a87fffe8087719ad2e7aa09da482db88a65d123dc9170297b7e2a162fb5" +version = "0.3.0" dependencies = [ - "axaddrspace 0.1.5", - "axerrno 0.1.2", + "axaddrspace", + "axerrno 0.2.2", "axvisor_api", "memory_addr", "percpu", @@ -1683,20 +1554,20 @@ dependencies = [ "aarch64-cpu-ext", "anyhow", "arm-gic-driver 0.17.0", - "axaddrspace 0.1.5", - "axbuild", + "axaddrspace", "axconfig", - "axdevice 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axdevice_base 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "axdevice", + "axdevice_base", "axerrno 0.2.2", - "axhvc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "axklib 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "axhvc", + "axklib", + "axplat-riscv64-qemu-virt", "axplat-x86-qemu-q35", "axstd", - "axvcpu 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "axvcpu", "axvisor_api", - "axvm 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "axvmconfig 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "axvm", + "axvmconfig", "bitflags 2.11.0", "byte-unit", "cargo_metadata", @@ -1705,7 +1576,7 @@ dependencies = [ "clap", "colored", "cpumask 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crate_interface 0.1.4", + "crate_interface", "extern-trait", "fdt-parser", "flate2", @@ -1732,7 +1603,6 @@ dependencies = [ "rdrive", "regex", "reqwest 0.13.2", - "rk3568_clk", "rk3588-clk", "rockchip-pm", "schemars", @@ -1740,7 +1610,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "spin 0.9.8", + "spin 0.10.0", "syn 2.0.117", "tar", "timer_list 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1750,21 +1620,18 @@ dependencies = [ [[package]] name = "axvisor_api" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa7233b2a1338dc06a80e2779b572b4df02007ea128ef7b235b66fc3eeac0ca6" +version = "0.3.0" dependencies = [ - "axaddrspace 0.1.5", + "axaddrspace", "axvisor_api_proc", - "crate_interface 0.1.4", + "cpumask 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crate_interface", "memory_addr", ] [[package]] name = "axvisor_api_proc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a64eb4410ae8357ac8c01c2fb201e57d7aeeb5436ed4d0f5774bfa11cc5902" +version = "0.3.0" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1774,42 +1641,17 @@ dependencies = [ [[package]] name = "axvm" -version = "0.2.3" -dependencies = [ - "arm_vcpu 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "arm_vgic 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axaddrspace 0.1.5", - "axdevice 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axdevice_base 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axerrno 0.2.2", - "axvcpu 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "axvmconfig 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if", - "cpumask 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log", - "memory_addr", - "page_table_entry", - "page_table_multiarch", - "percpu", - "riscv_vcpu 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "spin 0.9.8", - "x86_vcpu", -] - -[[package]] -name = "axvm" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76c13dc01a73107817fa04f8f2cad019eaee992541713d7064fd2da855502c6d" +version = "0.3.0" dependencies = [ - "arm_vcpu 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "arm_vgic 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axaddrspace 0.1.5", - "axdevice 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axdevice_base 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "arm_vcpu", + "arm_vgic", + "axaddrspace", + "axdevice", + "axdevice_base", "axerrno 0.2.2", - "axvcpu 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "axvmconfig 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "axvcpu", + "axvisor_api", + "axvmconfig", "cfg-if", "cpumask 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "log", @@ -1817,8 +1659,8 @@ dependencies = [ "page_table_entry", "page_table_multiarch", "percpu", - "riscv_vcpu 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "spin 0.9.8", + "riscv_vcpu", + "spin 0.10.0", "x86_vcpu", ] @@ -1837,23 +1679,6 @@ dependencies = [ "toml", ] -[[package]] -name = "axvmconfig" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35f2cf8bfcabc84e636f5cf3cd7b1c1ddcf1f584d6fa91791a572056204db35a" -dependencies = [ - "axerrno 0.2.2", - "clap", - "enumerable", - "env_logger 0.11.9", - "log", - "schemars", - "serde", - "serde_repr", - "toml", -] - [[package]] name = "bare-metal" version = "1.0.0" @@ -2327,7 +2152,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2489,17 +2314,6 @@ dependencies = [ "bitmaps", ] -[[package]] -name = "crate_interface" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70272a03a2cef15589bac05d3d15c023752f5f8f2da8be977d983a9d9e6250fb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "crate_interface" version = "0.3.0" @@ -3278,7 +3092,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3548,11 +3362,9 @@ dependencies = [ [[package]] name = "fxmac_rs" version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cc278a5f88871e3800ca269ddf40f6504d29d702f3b47375954b5a64d0cfd4f" dependencies = [ "aarch64-cpu 10.0.0", - "crate_interface 0.1.4", + "crate_interface", "log", ] @@ -4187,7 +3999,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -4350,7 +4162,7 @@ name = "kernel_guard" version = "0.1.3" dependencies = [ "cfg-if", - "crate_interface 0.1.4", + "crate_interface", ] [[package]] @@ -5524,12 +5336,6 @@ checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" name = "range-alloc-arceos" version = "0.1.4" -[[package]] -name = "range-alloc-arceos" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187083342c080f6150a1e8e6e6a687b49239df195aaf866ff4ed535961ab860e" - [[package]] name = "ranges-ext" version = "0.6.1" @@ -5948,19 +5754,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b59d645e392e041ad18f5e529ed13242d8405c66bb192f59703ea2137017d0" -[[package]] -name = "riscv-h" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cffa652689d01c5f7033abe105e69f4d57ac85bf7e17da688bab10e4b9d3a2d8" -dependencies = [ - "bare-metal", - "bit_field", - "bitflags 2.11.0", - "log", - "riscv 0.14.0", -] - [[package]] name = "riscv-h" version = "0.2.0" @@ -5972,19 +5765,6 @@ dependencies = [ "riscv 0.14.0", ] -[[package]] -name = "riscv-h" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bf93901d1af87f984c7be7a348cd27571e1f6f29293b8f3b3193f4c40d4d40" -dependencies = [ - "bare-metal", - "bit_field", - "bitflags 2.11.0", - "log", - "riscv 0.14.0", -] - [[package]] name = "riscv-macros" version = "0.2.0" @@ -6043,97 +5823,41 @@ dependencies = [ [[package]] name = "riscv_vcpu" -version = "0.2.2" -dependencies = [ - "axaddrspace 0.1.5", - "axerrno 0.1.2", - "axvcpu 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "axvisor_api", - "bit_field", - "bitflags 2.11.0", - "cfg-if", - "crate_interface 0.1.4", - "log", - "memoffset", - "memory_addr", - "page_table_entry", - "riscv 0.14.0", - "riscv-decode", - "riscv-h 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustsbi", - "sbi-rt", - "sbi-spec", - "tock-registers 0.9.0", -] - -[[package]] -name = "riscv_vcpu" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6122b26f3d5920206f22b871fd13d79f5b23e570c7860f345d8736528dacc4c" +version = "0.3.0" dependencies = [ - "axaddrspace 0.1.5", - "axerrno 0.1.2", - "axvcpu 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "axaddrspace", + "axerrno 0.2.2", + "axvcpu", "axvisor_api", "bit_field", "bitflags 2.11.0", "cfg-if", - "crate_interface 0.1.4", + "crate_interface", "log", "memoffset", "memory_addr", "page_table_entry", "riscv 0.14.0", "riscv-decode", - "riscv-h 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "riscv-h", "rustsbi", "sbi-rt", "sbi-spec", - "tock-registers 0.9.0", -] - -[[package]] -name = "riscv_vplic" -version = "0.2.1" -dependencies = [ - "axaddrspace 0.1.5", - "axdevice_base 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axerrno 0.1.2", - "axvisor_api", - "bitmaps", - "log", - "riscv-h 0.1.0", - "spin 0.9.8", + "tock-registers 0.10.1", ] [[package]] name = "riscv_vplic" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193b9d354fe0680a7e151b069929ab7e5722519b5623b24f2bf0211236742a5f" +version = "0.2.2" dependencies = [ - "axaddrspace 0.1.5", - "axdevice_base 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axerrno 0.1.2", + "axaddrspace", + "axdevice_base", + "axerrno 0.2.2", "axvisor_api", "bitmaps", "log", - "riscv-h 0.1.0", - "spin 0.9.8", -] - -[[package]] -name = "rk3568_clk" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a9786b01d79acb045652d4cb451b049b26a509171d4afe5c19767d06a366585" -dependencies = [ - "aarch64-cpu 10.0.0", - "bare-test-macros", - "fdt-parser", - "kspin", - "log", + "riscv-h", + "spin 0.10.0", ] [[package]] @@ -6309,7 +6033,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -6368,7 +6092,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -6845,7 +6569,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -7341,7 +7065,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -8278,7 +8002,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -8764,26 +8488,24 @@ dependencies = [ [[package]] name = "x86_vcpu" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135b71adc527803929792ea1af4a2cd7c264c9cd3dd0096b03f9bc959505f73a" +version = "0.3.0" dependencies = [ - "axaddrspace 0.1.5", - "axdevice_base 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axerrno 0.1.2", - "axvcpu 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "axaddrspace", + "axdevice_base", + "axerrno 0.2.2", + "axvcpu", "axvisor_api", "bit_field", "bitflags 2.11.0", "cfg-if", - "crate_interface 0.1.4", + "crate_interface", "log", "memory_addr", "numeric-enum-macro", "page_table_entry", "paste", "raw-cpuid 11.6.0", - "spin 0.9.8", + "spin 0.10.0", "x86", "x86_64", "x86_vlapic", @@ -8791,13 +8513,11 @@ dependencies = [ [[package]] name = "x86_vlapic" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809257bd2252fc337f3b494ae9230e4600bfa4e7d3f2478a0dd794e16849040d" +version = "0.2.2" dependencies = [ - "axaddrspace 0.1.5", - "axdevice_base 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "axerrno 0.1.2", + "axaddrspace", + "axdevice_base", + "axerrno 0.2.2", "axvisor_api", "bit", "log", diff --git a/Cargo.toml b/Cargo.toml index 464dded91..4db2625f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -229,6 +229,29 @@ scope-local = { path = "components/scope-local", version = "0.1.0" } axbuild = { path = "scripts/axbuild", version = "0.3.0-preview.3" } +# === axvisor === +axvm = { path = "components/axvm", version = "0.3" } +axvmconfig = { path = "components/axvmconfig", version = "0.2.2" } +axvcpu = { path = "components/axvcpu", version = "0.3" } +x86_vcpu = { path = "components/x86_vcpu", version = "0.3" } +x86_vlapic = { path = "components/x86_vlapic", version = "0.2.2" } +arm_vcpu = { path = "components/arm_vcpu", version = "0.3" } +arm_vgic = { path = "components/arm_vgic", version = "0.2.2" } +aarch64_sysreg = { path = "components/aarch64_sysreg", version = "0.1.1" } +riscv_vcpu = { path = "components/riscv_vcpu", version = "0.3" } +riscv_vplic = { path = "components/riscv_vplic", version = "0.2.2" } +riscv-h = { path = "components/riscv-h", version = "0.2" } +axaddrspace = { path = "components/axaddrspace", version = "0.3" } +axdevice = { path = "components/axdevice", version = "0.2.2" } +axdevice_base = { path = "components/axdevice_base", version = "0.2.2" } +axvisor_api = { path = "components/axvisor_api", version = "0.3" } +axhvc = { path = "components/axhvc", version = "0.2.0" } +axklib = { path = "components/axklib", version = "0.3.0" } +range-alloc-arceos = { path = "components/range-alloc-arceos", version = "0.1.4" } + +# === drivers === +fxmac_rs = { path = "components/fxmac_rs", version = "0.2.1" } + [workspace.dependencies] # === os/arceos crates (0.3.0-preview.3) === # ulib @@ -307,7 +330,7 @@ bindgen = "0.72" buddy-slab-allocator = "0.2" cfg-if = "1.0" chrono = { version = "0.4", default-features = false } -crate_interface = "0.1" +crate_interface = "0.3" ctor_bare = "0.2" event-listener = { version = "5.4.0", default-features = false } kernel_guard = "0.1" diff --git a/components/aarch64_sysreg/.github/config.json b/components/aarch64_sysreg/.github/config.json new file mode 100644 index 000000000..b56dee791 --- /dev/null +++ b/components/aarch64_sysreg/.github/config.json @@ -0,0 +1,17 @@ +{ + "component": { + "name": "aarch64_sysreg", + "crate_name": "aarch64_sysreg" + }, + "targets": [ + "aarch64-unknown-none-softfloat" + ], + "unit_test_targets": [ + "x86_64-unknown-linux-gnu" + ], + "rust_components": [ + "rust-src", + "clippy", + "rustfmt" + ] +} \ No newline at end of file diff --git a/components/aarch64_sysreg/.github/workflows/check.yml b/components/aarch64_sysreg/.github/workflows/check.yml new file mode 100644 index 000000000..0207ed160 --- /dev/null +++ b/components/aarch64_sysreg/.github/workflows/check.yml @@ -0,0 +1,15 @@ +# Quality Check Workflow +# References shared workflow from axci + +name: Check + +on: + push: + branches: ['**'] + tags-ignore: ['**'] + pull_request: + workflow_dispatch: + +jobs: + check: + uses: arceos-hypervisor/axci/.github/workflows/check.yml@main \ No newline at end of file diff --git a/components/aarch64_sysreg/.github/workflows/ci.yml b/components/aarch64_sysreg/.github/workflows/ci.yml deleted file mode 100644 index b72f04c5c..000000000 --- a/components/aarch64_sysreg/.github/workflows/ci.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: CI - -on: [push, pull_request] - -jobs: - ci: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - rust-toolchain: [nightly] - targets: [x86_64-unknown-linux-gnu, x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none-softfloat] - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - with: - toolchain: ${{ matrix.rust-toolchain }} - components: rust-src, clippy, rustfmt - targets: ${{ matrix.targets }} - - name: Check rust version - run: rustc --version --verbose - - name: Check code format - run: cargo fmt --all -- --check - - name: Clippy - run: cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default - - name: Build - run: cargo build --target ${{ matrix.targets }} --all-features - - name: Unit test - if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }} - run: cargo test --target ${{ matrix.targets }} -- --nocapture - - doc: - runs-on: ubuntu-latest - strategy: - fail-fast: false - permissions: - contents: write - env: - default-branch: ${{ format('refs/heads/{0}', github.event.repository.default_branch) }} - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - - name: Build docs - continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }} - run: | - cargo doc --no-deps --all-features - printf '' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html - - name: Deploy to Github Pages - if: ${{ github.ref == env.default-branch }} - uses: JamesIves/github-pages-deploy-action@v4 - with: - single-commit: true - branch: gh-pages - folder: target/doc \ No newline at end of file diff --git a/components/aarch64_sysreg/.github/workflows/deploy.yml b/components/aarch64_sysreg/.github/workflows/deploy.yml new file mode 100644 index 000000000..c28115ab9 --- /dev/null +++ b/components/aarch64_sysreg/.github/workflows/deploy.yml @@ -0,0 +1,16 @@ +# Deploy Workflow +# References shared workflow from axci + +name: Deploy + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + +jobs: + deploy: + uses: arceos-hypervisor/axci/.github/workflows/deploy.yml@main + with: + verify_branch: true + verify_version: true \ No newline at end of file diff --git a/components/aarch64_sysreg/.github/workflows/push.yml b/components/aarch64_sysreg/.github/workflows/push.yml new file mode 100644 index 000000000..3ff391a4a --- /dev/null +++ b/components/aarch64_sysreg/.github/workflows/push.yml @@ -0,0 +1,16 @@ +name: Notify Parent Repository + +on: + push: + branches: + - main + - zcs + workflow_dispatch: + +jobs: + notify-parent: + name: Notify Parent Repository + # 调用 axci 仓库的可复用工作流 + uses: arceos-hypervisor/axci/.github/workflows/push.yml@main + secrets: + PARENT_REPO_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} diff --git a/components/aarch64_sysreg/.github/workflows/release.yml b/components/aarch64_sysreg/.github/workflows/release.yml new file mode 100644 index 000000000..20f186395 --- /dev/null +++ b/components/aarch64_sysreg/.github/workflows/release.yml @@ -0,0 +1,27 @@ +# Release Workflow +# References shared workflow from axci +# check + test must pass before release + +name: Release + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + - 'v[0-9]+.[0-9]+.[0-9]+-pre.[0-9]+' + +jobs: + check: + uses: arceos-hypervisor/axci/.github/workflows/check.yml@main + + test: + uses: arceos-hypervisor/axci/.github/workflows/test.yml@main + + release: + needs: [check, test] + uses: arceos-hypervisor/axci/.github/workflows/release.yml@main + with: + verify_branch: true + verify_version: true + secrets: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/components/aarch64_sysreg/.github/workflows/test.yml b/components/aarch64_sysreg/.github/workflows/test.yml new file mode 100644 index 000000000..dc91940d4 --- /dev/null +++ b/components/aarch64_sysreg/.github/workflows/test.yml @@ -0,0 +1,17 @@ +# Integration Test Workflow +# References shared workflow from axci + +name: Test + +on: + push: + branches: + - '**' + tags-ignore: + - '**' + pull_request: + workflow_dispatch: + +jobs: + test: + uses: arceos-hypervisor/axci/.github/workflows/test.yml@main \ No newline at end of file diff --git a/components/aarch64_sysreg/.gitignore b/components/aarch64_sysreg/.gitignore index d478c630d..d7c96c4a7 100644 --- a/components/aarch64_sysreg/.gitignore +++ b/components/aarch64_sysreg/.gitignore @@ -1,4 +1,14 @@ /target /.vscode .DS_Store -Cargo.lock \ No newline at end of file +Cargo.lock + +# Test results (generated by shared test framework) +/test-results/ +/test_repos/ +*.log + +# Downloaded test framework +/scripts/.axci/ + +.claude \ No newline at end of file diff --git a/components/aarch64_sysreg/Cargo.toml b/components/aarch64_sysreg/Cargo.toml index 77405edf6..75063ba92 100644 --- a/components/aarch64_sysreg/Cargo.toml +++ b/components/aarch64_sysreg/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.1" edition = "2021" authors = ["Debin "] description = "Address translation of system registers" -license = "GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0" +license = "Apache-2.0" homepage = "https://github.com/arceos-org/arceos" repository = "https://github.com/arceos-org/aarch64_sysreg" documentation = "https://docs.rs/aarch64_sysreg" diff --git a/components/aarch64_sysreg/LICENSE b/components/aarch64_sysreg/LICENSE new file mode 100644 index 000000000..15e431c22 --- /dev/null +++ b/components/aarch64_sysreg/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/components/aarch64_sysreg/README.md b/components/aarch64_sysreg/README.md index e69de29bb..d4f507a25 100644 --- a/components/aarch64_sysreg/README.md +++ b/components/aarch64_sysreg/README.md @@ -0,0 +1,129 @@ +

aarch64_sysreg

+ +

AArch64 System Register Type Definitions

+ +
+ +[![Crates.io](https://img.shields.io/crates/v/aarch64_sysreg.svg)](https://crates.io/crates/aarch64_sysreg) +[![Docs.rs](https://docs.rs/aarch64_sysreg/badge.svg)](https://docs.rs/aarch64_sysreg) +[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/arceos-org/aarch64_sysreg/blob/main/LICENSE) + +
+ +English | [中文](README_CN.md) + +# Introduction + +A library providing type definitions for AArch64 system registers, including operation types, register types, and system register enumerations for the ARM64 architecture. Supports `#![no_std]` for bare-metal and OS kernel development. + +This library exports three core enumeration types: + +- **`OperationType`** — AArch64 instruction operation types (1000+ instructions) +- **`RegistersType`** — General-purpose and vector registers (W/X/V/B/H/S/D/Q/Z/P, etc.) +- **`SystemRegType`** — System registers (debug, trace, performance counters, system control, etc.) + +Each type implements `Display`, `From`, `LowerHex`, and `UpperHex` traits. + +## Quick Start + +### Requirements + +- Rust nightly toolchain +- Rust components: rust-src, clippy, rustfmt + +```bash +# Install rustup (if not installed) +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# Install nightly toolchain and components +rustup install nightly +rustup component add rust-src clippy rustfmt --toolchain nightly +``` + +### Run Check and Test + +```bash +# 1. Clone the repository +git clone https://github.com/arceos-org/aarch64_sysreg.git +cd aarch64_sysreg + +# 2. Code check (format + clippy + build + doc generation) +./scripts/check.sh + +# 3. Run tests +# Run all tests (unit tests + integration tests) +./scripts/test.sh + +# Run unit tests only +./scripts/test.sh unit + +# Run integration tests only +./scripts/test.sh integration + +# List all available test suites +./scripts/test.sh list + +# Specify unit test target +./scripts/test.sh unit --unit-targets x86_64-unknown-linux-gnu +``` + +## Integration + +### Installation + +Add to your `Cargo.toml`: + +```toml +[dependencies] +aarch64_sysreg = "0.1.1" +``` + +### Example + +```rust +use aarch64_sysreg::{OperationType, RegistersType, SystemRegType}; + +fn main() { + // Operation type: enum variant and value conversion + let op = OperationType::ADD; + println!("{}", op); // ADD + println!("0x{:x}", op); // 0x6 + println!("0x{:X}", op); // 0x6 + + let op_from = OperationType::from(0x6); + assert_eq!(op_from, OperationType::ADD); + + // Register type + let reg = RegistersType::X0; + println!("{}", reg); // X0 + let reg_from = RegistersType::from(0x22); + assert_eq!(reg_from, RegistersType::X0); + + // System register + let sys_reg = SystemRegType::MDSCR_EL1; + println!("{}", sys_reg); // MDSCR_EL1 + println!("0x{:x}", sys_reg); // 0x240004 +} +``` + +### Documentation + +Generate and view API documentation: + +```bash +cargo doc --no-deps --open +``` + +Online documentation: [docs.rs/aarch64_sysreg](https://docs.rs/aarch64_sysreg) + +# Contributing + +1. Fork the repository and create a branch +2. Run local check: `./scripts/check.sh` +3. Run local tests: `./scripts/test.sh` +4. Submit PR and pass CI checks + +# License + +Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details. diff --git a/components/aarch64_sysreg/README_CN.md b/components/aarch64_sysreg/README_CN.md new file mode 100644 index 000000000..70efd7c97 --- /dev/null +++ b/components/aarch64_sysreg/README_CN.md @@ -0,0 +1,129 @@ +

aarch64_sysreg

+ +

AArch64 系统寄存器类型定义库

+ +
+ +[![Crates.io](https://img.shields.io/crates/v/aarch64_sysreg.svg)](https://crates.io/crates/aarch64_sysreg) +[![Docs.rs](https://docs.rs/aarch64_sysreg/badge.svg)](https://docs.rs/aarch64_sysreg) +[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/arceos-org/aarch64_sysreg/blob/main/LICENSE) + +
+ +[English](README.md) | 中文 + +# 简介 + +AArch64 系统寄存器类型定义库,提供 ARM64 架构中操作类型、寄存器类型和系统寄存器的枚举定义。支持 `#![no_std]`,可用于裸机和操作系统内核开发。 + +本库导出三个核心枚举类型: + +- **`OperationType`** — AArch64 指令操作类型(1000+ 种指令) +- **`RegistersType`** — 通用寄存器与向量寄存器(W/X/V/B/H/S/D/Q/Z/P 等) +- **`SystemRegType`** — 系统寄存器(调试、跟踪、性能计数、系统控制等) + +每个类型均实现了 `Display`、`From`、`LowerHex`、`UpperHex` trait。 + +## 快速上手 + +### 环境要求 + +- Rust nightly 工具链 +- Rust 组件: rust-src, clippy, rustfmt + +```bash +# 安装 rustup(如未安装) +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# 安装 nightly 工具链及组件 +rustup install nightly +rustup component add rust-src clippy rustfmt --toolchain nightly +``` + +### 运行检查和测试 + +```bash +# 1. 克隆仓库 +git clone https://github.com/arceos-org/aarch64_sysreg.git +cd aarch64_sysreg + +# 2. 代码检查(格式检查 + clippy + 构建 + 文档生成) +./scripts/check.sh + +# 3. 运行测试 +# 运行全部测试(单元测试 + 集成测试) +./scripts/test.sh + +# 仅运行单元测试 +./scripts/test.sh unit + +# 仅运行集成测试 +./scripts/test.sh integration + +# 列出所有可用的测试套件 +./scripts/test.sh list + +# 指定单元测试目标 +./scripts/test.sh unit --unit-targets x86_64-unknown-linux-gnu +``` + +## 集成使用 + +### 安装 + +在 `Cargo.toml` 中添加: + +```toml +[dependencies] +aarch64_sysreg = "0.1.1" +``` + +### 使用示例 + +```rust +use aarch64_sysreg::{OperationType, RegistersType, SystemRegType}; + +fn main() { + // 操作类型:枚举变体与数值互转 + let op = OperationType::ADD; + println!("{}", op); // ADD + println!("0x{:x}", op); // 0x6 + println!("0x{:X}", op); // 0x6 + + let op_from = OperationType::from(0x6); + assert_eq!(op_from, OperationType::ADD); + + // 寄存器类型 + let reg = RegistersType::X0; + println!("{}", reg); // X0 + let reg_from = RegistersType::from(0x22); + assert_eq!(reg_from, RegistersType::X0); + + // 系统寄存器 + let sys_reg = SystemRegType::MDSCR_EL1; + println!("{}", sys_reg); // MDSCR_EL1 + println!("0x{:x}", sys_reg); // 0x240004 +} +``` + +### 文档 + +生成并查看 API 文档: + +```bash +cargo doc --no-deps --open +``` + +在线文档:[docs.rs/aarch64_sysreg](https://docs.rs/aarch64_sysreg) + +# 贡献 + +1. Fork 仓库并创建分支 +2. 运行本地检查:`./scripts/check.sh` +3. 运行本地测试:`./scripts/test.sh` +4. 提交 PR 并通过 CI 检查 + +# 协议 + +本项目采用 Apache License, Version 2.0 许可证。详见 [LICENSE](LICENSE) 文件。 diff --git a/components/aarch64_sysreg/scripts/check.sh b/components/aarch64_sysreg/scripts/check.sh new file mode 100755 index 000000000..8c95f0757 --- /dev/null +++ b/components/aarch64_sysreg/scripts/check.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# +# aarch64_sysreg 代码检查脚本 +# 下载并调用 axci 仓库中的检查脚本 +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +COMPONENT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +COMPONENT_NAME="$(basename "$COMPONENT_DIR")" +AXCI_DIR="${SCRIPT_DIR}/.axci" +AXCI_REPO="https://github.com/arceos-hypervisor/axci.git" + +# 下载或更新 axci 仓库 +download_axci() { + if [ -d "$AXCI_DIR" ]; then + echo "Updating axci repository..." + cd "$AXCI_DIR" && git pull --quiet + else + echo "Downloading axci repository..." + git clone --quiet "$AXCI_REPO" "$AXCI_DIR" + fi +} + +# 主函数 +main() { + download_axci + + # 在组件目录中运行检查 + cd "$COMPONENT_DIR" + exec bash "$AXCI_DIR/check.sh" --component-dir "$COMPONENT_DIR" "$@" +} + +main "$@" diff --git a/components/aarch64_sysreg/scripts/test.sh b/components/aarch64_sysreg/scripts/test.sh new file mode 100755 index 000000000..751cb0ef1 --- /dev/null +++ b/components/aarch64_sysreg/scripts/test.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# +# aarch64_sysreg 测试脚本 +# 下载并调用 axci 仓库中的测试框架 +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +COMPONENT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +AXCI_DIR="${SCRIPT_DIR}/.axci" +AXCI_REPO="https://github.com/arceos-hypervisor/axci.git" + +# 下载或更新 axci 仓库 +download_axci() { + if [ -d "$AXCI_DIR" ]; then + echo "Updating axci repository..." + cd "$AXCI_DIR" && git pull --quiet + else + echo "Downloading axci repository..." + git clone --quiet "$AXCI_REPO" "$AXCI_DIR" + fi +} + +# 主函数 +main() { + download_axci + + # 在组件目录中运行测试,自动指定当前组件 + cd "$COMPONENT_DIR" + exec bash "$AXCI_DIR/tests.sh" --component-dir "$COMPONENT_DIR" "$@" +} + +main "$@" diff --git a/components/aarch64_sysreg/tests/operation_type_tests.rs b/components/aarch64_sysreg/tests/operation_type_tests.rs new file mode 100644 index 000000000..041165266 --- /dev/null +++ b/components/aarch64_sysreg/tests/operation_type_tests.rs @@ -0,0 +1,63 @@ +use aarch64_sysreg::OperationType; + +#[test] +fn test_operation_type_values() { + assert_eq!(OperationType::ERROR as usize, 0x0); + assert_eq!(OperationType::ABS as usize, 0x1); + assert_eq!(OperationType::ADD as usize, 0x6); + assert_eq!(OperationType::SUB as usize, 0x3d8); + assert_eq!(OperationType::ZIP2 as usize, 0x49f); +} + +#[test] +fn test_operation_type_display() { + assert_eq!(format!("{}", OperationType::ERROR), "ERROR"); + assert_eq!(format!("{}", OperationType::ADD), "ADD"); + assert_eq!(format!("{}", OperationType::SUB), "SUB"); + assert_eq!(format!("{}", OperationType::MUL), "MUL"); + assert_eq!(format!("{}", OperationType::RET), "RET"); + assert_eq!(format!("{}", OperationType::BL), "BL"); +} + +#[test] +fn test_operation_type_lower_hex() { + assert_eq!(format!("{:x}", OperationType::ADD), "6"); + assert_eq!(format!("{:x}", OperationType::SUB), "3d8"); + assert_eq!(format!("{:x}", OperationType::ERROR), "0"); +} + +#[test] +fn test_operation_type_upper_hex() { + assert_eq!(format!("{:X}", OperationType::ADD), "6"); + assert_eq!(format!("{:X}", OperationType::SUB), "3D8"); + assert_eq!(format!("{:X}", OperationType::ERROR), "0"); +} + +#[test] +fn test_operation_type_from_usize() { + assert_eq!(OperationType::from(0x0), OperationType::ERROR); + assert_eq!(OperationType::from(0x1), OperationType::ABS); + assert_eq!(OperationType::from(0x6), OperationType::ADD); + assert_eq!(OperationType::from(0x3d8), OperationType::SUB); +} + +#[test] +#[should_panic(expected = "Invalid arm64 operation value")] +fn test_operation_type_from_invalid() { + let _ = OperationType::from(0xFFFF); +} + +#[test] +fn test_operation_type_clone_copy() { + let op = OperationType::ADD; + let op_clone = op.clone(); + let op_copy = op; + assert_eq!(op, op_clone); + assert_eq!(op, op_copy); +} + +#[test] +fn test_operation_type_partial_eq() { + assert_eq!(OperationType::ADD, OperationType::ADD); + assert_ne!(OperationType::ADD, OperationType::SUB); +} diff --git a/components/aarch64_sysreg/tests/registers_type_tests.rs b/components/aarch64_sysreg/tests/registers_type_tests.rs new file mode 100644 index 000000000..fff63b8a3 --- /dev/null +++ b/components/aarch64_sysreg/tests/registers_type_tests.rs @@ -0,0 +1,66 @@ +use aarch64_sysreg::RegistersType; + +#[test] +fn test_registers_type_values() { + assert_eq!(RegistersType::NONE as usize, 0x0); + assert_eq!(RegistersType::W0 as usize, 0x1); + assert_eq!(RegistersType::X0 as usize, 0x22); + assert_eq!(RegistersType::SP as usize, 0x42); + assert_eq!(RegistersType::V0 as usize, 0x43); + assert_eq!(RegistersType::Q0 as usize, 0xe3); +} + +#[test] +fn test_registers_type_display() { + assert_eq!(format!("{}", RegistersType::NONE), "NONE"); + assert_eq!(format!("{}", RegistersType::W0), "W0"); + assert_eq!(format!("{}", RegistersType::X0), "X0"); + assert_eq!(format!("{}", RegistersType::XZR), "XZR"); + assert_eq!(format!("{}", RegistersType::WZR), "WZR"); + assert_eq!(format!("{}", RegistersType::SP), "SP"); + assert_eq!(format!("{}", RegistersType::WSP), "WSP"); +} + +#[test] +fn test_registers_type_lower_hex() { + assert_eq!(format!("{:x}", RegistersType::W0), "1"); + assert_eq!(format!("{:x}", RegistersType::X0), "22"); + assert_eq!(format!("{:x}", RegistersType::SP), "42"); +} + +#[test] +fn test_registers_type_upper_hex() { + assert_eq!(format!("{:X}", RegistersType::W0), "1"); + assert_eq!(format!("{:X}", RegistersType::X0), "22"); + assert_eq!(format!("{:X}", RegistersType::Q0), "E3"); +} + +#[test] +fn test_registers_type_from_usize() { + assert_eq!(RegistersType::from(0x0), RegistersType::NONE); + assert_eq!(RegistersType::from(0x1), RegistersType::W0); + assert_eq!(RegistersType::from(0x22), RegistersType::X0); + assert_eq!(RegistersType::from(0x42), RegistersType::SP); +} + +#[test] +#[should_panic(expected = "Invalid register value")] +fn test_registers_type_from_invalid() { + let _ = RegistersType::from(0xFFFF); +} + +#[test] +fn test_registers_type_clone_copy() { + let reg = RegistersType::X0; + let reg_clone = reg.clone(); + let reg_copy = reg; + assert_eq!(reg, reg_clone); + assert_eq!(reg, reg_copy); +} + +#[test] +fn test_registers_type_partial_eq() { + assert_eq!(RegistersType::X0, RegistersType::X0); + assert_ne!(RegistersType::X0, RegistersType::X1); + assert_ne!(RegistersType::W0, RegistersType::X0); +} diff --git a/components/aarch64_sysreg/tests/system_reg_type_tests.rs b/components/aarch64_sysreg/tests/system_reg_type_tests.rs new file mode 100644 index 000000000..03b0389c5 --- /dev/null +++ b/components/aarch64_sysreg/tests/system_reg_type_tests.rs @@ -0,0 +1,56 @@ +use aarch64_sysreg::SystemRegType; + +#[test] +fn test_system_reg_type_values() { + assert_eq!(SystemRegType::OSDTRRX_EL1 as usize, 0x240000); + assert_eq!(SystemRegType::DBGBVR0_EL1 as usize, 0x280000); + assert_eq!(SystemRegType::MDSCR_EL1 as usize, 0x240004); +} + +#[test] +fn test_system_reg_type_display() { + assert_eq!(format!("{}", SystemRegType::OSDTRRX_EL1), "OSDTRRX_EL1"); + assert_eq!(format!("{}", SystemRegType::DBGBVR0_EL1), "DBGBVR0_EL1"); + assert_eq!(format!("{}", SystemRegType::MDSCR_EL1), "MDSCR_EL1"); + assert_eq!(format!("{}", SystemRegType::PSTATE_SPSEL), "PSTATE_SPSEL"); +} + +#[test] +fn test_system_reg_type_lower_hex() { + assert_eq!(format!("{:x}", SystemRegType::OSDTRRX_EL1), "240000"); + assert_eq!(format!("{:x}", SystemRegType::DBGBVR0_EL1), "280000"); +} + +#[test] +fn test_system_reg_type_upper_hex() { + assert_eq!(format!("{:X}", SystemRegType::OSDTRRX_EL1), "240000"); + assert_eq!(format!("{:X}", SystemRegType::DBGBVR0_EL1), "280000"); +} + +#[test] +fn test_system_reg_type_from_usize() { + assert_eq!(SystemRegType::from(0x240000), SystemRegType::OSDTRRX_EL1); + assert_eq!(SystemRegType::from(0x280000), SystemRegType::DBGBVR0_EL1); + assert_eq!(SystemRegType::from(0x240004), SystemRegType::MDSCR_EL1); +} + +#[test] +#[should_panic(expected = "Invalid system register value")] +fn test_system_reg_type_from_invalid() { + let _ = SystemRegType::from(0xFFFFFF); +} + +#[test] +fn test_system_reg_type_clone_copy() { + let reg = SystemRegType::MDSCR_EL1; + let reg_clone = reg.clone(); + let reg_copy = reg; + assert_eq!(reg, reg_clone); + assert_eq!(reg, reg_copy); +} + +#[test] +fn test_system_reg_type_partial_eq() { + assert_eq!(SystemRegType::MDSCR_EL1, SystemRegType::MDSCR_EL1); + assert_ne!(SystemRegType::MDSCR_EL1, SystemRegType::OSDTRRX_EL1); +} diff --git a/components/arm_vcpu/.github/workflows/push.yml b/components/arm_vcpu/.github/workflows/push.yml new file mode 100644 index 000000000..a18de4a04 --- /dev/null +++ b/components/arm_vcpu/.github/workflows/push.yml @@ -0,0 +1,147 @@ +# ═══════════════════════════════════════════════════════════════════════════════ +# 组件仓库 GitHub Actions 配置模板 +# ═══════════════════════════════════════════════════════════════════════════════ +# +# 此文件用于子仓库,当子仓库有更新时通知主仓库进行 subtree pull 同步。 +# +# 【使用步骤】 +# ───────────────────────────────────────────────────────────────────────────── +# 1. 将此文件复制到子仓库的 .github/workflows/ 目录: +# cp scripts/push.yml <子仓库>/.github/workflows/push.yml +# +# 2. 在子仓库中配置 Secret: +# GitHub 仓库 → Settings → Secrets → Actions → New repository secret +# 名称: PARENT_REPO_TOKEN +# 值: 具有主仓库 repo 权限的 Personal Access Token +# +# 3. 修改下方 env 块中的一个变量(标注了「需要修改」的行): +# PARENT_REPO - 主仓库路径,例如 rcore-os/tgoskits +# (subtree 目录由主仓库自动从 git 历史中推断,无需手动指定) +# +# 【Token 权限要求】 +# ───────────────────────────────────────────────────────────────────────────── +# PARENT_REPO_TOKEN 需要 Classic Personal Access Token,权限包括: +# - repo (Full control of private repositories) +# 或 +# - Fine-grained token: Contents (Read and Write) +# +# 【触发条件】 +# ───────────────────────────────────────────────────────────────────────────── +# - 自动触发:推送到 dev 或 main 分支时 +# - 手动触发:Actions → Notify Parent Repository → Run workflow +# +# 【工作流程】 +# ───────────────────────────────────────────────────────────────────────────── +# 子仓库 push → 触发此工作流 → 调用主仓库 API → 主仓库 subtree pull +# +# 【注意事项】 +# ───────────────────────────────────────────────────────────────────────────── +# - 主仓库需要配置接收 repository_dispatch 事件的同步工作流 +# - 如果不需要子仓库到主仓库的同步,可以不使用此文件 +# +# ═══════════════════════════════════════════════════════════════════════════════ + +name: Notify Parent Repository + +# 当有新的推送时触发 +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Get repository info + id: repo + env: + GH_REPO_NAME: ${{ github.event.repository.name }} + GH_REF_NAME: ${{ github.ref_name }} + GH_SERVER_URL: ${{ github.server_url }} + GH_REPOSITORY: ${{ github.repository }} + run: | + # 直接使用 GitHub Actions 内置变量,通过 env 传入避免 shell 注入 + COMPONENT="$GH_REPO_NAME" + BRANCH="$GH_REF_NAME" + # 构造标准 HTTPS URL,供主仓库按 URL 精确匹配 repos.list + REPO_URL="${GH_SERVER_URL}/${GH_REPOSITORY}" + + echo "component=${COMPONENT}" >> $GITHUB_OUTPUT + echo "branch=${BRANCH}" >> $GITHUB_OUTPUT + echo "repo_url=${REPO_URL}" >> $GITHUB_OUTPUT + + echo "Component: ${COMPONENT}" + echo "Branch: ${BRANCH}" + echo "Repo URL: ${REPO_URL}" + + - name: Notify parent repository + env: + # ── 需要修改 ────────────────────────────────────────────────────────── + PARENT_REPO: "rcore-os/tgoskits" # 主仓库路径 + # ── 无需修改 ────────────────────────────────────────────────────────── + DISPATCH_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} + # 将用户可控内容通过 env 传入,避免直接插值到 shell 脚本 + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + GIT_ACTOR: ${{ github.actor }} + GIT_SHA: ${{ github.sha }} + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "Notifying parent repository about update in ${COMPONENT}:${BRANCH}" + + # 使用 jq 安全构建 JSON,避免 commit message 中任何特殊字符导致注入 + PAYLOAD=$(jq -n \ + --arg component "$COMPONENT" \ + --arg branch "$BRANCH" \ + --arg repo_url "$REPO_URL" \ + --arg commit "$GIT_SHA" \ + --arg message "$COMMIT_MESSAGE" \ + --arg author "$GIT_ACTOR" \ + '{ + event_type: "subtree-update", + client_payload: { + component: $component, + branch: $branch, + repo_url: $repo_url, + commit: $commit, + message: $message, + author: $author + } + }') + + curl --fail --show-error -X POST \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${DISPATCH_TOKEN}" \ + https://api.github.com/repos/${PARENT_REPO}/dispatches \ + -d "$PAYLOAD" + + echo "Notification sent successfully" + + - name: Create summary + env: + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + GIT_SHA: ${{ github.sha }} + GIT_ACTOR: ${{ github.actor }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "## Notification Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Component**: ${COMPONENT}" >> $GITHUB_STEP_SUMMARY + echo "- **Branch**: ${BRANCH}" >> $GITHUB_STEP_SUMMARY + echo "- **Repo URL**: ${REPO_URL}" >> $GITHUB_STEP_SUMMARY + echo "- **Commit**: \`${GIT_SHA}\`" >> $GITHUB_STEP_SUMMARY + echo "- **Author**: ${GIT_ACTOR}" >> $GITHUB_STEP_SUMMARY + echo "- **Status**: ✅ Notification sent" >> $GITHUB_STEP_SUMMARY diff --git a/components/arm_vcpu/Cargo.toml b/components/arm_vcpu/Cargo.toml index 91b8f391d..3522a3d10 100644 --- a/components/arm_vcpu/Cargo.toml +++ b/components/arm_vcpu/Cargo.toml @@ -1,13 +1,13 @@ [package] edition = "2024" name = "arm_vcpu" -version = "0.2.2" +version = "0.3.0" authors = [ - "KeYang Hu ", - "Mingxian Su ", - "ShiMei Tang ", - "DeBin Luo ", - "周睿 " + "KeYang Hu ", + "Mingxian Su ", + "ShiMei Tang ", + "DeBin Luo ", + "周睿 ", ] description = "Aarch64 VCPU implementation for Arceos Hypervisor" license = "Apache-2.0" @@ -19,13 +19,13 @@ keywords = ["hypervisor", "aarch64", "vcpu"] log = "0.4" spin = "0.10" -aarch64-cpu = "10.0" +aarch64-cpu = "11.0" numeric-enum-macro = "0.2" -axerrno = "0.1.0" +axerrno = "0.2.0" percpu = { version = "0.2.3-preview.1", features = ["arm-el2"] } -axaddrspace = "0.1.5" -axdevice_base = "0.2.1" -axvcpu = "0.2" -axvisor_api = "0.1.0" +axaddrspace = "0.3" +axdevice_base = "0.2.2" +axvcpu = "0.3" +axvisor_api = "0.3" diff --git a/components/arm_vcpu/rust-toolchain.toml b/components/arm_vcpu/rust-toolchain.toml index 28393965a..3286bc46f 100644 --- a/components/arm_vcpu/rust-toolchain.toml +++ b/components/arm_vcpu/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] profile = "minimal" -channel = "nightly-2025-05-20" +channel = "nightly-2026-02-25" components = ["rust-src", "llvm-tools", "rustfmt", "clippy"] targets = [ "aarch64-unknown-none-softfloat", diff --git a/components/arm_vcpu/src/exception.rs b/components/arm_vcpu/src/exception.rs index 27e2336d3..3d14e7aef 100644 --- a/components/arm_vcpu/src/exception.rs +++ b/components/arm_vcpu/src/exception.rs @@ -293,12 +293,12 @@ fn handle_smc64_exception(ctx: &mut TrapFrame) -> AxResult { /// Handles IRQ exceptions that occur from the current exception level. /// Dispatches IRQs to the appropriate handler provided by the underlying host OS, -/// which is registered at [`crate::pcpu::IRQ_HANDLER`] during `Aarch64PerCpu::new()`. +/// which is provided by `axvisor_api::arch::handle_irq()`. #[unsafe(no_mangle)] fn current_el_irq_handler(_tf: &mut TrapFrame) { - unsafe { crate::pcpu::IRQ_HANDLER.current_ref_raw() } - .get() - .unwrap()() + // TODO: consider if returning AxVCpuExitReason::ExternalInterrupt (or another enum variant) is + // better than directly calling the handler here. + axvisor_api::arch::handle_irq() } /// Handles synchronous exceptions that occur from the current exception level. diff --git a/components/arm_vcpu/src/lib.rs b/components/arm_vcpu/src/lib.rs index e70e014c4..0b41dc6f2 100644 --- a/components/arm_vcpu/src/lib.rs +++ b/components/arm_vcpu/src/lib.rs @@ -13,6 +13,7 @@ // limitations under the License. #![no_std] +#![cfg(target_arch = "aarch64")] #![feature(doc_cfg)] #![doc = include_str!("../README.md")] diff --git a/components/arm_vcpu/src/pcpu.rs b/components/arm_vcpu/src/pcpu.rs index ece307635..e1e2c3f26 100644 --- a/components/arm_vcpu/src/pcpu.rs +++ b/components/arm_vcpu/src/pcpu.rs @@ -12,45 +12,32 @@ // See the License for the specific language governing permissions and // limitations under the License. -use core::{cell::OnceCell, marker::PhantomData}; +use core::mem; use aarch64_cpu::registers::*; use axerrno::AxResult; -use axvcpu::{AxArchPerCpu, AxVCpuHal}; +use axvcpu::AxArchPerCpu; /// Per-CPU data. A pointer to this struct is loaded into TP when a CPU starts. This structure #[repr(C)] #[repr(align(4096))] -pub struct Aarch64PerCpu { +pub struct Aarch64PerCpu { /// per cpu id pub cpu_id: usize, - _phantom: PhantomData, + /// The original value of `VBAR_EL2` (exception vector base) before enabling + /// the virtualization. + pub original_vbar_el2: u64, } -#[percpu::def_percpu] -static ORI_EXCEPTION_VECTOR_BASE: usize = 0; - -/// IRQ handler registered by underlying host OS during per-cpu initialization, -/// for dispatching IRQs to the host OS. -/// -/// Set `IRQ_HANDLER` as per-cpu variable to avoid the need of `OnceLock`. -#[percpu::def_percpu] -pub static IRQ_HANDLER: OnceCell<&(dyn Fn() + Send + Sync)> = OnceCell::new(); - unsafe extern "C" { fn exception_vector_base_vcpu(); } -impl AxArchPerCpu for Aarch64PerCpu { +impl AxArchPerCpu for Aarch64PerCpu { fn new(cpu_id: usize) -> AxResult { - // Register IRQ handler for this CPU. - let _ = unsafe { IRQ_HANDLER.current_ref_mut_raw() } - .set(&|| H::irq_hanlder()) - .map(|_| {}); - Ok(Self { cpu_id, - _phantom: PhantomData, + original_vbar_el2: 0, }) } @@ -62,11 +49,11 @@ impl AxArchPerCpu for Aarch64PerCpu { // First we save origin `exception_vector_base`. // Safety: // Todo: take care of `preemption` - unsafe { ORI_EXCEPTION_VECTOR_BASE.write_current_raw(VBAR_EL2.get() as usize) } + self.original_vbar_el2 = VBAR_EL2.get(); // Set current `VBAR_EL2` to `exception_vector_base_vcpu` // defined in this crate. - VBAR_EL2.set(exception_vector_base_vcpu as usize as _); + VBAR_EL2.set(exception_vector_base_vcpu as *const () as usize as _); HCR_EL2.modify( HCR_EL2::VM::Enable + HCR_EL2::RW::EL1IsAarch64 + HCR_EL2::TSC::EnableTrapEl1SmcToEl2, @@ -92,9 +79,13 @@ impl AxArchPerCpu for Aarch64PerCpu { // Reset `VBAR_EL2` into previous value. // Safety: // Todo: take care of `preemption` - VBAR_EL2.set(unsafe { ORI_EXCEPTION_VECTOR_BASE.read_current_raw() } as _); + VBAR_EL2.set(mem::take(&mut self.original_vbar_el2)); HCR_EL2.set(HCR_EL2::VM::Disable.into()); Ok(()) } + + fn max_guest_page_table_levels(&self) -> usize { + crate::vcpu::max_gpt_level(crate::vcpu::pa_bits()) + } } diff --git a/components/arm_vcpu/src/vcpu.rs b/components/arm_vcpu/src/vcpu.rs index 4c1bd49ef..9f467a82e 100644 --- a/components/arm_vcpu/src/vcpu.rs +++ b/components/arm_vcpu/src/vcpu.rs @@ -12,12 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use core::marker::PhantomData; - use aarch64_cpu::registers::*; use axaddrspace::{GuestPhysAddr, HostPhysAddr, device::SysRegAddr}; use axerrno::AxResult; -use axvcpu::{AxArchVCpu, AxVCpuExitReason, AxVCpuHal}; +use axvcpu::{AxArchVCpu, AxVCpuExitReason}; use crate::{ TrapFrame, @@ -53,7 +51,7 @@ pub struct VmCpuRegisters { /// A virtual CPU within a guest #[repr(C)] #[derive(Debug)] -pub struct Aarch64VCpu { +pub struct Aarch64VCpu { // DO NOT modify `guest_regs` and `host_stack_top` and their order unless you do know what you are doing! // DO NOT add anything before or between them unless you do know what you are doing! ctx: TrapFrame, @@ -61,7 +59,6 @@ pub struct Aarch64VCpu { guest_system_regs: GuestSystemRegisters, /// The MPIDR_EL1 value for the vCPU. mpidr: u64, - _phantom: PhantomData, } /// Configuration for creating a new `Aarch64VCpu` @@ -85,7 +82,7 @@ pub struct Aarch64VCpuSetupConfig { pub passthrough_timer: bool, } -impl axvcpu::AxArchVCpu for Aarch64VCpu { +impl axvcpu::AxArchVCpu for Aarch64VCpu { type CreateConfig = Aarch64VCpuCreateConfig; type SetupConfig = Aarch64VCpuSetupConfig; @@ -99,7 +96,6 @@ impl axvcpu::AxArchVCpu for Aarch64VCpu { host_stack_top: 0, guest_system_regs: GuestSystemRegisters::default(), mpidr: config.mpidr_el1, - _phantom: PhantomData, }) } @@ -158,7 +154,7 @@ impl axvcpu::AxArchVCpu for Aarch64VCpu { } // Private function -impl Aarch64VCpu { +impl Aarch64VCpu { fn init_hv(&mut self, config: Aarch64VCpuSetupConfig) { self.ctx.spsr = (SPSR_EL1::M::EL1h + SPSR_EL1::I::Masked @@ -225,7 +221,7 @@ impl Aarch64VCpu { } /// Private functions related to vcpu runtime control flow. -impl Aarch64VCpu { +impl Aarch64VCpu { /// Save host context and run guest. /// /// When a VM-Exit happens when guest's vCpu is running, @@ -320,7 +316,7 @@ impl Aarch64VCpu { let result = match exit_reason { TrapKind::Synchronous => handle_exception_sync(&mut self.ctx), TrapKind::Irq => Ok(AxVCpuExitReason::ExternalInterrupt { - vector: H::irq_fetch() as _, + vector: axvisor_api::arch::fetch_irq(), }), _ => panic!("Unhandled exception {:?}", exit_reason), }; diff --git a/components/arm_vgic/.github/workflows/push.yml b/components/arm_vgic/.github/workflows/push.yml new file mode 100644 index 000000000..a18de4a04 --- /dev/null +++ b/components/arm_vgic/.github/workflows/push.yml @@ -0,0 +1,147 @@ +# ═══════════════════════════════════════════════════════════════════════════════ +# 组件仓库 GitHub Actions 配置模板 +# ═══════════════════════════════════════════════════════════════════════════════ +# +# 此文件用于子仓库,当子仓库有更新时通知主仓库进行 subtree pull 同步。 +# +# 【使用步骤】 +# ───────────────────────────────────────────────────────────────────────────── +# 1. 将此文件复制到子仓库的 .github/workflows/ 目录: +# cp scripts/push.yml <子仓库>/.github/workflows/push.yml +# +# 2. 在子仓库中配置 Secret: +# GitHub 仓库 → Settings → Secrets → Actions → New repository secret +# 名称: PARENT_REPO_TOKEN +# 值: 具有主仓库 repo 权限的 Personal Access Token +# +# 3. 修改下方 env 块中的一个变量(标注了「需要修改」的行): +# PARENT_REPO - 主仓库路径,例如 rcore-os/tgoskits +# (subtree 目录由主仓库自动从 git 历史中推断,无需手动指定) +# +# 【Token 权限要求】 +# ───────────────────────────────────────────────────────────────────────────── +# PARENT_REPO_TOKEN 需要 Classic Personal Access Token,权限包括: +# - repo (Full control of private repositories) +# 或 +# - Fine-grained token: Contents (Read and Write) +# +# 【触发条件】 +# ───────────────────────────────────────────────────────────────────────────── +# - 自动触发:推送到 dev 或 main 分支时 +# - 手动触发:Actions → Notify Parent Repository → Run workflow +# +# 【工作流程】 +# ───────────────────────────────────────────────────────────────────────────── +# 子仓库 push → 触发此工作流 → 调用主仓库 API → 主仓库 subtree pull +# +# 【注意事项】 +# ───────────────────────────────────────────────────────────────────────────── +# - 主仓库需要配置接收 repository_dispatch 事件的同步工作流 +# - 如果不需要子仓库到主仓库的同步,可以不使用此文件 +# +# ═══════════════════════════════════════════════════════════════════════════════ + +name: Notify Parent Repository + +# 当有新的推送时触发 +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Get repository info + id: repo + env: + GH_REPO_NAME: ${{ github.event.repository.name }} + GH_REF_NAME: ${{ github.ref_name }} + GH_SERVER_URL: ${{ github.server_url }} + GH_REPOSITORY: ${{ github.repository }} + run: | + # 直接使用 GitHub Actions 内置变量,通过 env 传入避免 shell 注入 + COMPONENT="$GH_REPO_NAME" + BRANCH="$GH_REF_NAME" + # 构造标准 HTTPS URL,供主仓库按 URL 精确匹配 repos.list + REPO_URL="${GH_SERVER_URL}/${GH_REPOSITORY}" + + echo "component=${COMPONENT}" >> $GITHUB_OUTPUT + echo "branch=${BRANCH}" >> $GITHUB_OUTPUT + echo "repo_url=${REPO_URL}" >> $GITHUB_OUTPUT + + echo "Component: ${COMPONENT}" + echo "Branch: ${BRANCH}" + echo "Repo URL: ${REPO_URL}" + + - name: Notify parent repository + env: + # ── 需要修改 ────────────────────────────────────────────────────────── + PARENT_REPO: "rcore-os/tgoskits" # 主仓库路径 + # ── 无需修改 ────────────────────────────────────────────────────────── + DISPATCH_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} + # 将用户可控内容通过 env 传入,避免直接插值到 shell 脚本 + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + GIT_ACTOR: ${{ github.actor }} + GIT_SHA: ${{ github.sha }} + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "Notifying parent repository about update in ${COMPONENT}:${BRANCH}" + + # 使用 jq 安全构建 JSON,避免 commit message 中任何特殊字符导致注入 + PAYLOAD=$(jq -n \ + --arg component "$COMPONENT" \ + --arg branch "$BRANCH" \ + --arg repo_url "$REPO_URL" \ + --arg commit "$GIT_SHA" \ + --arg message "$COMMIT_MESSAGE" \ + --arg author "$GIT_ACTOR" \ + '{ + event_type: "subtree-update", + client_payload: { + component: $component, + branch: $branch, + repo_url: $repo_url, + commit: $commit, + message: $message, + author: $author + } + }') + + curl --fail --show-error -X POST \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${DISPATCH_TOKEN}" \ + https://api.github.com/repos/${PARENT_REPO}/dispatches \ + -d "$PAYLOAD" + + echo "Notification sent successfully" + + - name: Create summary + env: + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + GIT_SHA: ${{ github.sha }} + GIT_ACTOR: ${{ github.actor }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "## Notification Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Component**: ${COMPONENT}" >> $GITHUB_STEP_SUMMARY + echo "- **Branch**: ${BRANCH}" >> $GITHUB_STEP_SUMMARY + echo "- **Repo URL**: ${REPO_URL}" >> $GITHUB_STEP_SUMMARY + echo "- **Commit**: \`${GIT_SHA}\`" >> $GITHUB_STEP_SUMMARY + echo "- **Author**: ${GIT_ACTOR}" >> $GITHUB_STEP_SUMMARY + echo "- **Status**: ✅ Notification sent" >> $GITHUB_STEP_SUMMARY diff --git a/components/arm_vgic/Cargo.toml b/components/arm_vgic/Cargo.toml index 34430b4d0..5710e7955 100644 --- a/components/arm_vgic/Cargo.toml +++ b/components/arm_vgic/Cargo.toml @@ -1,5 +1,5 @@ [package] -version = "0.2.1" +version = "0.2.2" authors = [ "Mingxian Su ", "DeBin Luo ", @@ -19,15 +19,15 @@ default = [] vgicv3 = [] [dependencies] -axaddrspace = "0.1.4" -axdevice_base = "0.2.1" -axvisor_api = "0.1" +axaddrspace = "0.3" +axdevice_base = "0.2.2" +axvisor_api = "0.3" -aarch64-cpu = "10.0" +aarch64-cpu = "11.0" aarch64_sysreg = "0.1.1" -axerrno = "0.1.0" +axerrno = "0.2" bitmaps = {version = "3.2", default-features = false} log = "0.4" memory_addr = "0.4" -spin = "0.9" +spin = "0.10" tock-registers = "0.10" diff --git a/components/arm_vgic/rust-toolchain.toml b/components/arm_vgic/rust-toolchain.toml index d4da7a649..98a140ac9 100644 --- a/components/arm_vgic/rust-toolchain.toml +++ b/components/arm_vgic/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2025-05-20" +channel = "nightly-2026-02-25" components = ["rust-src", "llvm-tools", "rustfmt", "clippy"] profile = "minimal" targets = [ diff --git a/components/axaddrspace/.github/config.json b/components/axaddrspace/.github/config.json index f347e101e..2a1297146 100644 --- a/components/axaddrspace/.github/config.json +++ b/components/axaddrspace/.github/config.json @@ -1,10 +1,17 @@ { + "component": { + "name": "axaddrspace", + "crate_name": "axaddrspace" + }, "targets": [ "aarch64-unknown-none-softfloat", "x86_64-unknown-linux-gnu", "x86_64-unknown-none", "riscv64gc-unknown-none-elf" ], + "unit_test_targets": [ + "x86_64-unknown-linux-gnu" + ], "rust_components": [ "rust-src", "clippy", diff --git a/components/axaddrspace/.github/workflows/check.yml b/components/axaddrspace/.github/workflows/check.yml index 330fa15e9..018bbbeb2 100644 --- a/components/axaddrspace/.github/workflows/check.yml +++ b/components/axaddrspace/.github/workflows/check.yml @@ -1,66 +1,15 @@ -name: Quality Checks +# Quality Check Workflow +# References shared workflow from axci + +name: Check on: push: - branches: - - '**' - tags-ignore: - - '**' + branches: ['**'] + tags-ignore: ['**'] pull_request: - workflow_call: + workflow_dispatch: jobs: - load-config: - name: Load CI Configuration - runs-on: ubuntu-latest - outputs: - targets: ${{ steps.config.outputs.targets }} - rust_components: ${{ steps.config.outputs.rust_components }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Load configuration - id: config - run: | - TARGETS=$(jq -c '.targets' .github/config.json) - COMPONENTS=$(jq -r '.rust_components | join(", ")' .github/config.json) - - echo "targets=$TARGETS" >> $GITHUB_OUTPUT - echo "rust_components=$COMPONENTS" >> $GITHUB_OUTPUT - check: - name: Check - runs-on: ubuntu-latest - needs: load-config - strategy: - fail-fast: false - matrix: - target: ${{ fromJson(needs.load-config.outputs.targets) }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - with: - components: ${{ needs.load-config.outputs.rust_components }} - targets: ${{ matrix.target }} - - - name: Check rust version - run: rustc --version --verbose - - - name: Check code format - run: cargo fmt --all -- --check - - - name: Build - run: cargo build --target ${{ matrix.target }} --all-features - - - name: Run clippy - run: cargo clippy --target ${{ matrix.target }} --all-features -- -D warnings - - - name: Build documentation - env: - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - run: cargo doc --no-deps --target ${{ matrix.target }} --all-features + uses: arceos-hypervisor/axci/.github/workflows/check.yml@main diff --git a/components/axaddrspace/.github/workflows/deploy.yml b/components/axaddrspace/.github/workflows/deploy.yml index 882ba9310..37b00c09c 100644 --- a/components/axaddrspace/.github/workflows/deploy.yml +++ b/components/axaddrspace/.github/workflows/deploy.yml @@ -1,3 +1,6 @@ +# Deploy Workflow +# References shared workflow from axci + name: Deploy on: @@ -5,105 +8,9 @@ on: tags: - 'v[0-9]+.[0-9]+.[0-9]+' -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: 'pages' - cancel-in-progress: false - -env: - CARGO_TERM_COLOR: always - RUST_BACKTRACE: 1 - jobs: - verify-tag: - name: Verify Tag - runs-on: ubuntu-latest - outputs: - should_deploy: ${{ steps.check.outputs.should_deploy }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check if tag is on main or master branch - id: check - run: | - git fetch origin main master || true - BRANCHES=$(git branch -r --contains ${{ github.ref }}) - - if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then - echo "✓ Tag is on main or master branch" - echo "should_deploy=true" >> $GITHUB_OUTPUT - else - echo "✗ Tag is not on main or master branch, skipping deployment" - echo "Tag is on: $BRANCHES" - echo "should_deploy=false" >> $GITHUB_OUTPUT - fi - - - name: Verify version consistency - if: steps.check.outputs.should_deploy == 'true' - run: | - # Extract version from git tag (remove 'v' prefix) - TAG_VERSION="${{ github.ref_name }}" - TAG_VERSION="${TAG_VERSION#v}" - # Extract version from Cargo.toml - CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') - echo "Git tag version: $TAG_VERSION" - echo "Cargo.toml version: $CARGO_VERSION" - if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then - echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" - exit 1 - fi - echo "✓ Version check passed!" - - check: - uses: ./.github/workflows/check.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_deploy == 'true' - - test: - uses: ./.github/workflows/test.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_deploy == 'true' - - build: - name: Build documentation - runs-on: ubuntu-latest - needs: [verify-tag, check, test] - if: needs.verify-tag.outputs.should_deploy == 'true' - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Build docs - env: - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - run: | - cargo doc --no-deps --all-features - printf '' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: target/doc - deploy: - name: Deploy to GitHub Pages - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: [verify-tag, build] - if: needs.verify-tag.outputs.should_deploy == 'true' - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 + uses: arceos-hypervisor/axci/.github/workflows/deploy.yml@main + with: + verify_branch: true + verify_version: true diff --git a/components/axaddrspace/.github/workflows/push.yml b/components/axaddrspace/.github/workflows/push.yml new file mode 100644 index 000000000..3ff391a4a --- /dev/null +++ b/components/axaddrspace/.github/workflows/push.yml @@ -0,0 +1,16 @@ +name: Notify Parent Repository + +on: + push: + branches: + - main + - zcs + workflow_dispatch: + +jobs: + notify-parent: + name: Notify Parent Repository + # 调用 axci 仓库的可复用工作流 + uses: arceos-hypervisor/axci/.github/workflows/push.yml@main + secrets: + PARENT_REPO_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} diff --git a/components/axaddrspace/.github/workflows/release.yml b/components/axaddrspace/.github/workflows/release.yml index 2e857b48b..20f186395 100644 --- a/components/axaddrspace/.github/workflows/release.yml +++ b/components/axaddrspace/.github/workflows/release.yml @@ -1,3 +1,7 @@ +# Release Workflow +# References shared workflow from axci +# check + test must pass before release + name: Release on: @@ -6,155 +10,18 @@ on: - 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+-pre.[0-9]+' -permissions: - contents: write - jobs: - verify-tag: - name: Verify Tag - runs-on: ubuntu-latest - outputs: - should_release: ${{ steps.check.outputs.should_release }} - is_prerelease: ${{ steps.check.outputs.is_prerelease }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check tag type and branch - id: check - run: | - git fetch origin main master dev || true - - TAG="${{ github.ref_name }}" - BRANCHES=$(git branch -r --contains ${{ github.ref }}) - - echo "Tag: $TAG" - echo "Branches containing this tag: $BRANCHES" - - # Check if it's a prerelease tag - if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-pre\.[0-9]+$ ]]; then - echo "📦 Detected prerelease tag" - echo "is_prerelease=true" >> $GITHUB_OUTPUT - - if echo "$BRANCHES" | grep -q 'origin/dev'; then - echo "✓ Prerelease tag is on dev branch" - echo "should_release=true" >> $GITHUB_OUTPUT - else - echo "✗ Prerelease tag must be on dev branch, skipping release" - echo "should_release=false" >> $GITHUB_OUTPUT - fi - elif [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "📦 Detected stable release tag" - echo "is_prerelease=false" >> $GITHUB_OUTPUT - - if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then - echo "✓ Stable release tag is on main or master branch" - echo "should_release=true" >> $GITHUB_OUTPUT - else - echo "✗ Stable release tag must be on main or master branch, skipping release" - echo "should_release=false" >> $GITHUB_OUTPUT - fi - else - echo "✗ Unknown tag format, skipping release" - echo "is_prerelease=false" >> $GITHUB_OUTPUT - echo "should_release=false" >> $GITHUB_OUTPUT - fi - - - name: Verify version consistency - if: steps.check.outputs.should_release == 'true' - run: | - # Extract version from git tag (remove 'v' prefix) - TAG_VERSION="${{ github.ref_name }}" - TAG_VERSION="${TAG_VERSION#v}" - # Extract version from Cargo.toml - CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') - echo "Git tag version: $TAG_VERSION" - echo "Cargo.toml version: $CARGO_VERSION" - if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then - echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" - exit 1 - fi - echo "✓ Version check passed!" - check: - uses: ./.github/workflows/check.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_release == 'true' + uses: arceos-hypervisor/axci/.github/workflows/check.yml@main test: - uses: ./.github/workflows/test.yml - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' + uses: arceos-hypervisor/axci/.github/workflows/test.yml@main release: - name: Create GitHub Release - runs-on: ubuntu-latest - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Generate release notes - id: release_notes - run: | - CURRENT_TAG="${{ github.ref_name }}" - - # Get previous tag - PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -A1 "^${CURRENT_TAG}$" | tail -n1) - - if [ -z "$PREVIOUS_TAG" ] || [ "$PREVIOUS_TAG" == "$CURRENT_TAG" ]; then - echo "No previous tag found, this is the first release" - CHANGELOG="Initial release" - else - echo "Generating changelog from $PREVIOUS_TAG to $CURRENT_TAG" - - # Generate changelog with commit messages - CHANGELOG=$(git log --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${CURRENT_TAG}") - - if [ -z "$CHANGELOG" ]; then - CHANGELOG="No changes" - fi - fi - - # Write changelog to output file (multi-line) - { - echo "changelog<> $GITHUB_OUTPUT - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - draft: false - prerelease: ${{ needs.verify-tag.outputs.is_prerelease == 'true' }} - body: | - ## Changes - ${{ steps.release_notes.outputs.changelog }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - publish: - name: Publish to crates.io - runs-on: ubuntu-latest - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Dry run publish - run: cargo publish --dry-run - - - name: Publish to crates.io - run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + needs: [check, test] + uses: arceos-hypervisor/axci/.github/workflows/release.yml@main + with: + verify_branch: true + verify_version: true + secrets: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/components/axaddrspace/.github/workflows/test.yml b/components/axaddrspace/.github/workflows/test.yml index dc3b293d9..6a58ef8e5 100644 --- a/components/axaddrspace/.github/workflows/test.yml +++ b/components/axaddrspace/.github/workflows/test.yml @@ -1,3 +1,6 @@ +# Integration Test Workflow +# References shared workflow from axci + name: Test on: @@ -7,44 +10,8 @@ on: tags-ignore: - '**' pull_request: - workflow_call: + workflow_dispatch: jobs: - load-config: - name: Load CI Configuration - runs-on: ubuntu-latest - outputs: - targets: ${{ steps.config.outputs.targets }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Load configuration - id: config - run: | - TARGETS=$(jq -c '.targets' .github/config.json) - echo "targets=$TARGETS" >> $GITHUB_OUTPUT - test: - name: Test - runs-on: ubuntu-latest - needs: load-config - strategy: - fail-fast: false - matrix: - target: ${{ fromJson(needs.load-config.outputs.targets) }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - # - name: Run tests - # run: cargo test --target ${{ matrix.target }} --all-features -- --nocapture - - # - name: Run doc tests - # run: cargo test --target ${{ matrix.target }} --doc - - name: Run tests - run: echo "Tests are skipped!" + uses: arceos-hypervisor/axci/.github/workflows/test.yml@main diff --git a/components/axaddrspace/.gitignore b/components/axaddrspace/.gitignore index ff78c42af..22d7b02fd 100644 --- a/components/axaddrspace/.gitignore +++ b/components/axaddrspace/.gitignore @@ -2,3 +2,11 @@ /.vscode .DS_Store Cargo.lock + +# Test results (generated by shared test framework) +/test-results/ +/test_repos/ +*.log + +# Downloaded test framework +/scripts/.axci/ diff --git a/components/axaddrspace/README.md b/components/axaddrspace/README.md index c626dd6b9..3fc839875 100644 --- a/components/axaddrspace/README.md +++ b/components/axaddrspace/README.md @@ -1,143 +1,181 @@ -# axaddrspace +

axaddrspace

-**ArceOS-Hypervisor guest VM address space management module** +

ArceOS-Hypervisor guest VM address space management module

-[![CI](https://github.com/arceos-hypervisor/axaddrspace/actions/workflows/ci.yml/badge.svg)](https://github.com/arceos-hypervisor/axaddrspace/actions/workflows/ci.yml) -[![Crates.io](https://img.shields.io/crates/v/axaddrspace)](https://crates.io/crates/axaddrspace) -[![License](https://img.shields.io/badge/license-Apache%202.0%20OR%20MIT-blue)](LICENSE) +
-## Overview +[![Crates.io](https://img.shields.io/crates/v/axaddrspace.svg)](https://crates.io/crates/axaddrspace) +[![Docs.rs](https://docs.rs/axaddrspace/badge.svg)](https://docs.rs/axaddrspace) +[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/arceos-hypervisor/axaddrspace/blob/main/LICENSE) -`axaddrspace` is a core component of the [ArceOS-Hypervisor](https://github.com/arceos-hypervisor/) project that provides guest virtual machine address space management capabilities. The crate implements nested page tables and address translation for hypervisor environments, supporting multiple architectures including x86_64, AArch64, and RISC-V. +
-## Features +English | [中文](README_CN.md) -- **Multi-architecture support**: x86_64 (VMX EPT), AArch64 (Stage 2 page tables), and RISC-V nested page tables -- **Flexible memory mapping backends**: - - **Linear mapping**: For contiguous physical memory regions with known addresses - - **Allocation mapping**: Dynamic allocation with optional lazy loading support -- **Nested page fault handling**: Comprehensive page fault management for guest VMs -- **Hardware abstraction layer**: Clean interface for memory management operations -- **No-std compatible**: Designed for bare-metal hypervisor environments +# Introduction -## Architecture Support +`axaddrspace` is the guest address space management crate for the +[ArceOS-Hypervisor](https://github.com/arceos-hypervisor/) project. It provides +nested page table management, guest physical address translation, memory +mapping backends, and nested page fault handling for hypervisor environments. -### x86_64 -- VMX Extended Page Tables (EPT) -- Memory type configuration (WriteBack, Uncached, etc.) -- Execute permissions for user-mode addresses +This crate supports multiple architectures: -### AArch64 -- VMSAv8-64 Stage 2 translation tables -- Configurable MAIR_EL2 memory attributes -- EL2 privilege level support +- **x86_64** - VMX Extended Page Tables (EPT) +- **AArch64** - Stage-2 page tables +- **RISC-V** - Nested page tables based on the hypervisor extension -### RISC-V -- Nested page table implementation -- Hypervisor fence instructions (`hfence.vvma`) -- Sv39 metadata support +Key capabilities include: -## Core Components +- **`AddrSpace`** - address space creation, mapping, unmapping, and translation +- **`AxMmHal`** - hardware abstraction trait for frame allocation and address conversion +- **Linear mapping backend** - map known contiguous host physical memory ranges +- **Allocation mapping backend** - allocate frames eagerly or lazily on page faults +- **Guest memory helpers** - translate guest addresses to accessible host buffers -### Address Space Management -The `AddrSpace` struct provides: -- Virtual address range management -- Page table root address tracking -- Memory area organization -- Address translation services +Supports `#![no_std]` and is intended for bare-metal hypervisor and kernel use. -### Memory Mapping Backends -Two types of mapping backends are supported: +## Quick Start -1. **Linear Backend**: Direct mapping with constant offset between virtual and physical addresses -2. **Allocation Backend**: Dynamic memory allocation with optional population strategies +### Requirements -### Nested Page Tables -Architecture-specific nested page table implementations: -- **x86_64**: `ExtendedPageTable` with EPT entries -- **AArch64**: Stage 2 page tables with descriptor attributes -- **RISC-V**: Sv39-based nested page tables +- Rust nightly toolchain +- Rust components: `rust-src`, `clippy`, `rustfmt` -## Usage +```bash +# Install rustup (if not installed) +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -Add this to your `Cargo.toml`: - -```toml -[dependencies] -axaddrspace = "0.1.0" +# Install nightly toolchain and components +rustup install nightly +rustup component add rust-src clippy rustfmt --toolchain nightly ``` -### Basic Example +### Run Check and Test -```rust -use axaddrspace::{AddrSpace, MappingFlags, GuestPhysAddr}; -use page_table_multiarch::PagingHandler; +```bash +# 1. Clone the repository +git clone https://github.com/arceos-hypervisor/axaddrspace.git +cd axaddrspace + +# 2. Code check +./scripts/check.sh + +# 3. Run tests +./scripts/test.sh -// Create a new address space -let mut addr_space = AddrSpace::::new_empty( - GuestPhysAddr::from(0x1000_0000), - 0x1000_0000, // 256MB -)?; - -// Create a linear mapping -addr_space.map_linear( - GuestPhysAddr::from(0x1000_0000), // Guest virtual address - PhysAddr::from(0x8000_0000), // Host physical address - 0x10_0000, // 1MB - MappingFlags::READ | MappingFlags::WRITE, -)?; - -// Handle a nested page fault -let fault_handled = addr_space.handle_page_fault( - GuestPhysAddr::from(0x1000_1000), - MappingFlags::READ, -); +# 4. Run a specific integration test target directly +cargo test --test address_space ``` -### Hardware Abstraction Layer +The helper scripts download the shared `axci` test/check framework on first run. + +## Integration + +### Installation + +Add to your `Cargo.toml`: + +```toml +[dependencies] +axaddrspace = "0.3.0" +``` -Implement the `AxMmHal` trait for your platform: +### Example ```rust -use axaddrspace::{AxMmHal, HostPhysAddr, HostVirtAddr}; +use axaddrspace::{AddrSpace, AxMmHal, GuestPhysAddr, HostPhysAddr, HostVirtAddr, MappingFlags}; +use memory_addr::{PhysAddr, VirtAddr}; +use page_table_multiarch::PagingHandler; struct MyHal; impl AxMmHal for MyHal { fn alloc_frame() -> Option { - // Your frame allocation implementation + unimplemented!() } - fn dealloc_frame(paddr: HostPhysAddr) { - // Your frame deallocation implementation + fn dealloc_frame(_paddr: HostPhysAddr) { + unimplemented!() } - fn phys_to_virt(paddr: HostPhysAddr) -> HostVirtAddr { - // Your physical to virtual address conversion + fn phys_to_virt(_paddr: HostPhysAddr) -> HostVirtAddr { + unimplemented!() } - fn virt_to_phys(vaddr: HostVirtAddr) -> HostPhysAddr { - // Your virtual to physical address conversion + fn virt_to_phys(_vaddr: HostVirtAddr) -> HostPhysAddr { + unimplemented!() } } + +impl PagingHandler for MyHal { + fn alloc_frame() -> Option { + ::alloc_frame() + } + + fn dealloc_frame(paddr: PhysAddr) { + ::dealloc_frame(paddr) + } + + fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { + ::phys_to_virt(paddr) + } +} + +fn example() -> axerrno::AxResult<()> { + let base = GuestPhysAddr::from_usize(0x1000_0000); + let mut addr_space = AddrSpace::::new_empty(4, base, 0x20_0000)?; + + addr_space.map_linear( + base, + PhysAddr::from_usize(0x8000_0000), + 0x10_0000, + MappingFlags::READ | MappingFlags::WRITE, + )?; + + addr_space.map_alloc( + base + 0x10_0000, + 0x2000, + MappingFlags::READ | MappingFlags::WRITE, + false, + )?; + + let fault_handled = addr_space.handle_page_fault( + base + 0x10_0000, + MappingFlags::READ, + ); + assert!(fault_handled); + + let host_paddr = addr_space.translate(base).unwrap(); + assert_eq!(host_paddr, PhysAddr::from_usize(0x8000_0000)); + + Ok(()) +} ``` -## Configuration +### Features + +- `arm-el2`: enable AArch64 EL2 support +- `default`: includes `arm-el2` -### Feature Flags +### Documentation -- `arm-el2`: Enable AArch64 EL2 support (default) -- `default`: Includes `arm-el2` feature +Generate and view API documentation: -## Contributing +```bash +cargo doc --no-deps --open +``` -Contributions are welcome! Please feel free to submit a Pull Request. +Online documentation: [docs.rs/axaddrspace](https://docs.rs/axaddrspace) -## Repository +# Contributing -- [GitHub Repository](https://github.com/arceos-hypervisor/axaddrspace) -- [ArceOS-Hypervisor Project](https://github.com/arceos-hypervisor/) +1. Fork the repository and create a branch +2. Run local check: `./scripts/check.sh` +3. Run local tests: `./scripts/test.sh` +4. Submit PR and pass CI checks -## License +# License -Axaddrspace is licensed under the Apache License, Version 2.0. See the [LICENSE](./LICENSE) file for details. +Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details. diff --git a/components/axaddrspace/README_CN.md b/components/axaddrspace/README_CN.md new file mode 100644 index 000000000..91fcf28a2 --- /dev/null +++ b/components/axaddrspace/README_CN.md @@ -0,0 +1,179 @@ +

axaddrspace

+ +

ArceOS-Hypervisor 客户机虚拟机地址空间管理模块

+ +
+ +[![Crates.io](https://img.shields.io/crates/v/axaddrspace.svg)](https://crates.io/crates/axaddrspace) +[![Docs.rs](https://docs.rs/axaddrspace/badge.svg)](https://docs.rs/axaddrspace) +[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/arceos-hypervisor/axaddrspace/blob/main/LICENSE) + +
+ +[English](README.md) | 中文 + +# 简介 + +`axaddrspace` 是 [ArceOS-Hypervisor](https://github.com/arceos-hypervisor/) +项目中的客户机地址空间管理 crate,提供嵌套页表管理、客户机物理地址转换、内存映射后端以及嵌套页错误处理能力,面向 Hypervisor 场景使用。 + +该 crate 支持多种体系结构: + +- **x86_64** - VMX Extended Page Tables(EPT) +- **AArch64** - Stage-2 页表 +- **RISC-V** - 基于 Hypervisor 扩展的嵌套页表 + +核心能力包括: + +- **`AddrSpace`** - 地址空间创建、映射、解除映射与地址转换 +- **`AxMmHal`** - 用于页帧分配与地址转换的硬件抽象 trait +- **线性映射后端** - 映射已知的连续宿主物理内存区域 +- **分配映射后端** - 支持预分配或缺页时惰性分配页帧 +- **客户机内存辅助接口** - 将客户机地址转换为宿主可访问缓冲区 + +该库支持 `#![no_std]`,适用于裸机 Hypervisor 和内核环境。 + +## 快速开始 + +### 环境要求 + +- Rust nightly 工具链 +- Rust 组件:`rust-src`、`clippy`、`rustfmt` + +```bash +# 安装 rustup(如未安装) +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# 安装 nightly 工具链和组件 +rustup install nightly +rustup component add rust-src clippy rustfmt --toolchain nightly +``` + +### 运行检查和测试 + +```bash +# 1. 克隆仓库 +git clone https://github.com/arceos-hypervisor/axaddrspace.git +cd axaddrspace + +# 2. 代码检查 +./scripts/check.sh + +# 3. 运行测试 +./scripts/test.sh + +# 4. 直接运行指定集成测试目标 +cargo test --test address_space +``` + +辅助脚本会在首次运行时自动下载共享的 `axci` 检查/测试框架。 + +## 集成方式 + +### 安装 + +在 `Cargo.toml` 中添加: + +```toml +[dependencies] +axaddrspace = "0.3.0" +``` + +### 示例 + +```rust +use axaddrspace::{AddrSpace, AxMmHal, GuestPhysAddr, HostPhysAddr, HostVirtAddr, MappingFlags}; +use memory_addr::{PhysAddr, VirtAddr}; +use page_table_multiarch::PagingHandler; + +struct MyHal; + +impl AxMmHal for MyHal { + fn alloc_frame() -> Option { + unimplemented!() + } + + fn dealloc_frame(_paddr: HostPhysAddr) { + unimplemented!() + } + + fn phys_to_virt(_paddr: HostPhysAddr) -> HostVirtAddr { + unimplemented!() + } + + fn virt_to_phys(_vaddr: HostVirtAddr) -> HostPhysAddr { + unimplemented!() + } +} + +impl PagingHandler for MyHal { + fn alloc_frame() -> Option { + ::alloc_frame() + } + + fn dealloc_frame(paddr: PhysAddr) { + ::dealloc_frame(paddr) + } + + fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { + ::phys_to_virt(paddr) + } +} + +fn example() -> axerrno::AxResult<()> { + let base = GuestPhysAddr::from_usize(0x1000_0000); + let mut addr_space = AddrSpace::::new_empty(4, base, 0x20_0000)?; + + addr_space.map_linear( + base, + PhysAddr::from_usize(0x8000_0000), + 0x10_0000, + MappingFlags::READ | MappingFlags::WRITE, + )?; + + addr_space.map_alloc( + base + 0x10_0000, + 0x2000, + MappingFlags::READ | MappingFlags::WRITE, + false, + )?; + + let fault_handled = addr_space.handle_page_fault( + base + 0x10_0000, + MappingFlags::READ, + ); + assert!(fault_handled); + + let host_paddr = addr_space.translate(base).unwrap(); + assert_eq!(host_paddr, PhysAddr::from_usize(0x8000_0000)); + + Ok(()) +} +``` + +### 特性 + +- `arm-el2`:启用 AArch64 EL2 支持 +- `default`:默认包含 `arm-el2` + +### 文档 + +生成并查看 API 文档: + +```bash +cargo doc --no-deps --open +``` + +在线文档:[docs.rs/axaddrspace](https://docs.rs/axaddrspace) + +# 贡献 + +1. Fork 仓库并创建分支 +2. 本地运行检查:`./scripts/check.sh` +3. 本地运行测试:`./scripts/test.sh` +4. 提交 PR 并通过 CI 检查 + +# 许可证 + +本项目采用 Apache License 2.0。详见 [LICENSE](LICENSE)。 diff --git a/components/axaddrspace/rust-toolchain.toml b/components/axaddrspace/rust-toolchain.toml index 923b76f06..76d75c606 100644 --- a/components/axaddrspace/rust-toolchain.toml +++ b/components/axaddrspace/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2025-05-20" +channel = "nightly-2026-02-25" components = ["rust-src", "llvm-tools", "rustfmt", "clippy"] profile = "minimal" targets = [ diff --git a/components/axaddrspace/scripts/check.sh b/components/axaddrspace/scripts/check.sh new file mode 100755 index 000000000..1e4092ca4 --- /dev/null +++ b/components/axaddrspace/scripts/check.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# +# axaddrspace 代码检查脚本 +# 下载并调用 axci 仓库中的检查脚本 +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +COMPONENT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +COMPONENT_NAME="$(basename "$COMPONENT_DIR")" +AXCI_DIR="${SCRIPT_DIR}/.axci" +AXCI_REPO="https://github.com/arceos-hypervisor/axci.git" + +# 下载或更新 axci 仓库 +download_axci() { + if [ -d "$AXCI_DIR" ]; then + echo "Updating axci repository..." + cd "$AXCI_DIR" && git pull --quiet + else + echo "Downloading axci repository..." + git clone --quiet "$AXCI_REPO" "$AXCI_DIR" + fi +} + +# 主函数 +main() { + download_axci + + # 在组件目录中运行检查 + cd "$COMPONENT_DIR" + exec bash "$AXCI_DIR/check.sh" --component-dir "$COMPONENT_DIR" "$@" +} + +main "$@" diff --git a/components/axaddrspace/scripts/test.sh b/components/axaddrspace/scripts/test.sh new file mode 100755 index 000000000..0ac99ecd7 --- /dev/null +++ b/components/axaddrspace/scripts/test.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# +# axaddrspace 测试脚本 +# 下载并调用 axci 仓库中的测试框架 +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +COMPONENT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +AXCI_DIR="${SCRIPT_DIR}/.axci" +AXCI_REPO="https://github.com/arceos-hypervisor/axci.git" + +# 下载或更新 axci 仓库 +download_axci() { + if [ -d "$AXCI_DIR" ]; then + echo "Updating axci repository..." + cd "$AXCI_DIR" && git pull --quiet + else + echo "Downloading axci repository..." + git clone --quiet "$AXCI_REPO" "$AXCI_DIR" + fi +} + +# 主函数 +main() { + download_axci + + # 在组件目录中运行测试,自动指定当前组件 + cd "$COMPONENT_DIR" + exec bash "$AXCI_DIR/tests.sh" --component-dir "$COMPONENT_DIR" "$@" +} + +main "$@" diff --git a/components/axaddrspace/src/address_space/mod.rs b/components/axaddrspace/src/address_space/mod.rs index 19d1bb990..08799a8fa 100644 --- a/components/axaddrspace/src/address_space/mod.rs +++ b/components/axaddrspace/src/address_space/mod.rs @@ -276,330 +276,3 @@ impl Drop for AddrSpace { self.clear(); } } - -#[cfg(test)] -mod tests { - use core::sync::atomic::Ordering; - - use axin::axin; - - use super::*; - use crate::test_utils::{ - ALLOC_COUNT, BASE_PADDR, DEALLOC_COUNT, MEMORY_LEN, MockHal, mock_hal_test, - test_dealloc_count, - }; - - /// Generate an address space for the test - fn setup_test_addr_space() -> (AddrSpace, GuestPhysAddr, usize) { - const BASE: GuestPhysAddr = GuestPhysAddr::from_usize(0x10000); - const SIZE: usize = 0x10000; - let addr_space = AddrSpace::::new_empty(4, BASE, SIZE).unwrap(); - (addr_space, BASE, SIZE) - } - - #[test] - #[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(1)))] - /// Check whether an address_space can be created correctly. - /// When creating a new address_space, a frame will be allocated for the page table, - /// thus triggering an alloc_frame operation. - fn test_addrspace_creation() { - let (addr_space, base, size) = setup_test_addr_space(); - assert_eq!(addr_space.base(), base); - assert_eq!(addr_space.size(), size); - assert_eq!(addr_space.end(), base + size); - assert_eq!(ALLOC_COUNT.load(Ordering::SeqCst), 1); - } - - #[test] - #[axin(decorator(mock_hal_test))] - fn test_contains_range() { - let (addr_space, base, size) = setup_test_addr_space(); - - // Within range - assert!(addr_space.contains_range(base, 0x1000)); - assert!(addr_space.contains_range(base + 0x1000, 0x2000)); - assert!(addr_space.contains_range(base, size)); - - // Out of range - assert!(!addr_space.contains_range(base - 0x1000, 0x1000)); - assert!(!addr_space.contains_range(base + size, 0x1000)); - assert!(!addr_space.contains_range(base, size + 0x1000)); - - // Partially out of range - assert!(!addr_space.contains_range(base + 0x3000, 0xf000)); - } - - #[test] - #[axin(decorator(mock_hal_test))] - fn test_map_linear() { - let (mut addr_space, _base, _size) = setup_test_addr_space(); - let vaddr = GuestPhysAddr::from_usize(0x18000); - let paddr = PhysAddr::from_usize(0x10000); - let map_linear_size = 0x8000; // 32KB - let flags = MappingFlags::READ | MappingFlags::WRITE; - - addr_space - .map_linear(vaddr, paddr, map_linear_size, flags) - .unwrap(); - - assert_eq!(addr_space.translate(vaddr).unwrap(), paddr); - assert_eq!( - addr_space.translate(vaddr + 0x1000).unwrap(), - paddr + 0x1000 - ); - } - - #[test] - #[axin(decorator(mock_hal_test))] - fn test_map_alloc_populate() { - let (mut addr_space, _base, _size) = setup_test_addr_space(); - let vaddr = GuestPhysAddr::from_usize(0x10000); - let map_alloc_size = 0x2000; // 8KB - let flags = MappingFlags::READ | MappingFlags::WRITE; - - // Frame count before allocation: 1 root page table - let initial_allocs = ALLOC_COUNT.load(Ordering::SeqCst); - assert_eq!(initial_allocs, 1); - - // Allocate physical frames immediately - addr_space - .map_alloc(vaddr, map_alloc_size, flags, true) - .unwrap(); - - // Verify additional frames were allocated - let final_allocs = ALLOC_COUNT.load(Ordering::SeqCst); - assert!(final_allocs > initial_allocs); - - // Verify mappings exist and addresses are valid - let paddr1 = addr_space.translate(vaddr).unwrap(); - let paddr2 = addr_space.translate(vaddr + 0x1000).unwrap(); - - // Verify physical addresses are within valid range - assert!(paddr1.as_usize() >= BASE_PADDR && paddr1.as_usize() < BASE_PADDR + MEMORY_LEN); - assert!(paddr2.as_usize() >= BASE_PADDR && paddr2.as_usize() < BASE_PADDR + MEMORY_LEN); - - // Verify two pages have different physical addresses - assert_ne!(paddr1, paddr2); - } - - #[test] - #[axin(decorator(mock_hal_test))] - fn test_map_alloc_lazy() { - let (mut addr_space, _base, _size) = setup_test_addr_space(); - let vaddr = GuestPhysAddr::from_usize(0x13000); - let map_alloc_size = 0x1000; - let flags = MappingFlags::READ | MappingFlags::WRITE; - - let initial_allocs = ALLOC_COUNT.load(Ordering::SeqCst); - - // Lazy allocation - don't allocate physical frames immediately - addr_space - .map_alloc(vaddr, map_alloc_size, flags, false) - .unwrap(); - - // Frame count should only increase for page table structure, not data pages - let after_map_allocs = ALLOC_COUNT.load(Ordering::SeqCst); - assert!(after_map_allocs >= initial_allocs); // May have allocated intermediate page tables - assert!(addr_space.translate(vaddr).is_none()); - } - - #[test] - #[axin(decorator(mock_hal_test))] - fn test_page_fault_handling() { - let (mut addr_space, _base, _size) = setup_test_addr_space(); - let vaddr = GuestPhysAddr::from_usize(0x14000); - let map_alloc_size = 0x1000; - let flags = MappingFlags::READ | MappingFlags::WRITE; - - // Create lazy allocation mapping - addr_space - .map_alloc(vaddr, map_alloc_size, flags, false) - .unwrap(); - - let before_pf_allocs = ALLOC_COUNT.load(Ordering::SeqCst); - - // Simulate page fault - let handled = addr_space.handle_page_fault(vaddr, MappingFlags::READ); - - // Page fault should be handled - assert!(handled); - - // Should have allocated physical frames - let after_pf_allocs = ALLOC_COUNT.load(Ordering::SeqCst); - assert!(after_pf_allocs > before_pf_allocs); - - // Translation should succeed now - let paddr = addr_space.translate(vaddr); - assert!(paddr.is_some()); - } - - #[test] - #[axin(decorator(mock_hal_test))] - fn test_unmap() { - let (mut addr_space, _base, _size) = setup_test_addr_space(); - let vaddr = GuestPhysAddr::from_usize(0x15000); - let map_alloc_size = 0x2000; - let flags = MappingFlags::READ | MappingFlags::WRITE; - - // Create mapping - addr_space - .map_alloc(vaddr, map_alloc_size, flags, true) - .unwrap(); - - // Verify mapping exists - assert!(addr_space.translate(vaddr).is_some()); - assert!(addr_space.translate(vaddr + 0x1000).is_some()); - - let before_unmap_deallocs = DEALLOC_COUNT.load(Ordering::SeqCst); - - // Unmap - addr_space.unmap(vaddr, map_alloc_size).unwrap(); - - // Verify mapping is removed - assert!(addr_space.translate(vaddr).is_none()); - assert!(addr_space.translate(vaddr + 0x1000).is_none()); - - // Verify frames were deallocated - let after_unmap_deallocs = DEALLOC_COUNT.load(Ordering::SeqCst); - assert!(after_unmap_deallocs > before_unmap_deallocs); - } - - #[test] - #[axin(decorator(mock_hal_test))] - fn test_clear() { - let (mut addr_space, _base, _size) = setup_test_addr_space(); - let vaddr1 = GuestPhysAddr::from_usize(0x16000); - let vaddr2 = GuestPhysAddr::from_usize(0x17000); - let flags = MappingFlags::READ | MappingFlags::WRITE; - let map_alloc_size = 0x1000; - - // Create multiple mappings - addr_space - .map_alloc(vaddr1, map_alloc_size, flags, true) - .unwrap(); - addr_space - .map_alloc(vaddr2, map_alloc_size, flags, true) - .unwrap(); - - // Verify mappings exist - assert!(addr_space.translate(vaddr1).is_some()); - assert!(addr_space.translate(vaddr2).is_some()); - - let before_clear_deallocs = DEALLOC_COUNT.load(Ordering::SeqCst); - - // Clear all mappings - addr_space.clear(); - - // Verify all mappings are removed - assert!(addr_space.translate(vaddr1).is_none()); - assert!(addr_space.translate(vaddr2).is_none()); - - // Verify frames were deallocated - let after_clear_deallocs = DEALLOC_COUNT.load(Ordering::SeqCst); - assert!(after_clear_deallocs > before_clear_deallocs); - } - - #[test] - #[axin(decorator(mock_hal_test))] - fn test_translate() { - let (mut addr_space, _base, _size) = setup_test_addr_space(); - let vaddr = GuestPhysAddr::from_usize(0x18000); - let map_alloc_size = 0x1000; - let flags = MappingFlags::READ | MappingFlags::WRITE; - - // Create mapping - addr_space - .map_alloc(vaddr, map_alloc_size, flags, true) - .unwrap(); - - // Verify translation succeeds - let paddr = addr_space.translate(vaddr).expect("Translation failed"); - assert!(paddr.as_usize() >= BASE_PADDR); - assert!(paddr.as_usize() < BASE_PADDR + MEMORY_LEN); - - // Verify unmapped address translation fails - let unmapped_vaddr = GuestPhysAddr::from_usize(0x19000); - assert!(addr_space.translate(unmapped_vaddr).is_none()); - - // Verify out-of-range address translation fails - let out_of_range = GuestPhysAddr::from_usize(0x30000); - assert!(addr_space.translate(out_of_range).is_none()); - } - - #[test] - #[axin(decorator(mock_hal_test))] - fn test_translated_byte_buffer() { - let (mut addr_space, _base, _size) = setup_test_addr_space(); - let vaddr = GuestPhysAddr::from_usize(0x19000); - let map_alloc_size = 0x2000; // 8KB - let flags = MappingFlags::READ | MappingFlags::WRITE; - let buffer_size = 0x1100; - - // Create mapping - addr_space - .map_alloc(vaddr, map_alloc_size, flags, true) - .unwrap(); - - // Verify byte buffer can be obtained - let mut buffer = addr_space - .translated_byte_buffer(vaddr, buffer_size) - .expect("Failed to get byte buffer"); - - // Verify data write and read - // Fill with values ranging from 0 to 0x100 - for buffer_segment in buffer.iter_mut() { - for (i, byte) in buffer_segment.iter_mut().enumerate() { - *byte = (i % 0x100) as u8; - } - } - - // Verify data read correctness - for buffer_segment in buffer.iter_mut() { - for (i, byte) in buffer_segment.iter_mut().enumerate() { - assert_eq!(*byte, (i % 0x100) as u8); - } - } - - // Verify exceeding area size returns None - assert!( - addr_space - .translated_byte_buffer(vaddr, map_alloc_size + 0x1000) - .is_none() - ); - - // Verify unmapped address returns None - let unmapped_vaddr = GuestPhysAddr::from_usize(0x1D000); - assert!( - addr_space - .translated_byte_buffer(unmapped_vaddr, 0x100) - .is_none() - ); - } - - #[test] - #[axin(decorator(mock_hal_test))] - fn test_translate_and_get_limit() { - let (mut addr_space, _base, _size) = setup_test_addr_space(); - let vaddr = GuestPhysAddr::from_usize(0x1A000); - let map_alloc_size = 0x3000; // 12KB - let flags = MappingFlags::READ | MappingFlags::WRITE; - - // Create mapping - addr_space - .map_alloc(vaddr, map_alloc_size, flags, true) - .unwrap(); - - // Verify translation and area size retrieval - let (paddr, area_size) = addr_space.translate_and_get_limit(vaddr).unwrap(); - assert!(paddr.as_usize() >= BASE_PADDR && paddr.as_usize() < BASE_PADDR + MEMORY_LEN); - assert_eq!(area_size, map_alloc_size); - - // Verify unmapped address returns None - let unmapped_vaddr = GuestPhysAddr::from_usize(0x1E000); - assert!(addr_space.translate_and_get_limit(unmapped_vaddr).is_none()); - - // Verify out-of-range address returns None - let out_of_range = GuestPhysAddr::from_usize(0x30000); - assert!(addr_space.translate_and_get_limit(out_of_range).is_none()); - } -} diff --git a/components/axaddrspace/src/frame.rs b/components/axaddrspace/src/frame.rs index a632ea9ba..3ddfb619e 100644 --- a/components/axaddrspace/src/frame.rs +++ b/components/axaddrspace/src/frame.rs @@ -85,94 +85,3 @@ impl Drop for PhysFrame { } } } - -#[cfg(test)] -mod test { - use alloc::vec::Vec; - - use assert_matches::assert_matches; - use axin::axin; - - use super::*; - use crate::test_utils::{BASE_PADDR, MockHal, mock_hal_test, test_dealloc_count}; - - #[test] - #[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(1)))] - fn test_alloc_dealloc_cycle() { - let frame = PhysFrame::::alloc() - .unwrap_or_else(|e| panic!("Failed to allocate frame: {:?}", e)); - assert_eq!(frame.start_paddr().as_usize(), BASE_PADDR); - // frame is dropped here, dealloc_frame should be called - } - - #[test] - #[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(1)))] - fn test_alloc_zero() { - let frame = PhysFrame::::alloc_zero() - .unwrap_or_else(|e| panic!("Failed to allocate zero frame: {:?}", e)); - assert_eq!(frame.start_paddr().as_usize(), BASE_PADDR); - let ptr = frame.as_mut_ptr(); - let page = unsafe { &*(ptr as *const [u8; PAGE_SIZE]) }; - assert!(page.iter().all(|&x| x == 0)); - } - - #[test] - #[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(1)))] - fn test_fill_operation() { - let mut frame = PhysFrame::::alloc() - .unwrap_or_else(|e| panic!("Failed to allocate frame: {:?}", e)); - assert_eq!(frame.start_paddr().as_usize(), BASE_PADDR); - frame.fill(0xAA); - let ptr = frame.as_mut_ptr(); - let page = unsafe { &*(ptr as *const [u8; PAGE_SIZE]) }; - assert!(page.iter().all(|&x| x == 0xAA)); - } - - #[test] - #[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(5)))] - fn test_fill_multiple_frames() { - const NUM_FRAMES: usize = 5; - - let mut frames = Vec::new(); - let mut patterns = Vec::new(); - - for i in 0..NUM_FRAMES { - let mut frame = PhysFrame::::alloc().unwrap(); - let pattern = (0xA0 + i) as u8; - frame.fill(pattern); - frames.push(frame); - patterns.push(pattern); - } - - for i in 0..NUM_FRAMES { - let actual_page = unsafe { &*(frames[i].as_mut_ptr() as *mut [u8; PAGE_SIZE]) }; - let expected_page = &[patterns[i]; PAGE_SIZE]; - - assert_eq!( - actual_page, expected_page, - "Frame verification failed for frame index {i}: Expected pattern 0x{:02x}", - patterns[i] - ); - } - } - - #[test] - #[should_panic(expected = "uninitialized PhysFrame")] - fn test_uninit_access() { - // This test verifies that accessing an uninitialized PhysFrame (created with `unsafe { uninit() }`) - // leads to a panic when trying to retrieve its physical address. - let frame = unsafe { PhysFrame::::uninit() }; - frame.start_paddr(); // This should panic - } - - #[test] - #[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(0)))] - fn test_alloc_no_memory() { - // Configure MockHal to simulate an allocation failure. - MockHal::set_alloc_fail(true); - let result = PhysFrame::::alloc(); - // Assert that allocation failed and verify the specific error type. - assert_matches!(result, Err(axerrno::AxError::NoMemory)); - MockHal::set_alloc_fail(false); // Reset for other tests - } -} diff --git a/components/axaddrspace/src/lib.rs b/components/axaddrspace/src/lib.rs index 0a41f7757..ea57bfa79 100644 --- a/components/axaddrspace/src/lib.rs +++ b/components/axaddrspace/src/lib.rs @@ -54,6 +54,3 @@ fn mapping_err_to_ax_err(err: MappingError) -> AxError { MappingError::BadState => AxError::BadState, } } - -#[cfg(test)] -pub(crate) mod test_utils; diff --git a/components/axaddrspace/src/memory_accessor.rs b/components/axaddrspace/src/memory_accessor.rs index bbaf46ca3..6439d5789 100644 --- a/components/axaddrspace/src/memory_accessor.rs +++ b/components/axaddrspace/src/memory_accessor.rs @@ -198,268 +198,3 @@ pub trait GuestMemoryAccessor { self.write_obj(guest_addr, val) } } - -#[cfg(test)] -mod tests { - use axin::axin; - use memory_addr::PhysAddr; - - use super::*; - use crate::test_utils::{BASE_PADDR, mock_hal_test}; - - /// Mock implementation of GuestMemoryAccessor for testing - struct MockTranslator { - base_addr: PhysAddr, - memory_size: usize, - } - - impl MockTranslator { - pub fn new(base_addr: PhysAddr, memory_size: usize) -> Self { - Self { - base_addr, - memory_size, - } - } - } - - impl GuestMemoryAccessor for MockTranslator { - fn translate_and_get_limit(&self, guest_addr: GuestPhysAddr) -> Option<(PhysAddr, usize)> { - // Simple mapping: guest address directly maps to mock memory region - let offset = guest_addr.as_usize(); - if offset < self.memory_size { - // Convert physical address to virtual address for actual memory access - let phys_addr = - PhysAddr::from_usize(BASE_PADDR + self.base_addr.as_usize() + offset); - let virt_addr = crate::test_utils::MockHal::mock_phys_to_virt(phys_addr); - let accessible_size = self.memory_size - offset; - Some((PhysAddr::from_usize(virt_addr.as_usize()), accessible_size)) - } else { - None - } - } - } - - #[test] - #[axin(decorator(mock_hal_test))] - fn test_basic_read_write_operations() { - let translator = - MockTranslator::new(PhysAddr::from_usize(0), crate::test_utils::MEMORY_LEN); - - // Test u32 read/write operations - let test_addr = GuestPhysAddr::from_usize(0x100); - let test_value: u32 = 0x12345678; - - // Write a u32 value - translator - .write_obj(test_addr, test_value) - .expect("Failed to write u32 value"); - - // Read back the u32 value - let read_value: u32 = translator - .read_obj(test_addr) - .expect("Failed to read u32 value"); - - assert_eq!( - read_value, test_value, - "Read value should match written value" - ); - - // Test buffer read/write operations - let buffer_addr = GuestPhysAddr::from_usize(0x200); - let test_buffer = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]; - - // Write buffer - translator - .write_buffer(buffer_addr, &test_buffer) - .expect("Failed to write buffer"); - - // Read buffer back - let mut read_buffer = [0u8; 8]; - translator - .read_buffer(buffer_addr, &mut read_buffer) - .expect("Failed to read buffer"); - - assert_eq!( - read_buffer, test_buffer, - "Read buffer should match written buffer" - ); - - // Test error handling with invalid address - let invalid_addr = GuestPhysAddr::from_usize(crate::test_utils::MEMORY_LEN + 0x1000); - let result: AxResult = translator.read_obj(invalid_addr); - assert!(result.is_err(), "Reading from invalid address should fail"); - - let result = translator.write_obj(invalid_addr, 42u32); - assert!(result.is_err(), "Writing to invalid address should fail"); - } - - #[test] - #[axin(decorator(mock_hal_test))] - fn test_two_vm_isolation() { - // Create two different translators to simulate two different VMs - let vm1_translator = - MockTranslator::new(PhysAddr::from_usize(0), crate::test_utils::MEMORY_LEN / 2); // Offset for VM1 - let vm2_translator = MockTranslator::new( - PhysAddr::from_usize(crate::test_utils::MEMORY_LEN / 2), - crate::test_utils::MEMORY_LEN, - ); // Offset for VM2 - - // Both VMs write to the same guest address but different host memory regions - let guest_addr = GuestPhysAddr::from_usize(0x100); - let vm1_data: u64 = 0xDEADBEEFCAFEBABE; - let vm2_data: u64 = 0x1234567890ABCDEF; - - // VM1 writes its data - vm1_translator - .write_obj(guest_addr, vm1_data) - .expect("VM1 failed to write data"); - - // VM2 writes its data - vm2_translator - .write_obj(guest_addr, vm2_data) - .expect("VM2 failed to write data"); - - // Both VMs read back their own data - should be isolated - let vm1_read: u64 = vm1_translator - .read_obj(guest_addr) - .expect("VM1 failed to read data"); - let vm2_read: u64 = vm2_translator - .read_obj(guest_addr) - .expect("VM2 failed to read data"); - - // Verify isolation: each VM should read its own data - assert_eq!(vm1_read, vm1_data, "VM1 should read its own data"); - assert_eq!(vm2_read, vm2_data, "VM2 should read its own data"); - assert_ne!( - vm1_read, vm2_read, - "VM1 and VM2 should have different data (isolation)" - ); - - // Test buffer operations with different patterns - let buffer_addr = GuestPhysAddr::from_usize(0x200); - let vm1_buffer = [0xAA; 16]; // Pattern for VM1 - let vm2_buffer = [0x55; 16]; // Pattern for VM2 - - // Both VMs write their patterns - vm1_translator - .write_buffer(buffer_addr, &vm1_buffer) - .expect("VM1 failed to write buffer"); - vm2_translator - .write_buffer(buffer_addr, &vm2_buffer) - .expect("VM2 failed to write buffer"); - - // Read back and verify isolation - let mut vm1_read_buffer = [0u8; 16]; - let mut vm2_read_buffer = [0u8; 16]; - - vm1_translator - .read_buffer(buffer_addr, &mut vm1_read_buffer) - .expect("VM1 failed to read buffer"); - vm2_translator - .read_buffer(buffer_addr, &mut vm2_read_buffer) - .expect("VM2 failed to read buffer"); - - assert_eq!( - vm1_read_buffer, vm1_buffer, - "VM1 should read its own buffer pattern" - ); - assert_eq!( - vm2_read_buffer, vm2_buffer, - "VM2 should read its own buffer pattern" - ); - assert_ne!( - vm1_read_buffer, vm2_read_buffer, - "VM buffers should be isolated" - ); - - // Test that VM1 cannot access VM2's address space (beyond its limit) - let vm2_only_addr = GuestPhysAddr::from_usize(crate::test_utils::MEMORY_LEN / 2 + 0x100); - let result: AxResult = vm1_translator.read_obj(vm2_only_addr); - assert!( - result.is_err(), - "VM1 should not be able to access VM2's exclusive address space" - ); - } - - #[test] - #[axin(decorator(mock_hal_test))] - fn test_cross_page_access() { - let translator = - MockTranslator::new(PhysAddr::from_usize(0), crate::test_utils::MEMORY_LEN); - - // Test cross-region buffer operations - // Place buffer near a region boundary to test multi-region access - let cross_region_addr = GuestPhysAddr::from_usize(4096 - 8); // 8 bytes before 4K boundary - let test_data = [ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, - 0x0F, 0x10, - ]; // 16 bytes - - // Write cross-region data - translator - .write_buffer(cross_region_addr, &test_data) - .expect("Failed to write cross-region buffer"); - - // Read cross-region data back - let mut read_data = [0u8; 16]; - translator - .read_buffer(cross_region_addr, &mut read_data) - .expect("Failed to read cross-region buffer"); - - assert_eq!( - read_data, test_data, - "Cross-region read should match written data" - ); - - // Test individual byte access across region boundary - for (i, &expected_byte) in test_data.iter().enumerate() { - let byte_addr = GuestPhysAddr::from_usize(cross_region_addr.as_usize() + i); - let read_byte: u8 = translator - .read_obj(byte_addr) - .expect("Failed to read individual byte"); - assert_eq!( - read_byte, expected_byte, - "Byte at offset {} should match", - i - ); - } - } - - #[test] - #[axin(decorator(mock_hal_test))] - fn test_region_boundary_edge_cases() { - let translator = - MockTranslator::new(PhysAddr::from_usize(0), crate::test_utils::MEMORY_LEN); - - let boundary_addr = GuestPhysAddr::from_usize(4096); - let boundary_data = [0xAB, 0xCD, 0xEF, 0x12]; - - translator - .write_buffer(boundary_addr, &boundary_data) - .expect("Failed to write at boundary"); - - let mut read_boundary = [0u8; 4]; - translator - .read_buffer(boundary_addr, &mut read_boundary) - .expect("Failed to read at boundary"); - - assert_eq!(read_boundary, boundary_data, "Boundary data should match"); - - // Test zero-size buffer (should not fail) - let empty_buffer: &[u8] = &[]; - translator - .write_buffer(boundary_addr, empty_buffer) - .expect("Empty buffer write should succeed"); - - let mut empty_read: &mut [u8] = &mut []; - translator - .read_buffer(boundary_addr, &mut empty_read) - .expect("Empty buffer read should succeed"); - - // Test single byte at boundary (should work fine) - let single_byte = [0x42]; - translator - .write_buffer(boundary_addr, &single_byte) - .expect("Single byte write should succeed"); - } -} diff --git a/components/axaddrspace/src/npt/arch/x86_64.rs b/components/axaddrspace/src/npt/arch/x86_64.rs index 65b62617f..82a952c17 100644 --- a/components/axaddrspace/src/npt/arch/x86_64.rs +++ b/components/axaddrspace/src/npt/arch/x86_64.rs @@ -188,15 +188,18 @@ impl PagingMetaData for ExtendedPageTableMetadata { type VirtAddr = GuestPhysAddr; - // Under the x86 architecture, the flush_tlb operation will invoke the ring0 instruction, - // causing the test to trigger a SIGSEGV exception. + // Under the x86 architecture, flushing the TLB requires privileged + // instructions. Hosted binaries such as integration tests run in ring 3, + // so issue TLB invalidations only for bare-metal targets. #[allow(unused_variables)] fn flush_tlb(vaddr: Option) { - #[cfg(not(test))] - if let Some(vaddr) = vaddr { - unsafe { x86::tlb::flush(vaddr.into()) } - } else { - unsafe { x86::tlb::flush_all() } + #[cfg(target_os = "none")] + { + if let Some(vaddr) = vaddr { + unsafe { x86::tlb::flush(vaddr.into()) } + } else { + unsafe { x86::tlb::flush_all() } + } } } } diff --git a/components/axaddrspace/tests/address_space.rs b/components/axaddrspace/tests/address_space.rs new file mode 100644 index 000000000..80b8e125f --- /dev/null +++ b/components/axaddrspace/tests/address_space.rs @@ -0,0 +1,338 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod test_utils; + +use core::sync::atomic::Ordering; + +use axaddrspace::{AddrSpace, GuestPhysAddr, MappingFlags}; +use axin::axin; +use memory_addr::PhysAddr; +use test_utils::{ + ALLOC_COUNT, BASE_PADDR, DEALLOC_COUNT, MEMORY_LEN, MockHal, mock_hal_test, test_dealloc_count, +}; + +/// Generate an address space for the test +fn setup_test_addr_space() -> (AddrSpace, GuestPhysAddr, usize) { + const BASE: GuestPhysAddr = GuestPhysAddr::from_usize(0x10000); + const SIZE: usize = 0x10000; + let addr_space = AddrSpace::::new_empty(4, BASE, SIZE).unwrap(); + (addr_space, BASE, SIZE) +} + +#[test] +#[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(1)))] +/// Check whether an address_space can be created correctly. +/// When creating a new address_space, a frame will be allocated for the page table, +/// thus triggering an alloc_frame operation. +fn test_addrspace_creation() { + let (addr_space, base, size) = setup_test_addr_space(); + assert_eq!(addr_space.base(), base); + assert_eq!(addr_space.size(), size); + assert_eq!(addr_space.end(), base + size); + assert_eq!(ALLOC_COUNT.load(Ordering::SeqCst), 1); +} + +#[test] +#[axin(decorator(mock_hal_test))] +fn test_contains_range() { + let (addr_space, base, size) = setup_test_addr_space(); + + // Within range + assert!(addr_space.contains_range(base, 0x1000)); + assert!(addr_space.contains_range(base + 0x1000, 0x2000)); + assert!(addr_space.contains_range(base, size)); + + // Out of range + assert!(!addr_space.contains_range(base - 0x1000, 0x1000)); + assert!(!addr_space.contains_range(base + size, 0x1000)); + assert!(!addr_space.contains_range(base, size + 0x1000)); + + // Partially out of range + assert!(!addr_space.contains_range(base + 0x3000, 0xf000)); +} + +#[test] +#[axin(decorator(mock_hal_test))] +fn test_map_linear() { + let (mut addr_space, _base, _size) = setup_test_addr_space(); + let vaddr = GuestPhysAddr::from_usize(0x18000); + let paddr = PhysAddr::from_usize(0x10000); + let map_linear_size = 0x8000; // 32KB + let flags = MappingFlags::READ | MappingFlags::WRITE; + + addr_space + .map_linear(vaddr, paddr, map_linear_size, flags) + .unwrap(); + + assert_eq!(addr_space.translate(vaddr).unwrap(), paddr); + assert_eq!( + addr_space.translate(vaddr + 0x1000).unwrap(), + paddr + 0x1000 + ); +} + +#[test] +#[axin(decorator(mock_hal_test))] +fn test_map_alloc_populate() { + let (mut addr_space, _base, _size) = setup_test_addr_space(); + let vaddr = GuestPhysAddr::from_usize(0x10000); + let map_alloc_size = 0x2000; // 8KB + let flags = MappingFlags::READ | MappingFlags::WRITE; + + // Frame count before allocation: 1 root page table + let initial_allocs = ALLOC_COUNT.load(Ordering::SeqCst); + assert_eq!(initial_allocs, 1); + + // Allocate physical frames immediately + addr_space + .map_alloc(vaddr, map_alloc_size, flags, true) + .unwrap(); + + // Verify additional frames were allocated + let final_allocs = ALLOC_COUNT.load(Ordering::SeqCst); + assert!(final_allocs > initial_allocs); + + // Verify mappings exist and addresses are valid + let paddr1 = addr_space.translate(vaddr).unwrap(); + let paddr2 = addr_space.translate(vaddr + 0x1000).unwrap(); + + // Verify physical addresses are within valid range + assert!(paddr1.as_usize() >= BASE_PADDR && paddr1.as_usize() < BASE_PADDR + MEMORY_LEN); + assert!(paddr2.as_usize() >= BASE_PADDR && paddr2.as_usize() < BASE_PADDR + MEMORY_LEN); + + // Verify two pages have different physical addresses + assert_ne!(paddr1, paddr2); +} + +#[test] +#[axin(decorator(mock_hal_test))] +fn test_map_alloc_lazy() { + let (mut addr_space, _base, _size) = setup_test_addr_space(); + let vaddr = GuestPhysAddr::from_usize(0x13000); + let map_alloc_size = 0x1000; + let flags = MappingFlags::READ | MappingFlags::WRITE; + + let initial_allocs = ALLOC_COUNT.load(Ordering::SeqCst); + + // Lazy allocation - don't allocate physical frames immediately + addr_space + .map_alloc(vaddr, map_alloc_size, flags, false) + .unwrap(); + + // Frame count should only increase for page table structure, not data pages + let after_map_allocs = ALLOC_COUNT.load(Ordering::SeqCst); + assert!(after_map_allocs >= initial_allocs); // May have allocated intermediate page tables + assert!(addr_space.translate(vaddr).is_none()); +} + +#[test] +#[axin(decorator(mock_hal_test))] +fn test_page_fault_handling() { + let (mut addr_space, _base, _size) = setup_test_addr_space(); + let vaddr = GuestPhysAddr::from_usize(0x14000); + let map_alloc_size = 0x1000; + let flags = MappingFlags::READ | MappingFlags::WRITE; + + // Create lazy allocation mapping + addr_space + .map_alloc(vaddr, map_alloc_size, flags, false) + .unwrap(); + + let before_pf_allocs = ALLOC_COUNT.load(Ordering::SeqCst); + + // Simulate page fault + let handled = addr_space.handle_page_fault(vaddr, MappingFlags::READ); + + // Page fault should be handled + assert!(handled); + + // Should have allocated physical frames + let after_pf_allocs = ALLOC_COUNT.load(Ordering::SeqCst); + assert!(after_pf_allocs > before_pf_allocs); + + // Translation should succeed now + let paddr = addr_space.translate(vaddr); + assert!(paddr.is_some()); +} + +#[test] +#[axin(decorator(mock_hal_test))] +fn test_unmap() { + let (mut addr_space, _base, _size) = setup_test_addr_space(); + let vaddr = GuestPhysAddr::from_usize(0x15000); + let map_alloc_size = 0x2000; + let flags = MappingFlags::READ | MappingFlags::WRITE; + + // Create mapping + addr_space + .map_alloc(vaddr, map_alloc_size, flags, true) + .unwrap(); + + // Verify mapping exists + assert!(addr_space.translate(vaddr).is_some()); + assert!(addr_space.translate(vaddr + 0x1000).is_some()); + + let before_unmap_deallocs = DEALLOC_COUNT.load(Ordering::SeqCst); + + // Unmap + addr_space.unmap(vaddr, map_alloc_size).unwrap(); + + // Verify mapping is removed + assert!(addr_space.translate(vaddr).is_none()); + assert!(addr_space.translate(vaddr + 0x1000).is_none()); + + // Verify frames were deallocated + let after_unmap_deallocs = DEALLOC_COUNT.load(Ordering::SeqCst); + assert!(after_unmap_deallocs > before_unmap_deallocs); +} + +#[test] +#[axin(decorator(mock_hal_test))] +fn test_clear() { + let (mut addr_space, _base, _size) = setup_test_addr_space(); + let vaddr1 = GuestPhysAddr::from_usize(0x16000); + let vaddr2 = GuestPhysAddr::from_usize(0x17000); + let flags = MappingFlags::READ | MappingFlags::WRITE; + let map_alloc_size = 0x1000; + + // Create multiple mappings + addr_space + .map_alloc(vaddr1, map_alloc_size, flags, true) + .unwrap(); + addr_space + .map_alloc(vaddr2, map_alloc_size, flags, true) + .unwrap(); + + // Verify mappings exist + assert!(addr_space.translate(vaddr1).is_some()); + assert!(addr_space.translate(vaddr2).is_some()); + + let before_clear_deallocs = DEALLOC_COUNT.load(Ordering::SeqCst); + + // Clear all mappings + addr_space.clear(); + + // Verify all mappings are removed + assert!(addr_space.translate(vaddr1).is_none()); + assert!(addr_space.translate(vaddr2).is_none()); + + // Verify frames were deallocated + let after_clear_deallocs = DEALLOC_COUNT.load(Ordering::SeqCst); + assert!(after_clear_deallocs > before_clear_deallocs); +} + +#[test] +#[axin(decorator(mock_hal_test))] +fn test_translate() { + let (mut addr_space, _base, _size) = setup_test_addr_space(); + let vaddr = GuestPhysAddr::from_usize(0x18000); + let map_alloc_size = 0x1000; + let flags = MappingFlags::READ | MappingFlags::WRITE; + + // Create mapping + addr_space + .map_alloc(vaddr, map_alloc_size, flags, true) + .unwrap(); + + // Verify translation succeeds + let paddr = addr_space.translate(vaddr).expect("Translation failed"); + assert!(paddr.as_usize() >= BASE_PADDR); + assert!(paddr.as_usize() < BASE_PADDR + MEMORY_LEN); + + // Verify unmapped address translation fails + let unmapped_vaddr = GuestPhysAddr::from_usize(0x19000); + assert!(addr_space.translate(unmapped_vaddr).is_none()); + + // Verify out-of-range address translation fails + let out_of_range = GuestPhysAddr::from_usize(0x30000); + assert!(addr_space.translate(out_of_range).is_none()); +} + +#[test] +#[axin(decorator(mock_hal_test))] +fn test_translated_byte_buffer() { + let (mut addr_space, _base, _size) = setup_test_addr_space(); + let vaddr = GuestPhysAddr::from_usize(0x19000); + let map_alloc_size = 0x2000; // 8KB + let flags = MappingFlags::READ | MappingFlags::WRITE; + let buffer_size = 0x1100; + + // Create mapping + addr_space + .map_alloc(vaddr, map_alloc_size, flags, true) + .unwrap(); + + // Verify byte buffer can be obtained + let mut buffer = addr_space + .translated_byte_buffer(vaddr, buffer_size) + .expect("Failed to get byte buffer"); + + // Verify data write and read + // Fill with values ranging from 0 to 0x100 + for buffer_segment in buffer.iter_mut() { + for (i, byte) in buffer_segment.iter_mut().enumerate() { + *byte = (i % 0x100) as u8; + } + } + + // Verify data read correctness + for buffer_segment in buffer.iter_mut() { + for (i, byte) in buffer_segment.iter_mut().enumerate() { + assert_eq!(*byte, (i % 0x100) as u8); + } + } + + // Verify exceeding area size returns None + assert!( + addr_space + .translated_byte_buffer(vaddr, map_alloc_size + 0x1000) + .is_none() + ); + + // Verify unmapped address returns None + let unmapped_vaddr = GuestPhysAddr::from_usize(0x1D000); + assert!( + addr_space + .translated_byte_buffer(unmapped_vaddr, 0x100) + .is_none() + ); +} + +#[test] +#[axin(decorator(mock_hal_test))] +fn test_translate_and_get_limit() { + let (mut addr_space, _base, _size) = setup_test_addr_space(); + let vaddr = GuestPhysAddr::from_usize(0x1A000); + let map_alloc_size = 0x3000; // 12KB + let flags = MappingFlags::READ | MappingFlags::WRITE; + + // Create mapping + addr_space + .map_alloc(vaddr, map_alloc_size, flags, true) + .unwrap(); + + // Verify translation and area size retrieval + let (paddr, area_size) = addr_space.translate_and_get_limit(vaddr).unwrap(); + assert!(paddr.as_usize() >= BASE_PADDR && paddr.as_usize() < BASE_PADDR + MEMORY_LEN); + assert_eq!(area_size, map_alloc_size); + + // Verify unmapped address returns None + let unmapped_vaddr = GuestPhysAddr::from_usize(0x1E000); + assert!(addr_space.translate_and_get_limit(unmapped_vaddr).is_none()); + + // Verify out-of-range address returns None + let out_of_range = GuestPhysAddr::from_usize(0x30000); + assert!(addr_space.translate_and_get_limit(out_of_range).is_none()); +} diff --git a/components/axaddrspace/tests/frame.rs b/components/axaddrspace/tests/frame.rs new file mode 100644 index 000000000..5a8b28a44 --- /dev/null +++ b/components/axaddrspace/tests/frame.rs @@ -0,0 +1,105 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate alloc; + +mod test_utils; + +use alloc::vec::Vec; + +use assert_matches::assert_matches; +use axaddrspace::PhysFrame; +use axin::axin; +use memory_addr::PAGE_SIZE_4K as PAGE_SIZE; +use test_utils::{BASE_PADDR, MockHal, mock_hal_test, test_dealloc_count}; + +#[test] +#[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(1)))] +fn test_alloc_dealloc_cycle() { + let frame = PhysFrame::::alloc() + .unwrap_or_else(|e| panic!("Failed to allocate frame: {:?}", e)); + assert_eq!(frame.start_paddr().as_usize(), BASE_PADDR); + // frame is dropped here, dealloc_frame should be called +} + +#[test] +#[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(1)))] +fn test_alloc_zero() { + let frame = PhysFrame::::alloc_zero() + .unwrap_or_else(|e| panic!("Failed to allocate zero frame: {:?}", e)); + assert_eq!(frame.start_paddr().as_usize(), BASE_PADDR); + let ptr = frame.as_mut_ptr(); + let page = unsafe { &*(ptr as *const [u8; PAGE_SIZE]) }; + assert!(page.iter().all(|&x| x == 0)); +} + +#[test] +#[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(1)))] +fn test_fill_operation() { + let mut frame = PhysFrame::::alloc() + .unwrap_or_else(|e| panic!("Failed to allocate frame: {:?}", e)); + assert_eq!(frame.start_paddr().as_usize(), BASE_PADDR); + frame.fill(0xAA); + let ptr = frame.as_mut_ptr(); + let page = unsafe { &*(ptr as *const [u8; PAGE_SIZE]) }; + assert!(page.iter().all(|&x| x == 0xAA)); +} + +#[test] +#[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(5)))] +fn test_fill_multiple_frames() { + const NUM_FRAMES: usize = 5; + + let mut frames = Vec::new(); + let mut patterns = Vec::new(); + + for i in 0..NUM_FRAMES { + let mut frame = PhysFrame::::alloc().unwrap(); + let pattern = (0xA0 + i) as u8; + frame.fill(pattern); + frames.push(frame); + patterns.push(pattern); + } + + for i in 0..NUM_FRAMES { + let actual_page = unsafe { &*(frames[i].as_mut_ptr() as *mut [u8; PAGE_SIZE]) }; + let expected_page = &[patterns[i]; PAGE_SIZE]; + + assert_eq!( + actual_page, expected_page, + "Frame verification failed for frame index {i}: Expected pattern 0x{:02x}", + patterns[i] + ); + } +} + +#[test] +#[should_panic(expected = "uninitialized PhysFrame")] +fn test_uninit_access() { + // This test verifies that accessing an uninitialized PhysFrame (created with `unsafe { uninit() }`) + // leads to a panic when trying to retrieve its physical address. + let frame = unsafe { PhysFrame::::uninit() }; + frame.start_paddr(); // This should panic +} + +#[test] +#[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(0)))] +fn test_alloc_no_memory() { + // Configure MockHal to simulate an allocation failure. + MockHal::set_alloc_fail(true); + let result = PhysFrame::::alloc(); + // Assert that allocation failed and verify the specific error type. + assert_matches!(result, Err(axerrno::AxError::NoMemory)); + MockHal::set_alloc_fail(false); // Reset for other tests +} diff --git a/components/axaddrspace/tests/memory_accessor.rs b/components/axaddrspace/tests/memory_accessor.rs new file mode 100644 index 000000000..95f4dc460 --- /dev/null +++ b/components/axaddrspace/tests/memory_accessor.rs @@ -0,0 +1,269 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod test_utils; + +use axaddrspace::{GuestMemoryAccessor, GuestPhysAddr}; +use axerrno::AxResult; +use axin::axin; +use memory_addr::PhysAddr; +use test_utils::{BASE_PADDR, MEMORY_LEN, MockHal, mock_hal_test}; + +/// Mock implementation of GuestMemoryAccessor for testing +struct MockTranslator { + base_addr: PhysAddr, + memory_size: usize, +} + +impl MockTranslator { + pub fn new(base_addr: PhysAddr, memory_size: usize) -> Self { + Self { + base_addr, + memory_size, + } + } +} + +impl GuestMemoryAccessor for MockTranslator { + fn translate_and_get_limit(&self, guest_addr: GuestPhysAddr) -> Option<(PhysAddr, usize)> { + // Simple mapping: guest address directly maps to mock memory region + let offset = guest_addr.as_usize(); + if offset < self.memory_size { + // Convert physical address to virtual address for actual memory access + let phys_addr = PhysAddr::from_usize(BASE_PADDR + self.base_addr.as_usize() + offset); + let virt_addr = MockHal::mock_phys_to_virt(phys_addr); + let accessible_size = self.memory_size - offset; + Some((PhysAddr::from_usize(virt_addr.as_usize()), accessible_size)) + } else { + None + } + } +} + +#[test] +#[axin(decorator(mock_hal_test))] +fn test_basic_read_write_operations() { + let translator = MockTranslator::new(PhysAddr::from_usize(0), MEMORY_LEN); + + // Test u32 read/write operations + let test_addr = GuestPhysAddr::from_usize(0x100); + let test_value: u32 = 0x12345678; + + // Write a u32 value + translator + .write_obj(test_addr, test_value) + .expect("Failed to write u32 value"); + + // Read back the u32 value + let read_value: u32 = translator + .read_obj(test_addr) + .expect("Failed to read u32 value"); + + assert_eq!( + read_value, test_value, + "Read value should match written value" + ); + + // Test buffer read/write operations + let buffer_addr = GuestPhysAddr::from_usize(0x200); + let test_buffer = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]; + + // Write buffer + translator + .write_buffer(buffer_addr, &test_buffer) + .expect("Failed to write buffer"); + + // Read buffer back + let mut read_buffer = [0u8; 8]; + translator + .read_buffer(buffer_addr, &mut read_buffer) + .expect("Failed to read buffer"); + + assert_eq!( + read_buffer, test_buffer, + "Read buffer should match written buffer" + ); + + // Test error handling with invalid address + let invalid_addr = GuestPhysAddr::from_usize(MEMORY_LEN + 0x1000); + let result: AxResult = translator.read_obj(invalid_addr); + assert!(result.is_err(), "Reading from invalid address should fail"); + + let result = translator.write_obj(invalid_addr, 42u32); + assert!(result.is_err(), "Writing to invalid address should fail"); +} + +#[test] +#[axin(decorator(mock_hal_test))] +fn test_two_vm_isolation() { + // Create two different translators to simulate two different VMs + let vm1_translator = MockTranslator::new(PhysAddr::from_usize(0), MEMORY_LEN / 2); // Offset for VM1 + let vm2_translator = MockTranslator::new(PhysAddr::from_usize(MEMORY_LEN / 2), MEMORY_LEN); // Offset for VM2 + + // Both VMs write to the same guest address but different host memory regions + let guest_addr = GuestPhysAddr::from_usize(0x100); + let vm1_data: u64 = 0xDEADBEEFCAFEBABE; + let vm2_data: u64 = 0x1234567890ABCDEF; + + // VM1 writes its data + vm1_translator + .write_obj(guest_addr, vm1_data) + .expect("VM1 failed to write data"); + + // VM2 writes its data + vm2_translator + .write_obj(guest_addr, vm2_data) + .expect("VM2 failed to write data"); + + // Both VMs read back their own data - should be isolated + let vm1_read: u64 = vm1_translator + .read_obj(guest_addr) + .expect("VM1 failed to read data"); + let vm2_read: u64 = vm2_translator + .read_obj(guest_addr) + .expect("VM2 failed to read data"); + + // Verify isolation: each VM should read its own data + assert_eq!(vm1_read, vm1_data, "VM1 should read its own data"); + assert_eq!(vm2_read, vm2_data, "VM2 should read its own data"); + assert_ne!( + vm1_read, vm2_read, + "VM1 and VM2 should have different data (isolation)" + ); + + // Test buffer operations with different patterns + let buffer_addr = GuestPhysAddr::from_usize(0x200); + let vm1_buffer = [0xAA; 16]; // Pattern for VM1 + let vm2_buffer = [0x55; 16]; // Pattern for VM2 + + // Both VMs write their patterns + vm1_translator + .write_buffer(buffer_addr, &vm1_buffer) + .expect("VM1 failed to write buffer"); + vm2_translator + .write_buffer(buffer_addr, &vm2_buffer) + .expect("VM2 failed to write buffer"); + + // Read back and verify isolation + let mut vm1_read_buffer = [0u8; 16]; + let mut vm2_read_buffer = [0u8; 16]; + + vm1_translator + .read_buffer(buffer_addr, &mut vm1_read_buffer) + .expect("VM1 failed to read buffer"); + vm2_translator + .read_buffer(buffer_addr, &mut vm2_read_buffer) + .expect("VM2 failed to read buffer"); + + assert_eq!( + vm1_read_buffer, vm1_buffer, + "VM1 should read its own buffer pattern" + ); + assert_eq!( + vm2_read_buffer, vm2_buffer, + "VM2 should read its own buffer pattern" + ); + assert_ne!( + vm1_read_buffer, vm2_read_buffer, + "VM buffers should be isolated" + ); + + // Test that VM1 cannot access VM2's address space (beyond its limit) + let vm2_only_addr = GuestPhysAddr::from_usize(MEMORY_LEN / 2 + 0x100); + let result: AxResult = vm1_translator.read_obj(vm2_only_addr); + assert!( + result.is_err(), + "VM1 should not be able to access VM2's exclusive address space" + ); +} + +#[test] +#[axin(decorator(mock_hal_test))] +fn test_cross_page_access() { + let translator = MockTranslator::new(PhysAddr::from_usize(0), MEMORY_LEN); + + // Test cross-region buffer operations + // Place buffer near a region boundary to test multi-region access + let cross_region_addr = GuestPhysAddr::from_usize(4096 - 8); // 8 bytes before 4K boundary + let test_data = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, + ]; // 16 bytes + + // Write cross-region data + translator + .write_buffer(cross_region_addr, &test_data) + .expect("Failed to write cross-region buffer"); + + // Read cross-region data back + let mut read_data = [0u8; 16]; + translator + .read_buffer(cross_region_addr, &mut read_data) + .expect("Failed to read cross-region buffer"); + + assert_eq!( + read_data, test_data, + "Cross-region read should match written data" + ); + + // Test individual byte access across region boundary + for (i, &expected_byte) in test_data.iter().enumerate() { + let byte_addr = GuestPhysAddr::from_usize(cross_region_addr.as_usize() + i); + let read_byte: u8 = translator + .read_obj(byte_addr) + .expect("Failed to read individual byte"); + assert_eq!( + read_byte, expected_byte, + "Byte at offset {} should match", + i + ); + } +} + +#[test] +#[axin(decorator(mock_hal_test))] +fn test_region_boundary_edge_cases() { + let translator = MockTranslator::new(PhysAddr::from_usize(0), MEMORY_LEN); + + let boundary_addr = GuestPhysAddr::from_usize(4096); + let boundary_data = [0xAB, 0xCD, 0xEF, 0x12]; + + translator + .write_buffer(boundary_addr, &boundary_data) + .expect("Failed to write at boundary"); + + let mut read_boundary = [0u8; 4]; + translator + .read_buffer(boundary_addr, &mut read_boundary) + .expect("Failed to read at boundary"); + + assert_eq!(read_boundary, boundary_data, "Boundary data should match"); + + // Test zero-size buffer (should not fail) + let empty_buffer: &[u8] = &[]; + translator + .write_buffer(boundary_addr, empty_buffer) + .expect("Empty buffer write should succeed"); + + let mut empty_read: &mut [u8] = &mut []; + translator + .read_buffer(boundary_addr, &mut empty_read) + .expect("Empty buffer read should succeed"); + + // Test single byte at boundary (should work fine) + let single_byte = [0x42]; + translator + .write_buffer(boundary_addr, &single_byte) + .expect("Single byte write should succeed"); +} diff --git a/components/axaddrspace/src/test_utils/mod.rs b/components/axaddrspace/tests/test_utils/mod.rs similarity index 85% rename from components/axaddrspace/src/test_utils/mod.rs rename to components/axaddrspace/tests/test_utils/mod.rs index 4d2b7252d..d94b7b8a2 100644 --- a/components/axaddrspace/src/test_utils/mod.rs +++ b/components/axaddrspace/tests/test_utils/mod.rs @@ -14,26 +14,25 @@ use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use axaddrspace::{AxMmHal, HostPhysAddr, HostVirtAddr}; use lazy_static::lazy_static; use memory_addr::{PAGE_SIZE_4K as PAGE_SIZE, PhysAddr, VirtAddr}; use page_table_multiarch::PagingHandler; use spin::Mutex; -use crate::{AxMmHal, HostPhysAddr, HostVirtAddr}; - /// The starting physical address for the simulated memory region in tests. /// This offset is used to map simulated physical addresses to the `MEMORY` array's virtual address space. -pub(crate) const BASE_PADDR: usize = 0x1000; +pub const BASE_PADDR: usize = 0x1000; /// Static variables to simulate global state of a memory allocator in tests. -pub(crate) static NEXT_PADDR: AtomicUsize = AtomicUsize::new(BASE_PADDR); +pub static NEXT_PADDR: AtomicUsize = AtomicUsize::new(BASE_PADDR); /// Total length of the simulated physical memory block for testing, in bytes. -pub(crate) const MEMORY_LEN: usize = 0x10000; // 64KB for testing +pub const MEMORY_LEN: usize = 0x10000; // 64KB for testing // Use #[repr(align(4096))] to ensure 4KB alignment #[repr(align(4096))] -pub(crate) struct AlignedMemory([u8; MEMORY_LEN]); +pub struct AlignedMemory([u8; MEMORY_LEN]); impl Default for AlignedMemory { fn default() -> Self { @@ -43,21 +42,21 @@ impl Default for AlignedMemory { lazy_static! { /// Simulates the actual physical memory block used for allocation. - pub(crate) static ref MEMORY: Mutex = Mutex::new(AlignedMemory::default()); + pub static ref MEMORY: Mutex = Mutex::new(AlignedMemory::default()); /// Global mutex to enforce serial execution for tests that modify shared state. /// This ensures test isolation and prevents race conditions between tests. - pub(crate) static ref TEST_MUTEX: Mutex<()> = Mutex::new(()); + pub static ref TEST_MUTEX: Mutex<()> = Mutex::new(()); } /// Counter to track the number of allocations. (Added from Chen Hong's code) -pub(crate) static ALLOC_COUNT: AtomicUsize = AtomicUsize::new(0); +pub static ALLOC_COUNT: AtomicUsize = AtomicUsize::new(0); /// Counter to track the number of deallocations. -pub(crate) static DEALLOC_COUNT: AtomicUsize = AtomicUsize::new(0); +pub static DEALLOC_COUNT: AtomicUsize = AtomicUsize::new(0); /// Flag to simulate memory allocation failures for testing error handling. -pub(crate) static ALLOC_SHOULD_FAIL: AtomicBool = AtomicBool::new(false); +pub static ALLOC_SHOULD_FAIL: AtomicBool = AtomicBool::new(false); #[derive(Debug)] /// A mock implementation of AxMmHal for testing purposes. @@ -65,7 +64,7 @@ pub(crate) static ALLOC_SHOULD_FAIL: AtomicBool = AtomicBool::new(false); /// /// The `Debug` trait is derived because `assert_matches!` on `Result, _>` /// requires `PhysFrame` (the `T` type) to implement `Debug` for diagnostic output on assertion failure. -pub(crate) struct MockHal {} +pub struct MockHal {} impl AxMmHal for MockHal { fn alloc_frame() -> Option { @@ -121,7 +120,7 @@ impl PagingHandler for MockHal { } /// A utility decorator for test functions that require the MockHal state to be reset before execution. -pub(crate) fn mock_hal_test(test_fn: F) -> R +pub fn mock_hal_test(test_fn: F) -> R where F: FnOnce() -> R, { @@ -131,7 +130,7 @@ where } /// A utility function to verify the number of deallocations performed by the MockHal. -pub(crate) fn test_dealloc_count(expected: usize) { +pub fn test_dealloc_count(expected: usize) { let actual_dealloc_count = DEALLOC_COUNT.load(Ordering::SeqCst); assert_eq!( actual_dealloc_count, expected, @@ -141,7 +140,7 @@ pub(crate) fn test_dealloc_count(expected: usize) { impl MockHal { /// Simulates the allocation of a single physical frame. - pub(crate) fn mock_alloc_frame() -> Option { + pub fn mock_alloc_frame() -> Option { // Use a static mutable variable to control alloc_should_fail state if ALLOC_SHOULD_FAIL.load(Ordering::SeqCst) { return None; @@ -156,14 +155,14 @@ impl MockHal { } /// Simulates the deallocation of a single physical frame. - pub(crate) fn mock_dealloc_frame(_paddr: PhysAddr) { + pub fn mock_dealloc_frame(_paddr: PhysAddr) { DEALLOC_COUNT.fetch_add(1, Ordering::SeqCst); } /// In this test mock, the "virtual address" is simply a direct pointer /// to the corresponding location within the `MEMORY` array. /// It simulates a physical-to-virtual memory mapping for test purposes. - pub(crate) fn mock_phys_to_virt(paddr: PhysAddr) -> VirtAddr { + pub fn mock_phys_to_virt(paddr: PhysAddr) -> VirtAddr { let paddr_usize = paddr.as_usize(); assert!( paddr_usize >= BASE_PADDR && paddr_usize < BASE_PADDR + MEMORY_LEN, @@ -175,7 +174,7 @@ impl MockHal { } /// Maps a virtual address (within the test process) back to a simulated physical address. - pub(crate) fn mock_virt_to_phys(vaddr: VirtAddr) -> PhysAddr { + pub fn mock_virt_to_phys(vaddr: VirtAddr) -> PhysAddr { let base_virt = MEMORY.lock().0.as_ptr() as usize; let vaddr_usize = vaddr.as_usize(); assert!( @@ -188,13 +187,13 @@ impl MockHal { } /// Helper function to control the simulated allocation failure. - pub(crate) fn set_alloc_fail(fail: bool) { + pub fn set_alloc_fail(fail: bool) { ALLOC_SHOULD_FAIL.store(fail, Ordering::SeqCst); } /// Resets all static state of the MockHal to its initial, clean state. /// This is crucial for ensuring test isolation between individual test functions. - pub(crate) fn reset_state() { + pub fn reset_state() { NEXT_PADDR.store(BASE_PADDR, Ordering::SeqCst); ALLOC_SHOULD_FAIL.store(false, Ordering::SeqCst); ALLOC_COUNT.store(0, Ordering::SeqCst); diff --git a/components/axdevice/.github/config.json b/components/axdevice/.github/config.json index f347e101e..de46bed95 100644 --- a/components/axdevice/.github/config.json +++ b/components/axdevice/.github/config.json @@ -1,10 +1,17 @@ { + "component": { + "name": "axdevice", + "crate_name": "axdevice" + }, "targets": [ "aarch64-unknown-none-softfloat", "x86_64-unknown-linux-gnu", "x86_64-unknown-none", "riscv64gc-unknown-none-elf" ], + "unit_test_targets": [ + "x86_64-unknown-linux-gnu" + ], "rust_components": [ "rust-src", "clippy", diff --git a/components/axdevice/.github/workflows/check.yml b/components/axdevice/.github/workflows/check.yml index 330fa15e9..018bbbeb2 100644 --- a/components/axdevice/.github/workflows/check.yml +++ b/components/axdevice/.github/workflows/check.yml @@ -1,66 +1,15 @@ -name: Quality Checks +# Quality Check Workflow +# References shared workflow from axci + +name: Check on: push: - branches: - - '**' - tags-ignore: - - '**' + branches: ['**'] + tags-ignore: ['**'] pull_request: - workflow_call: + workflow_dispatch: jobs: - load-config: - name: Load CI Configuration - runs-on: ubuntu-latest - outputs: - targets: ${{ steps.config.outputs.targets }} - rust_components: ${{ steps.config.outputs.rust_components }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Load configuration - id: config - run: | - TARGETS=$(jq -c '.targets' .github/config.json) - COMPONENTS=$(jq -r '.rust_components | join(", ")' .github/config.json) - - echo "targets=$TARGETS" >> $GITHUB_OUTPUT - echo "rust_components=$COMPONENTS" >> $GITHUB_OUTPUT - check: - name: Check - runs-on: ubuntu-latest - needs: load-config - strategy: - fail-fast: false - matrix: - target: ${{ fromJson(needs.load-config.outputs.targets) }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - with: - components: ${{ needs.load-config.outputs.rust_components }} - targets: ${{ matrix.target }} - - - name: Check rust version - run: rustc --version --verbose - - - name: Check code format - run: cargo fmt --all -- --check - - - name: Build - run: cargo build --target ${{ matrix.target }} --all-features - - - name: Run clippy - run: cargo clippy --target ${{ matrix.target }} --all-features -- -D warnings - - - name: Build documentation - env: - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - run: cargo doc --no-deps --target ${{ matrix.target }} --all-features + uses: arceos-hypervisor/axci/.github/workflows/check.yml@main diff --git a/components/axdevice/.github/workflows/deploy.yml b/components/axdevice/.github/workflows/deploy.yml index fda9a323d..37b00c09c 100644 --- a/components/axdevice/.github/workflows/deploy.yml +++ b/components/axdevice/.github/workflows/deploy.yml @@ -1,3 +1,6 @@ +# Deploy Workflow +# References shared workflow from axci + name: Deploy on: @@ -5,122 +8,9 @@ on: tags: - 'v[0-9]+.[0-9]+.[0-9]+' -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: 'pages' - cancel-in-progress: false - -env: - CARGO_TERM_COLOR: always - RUST_BACKTRACE: 1 - jobs: - verify-tag: - name: Verify Tag - runs-on: ubuntu-latest - outputs: - should_deploy: ${{ steps.check.outputs.should_deploy }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check if tag is on main or master branch - id: check - run: | - git fetch origin main master || true - BRANCHES=$(git branch -r --contains ${{ github.ref }}) - - if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then - echo "✓ Tag is on main or master branch" - echo "should_deploy=true" >> $GITHUB_OUTPUT - else - echo "✗ Tag is not on main or master branch, skipping deployment" - echo "Tag is on: $BRANCHES" - echo "should_deploy=false" >> $GITHUB_OUTPUT - fi - - - name: Verify version consistency - if: steps.check.outputs.should_deploy == 'true' - run: | - # Extract version from git tag (remove 'v' prefix) - TAG_VERSION="${{ github.ref_name }}" - TAG_VERSION="${TAG_VERSION#v}" - # Extract version from Cargo.toml - CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') - echo "Git tag version: $TAG_VERSION" - echo "Cargo.toml version: $CARGO_VERSION" - if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then - echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" - exit 1 - fi - echo "✓ Version check passed!" - - check: - uses: ./.github/workflows/check.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_deploy == 'true' - - test: - uses: ./.github/workflows/test.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_deploy == 'true' - - build: - name: Build documentation - runs-on: ubuntu-latest - needs: [verify-tag, check, test] - if: needs.verify-tag.outputs.should_deploy == 'true' - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Build docs - env: - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - run: | - # Build documentation - cargo doc --no-deps --all-features - - # Auto-detect documentation directory - # Check if doc exists in target/doc or target/*/doc - if [ -d "target/doc" ]; then - DOC_DIR="target/doc" - else - # Find doc directory under target/*/doc pattern - DOC_DIR=$(find target -type d -name doc -path "target/*/doc" | head -n 1) - if [ -z "$DOC_DIR" ]; then - echo "Error: Could not find documentation directory" - exit 1 - fi - fi - - echo "Documentation found in: $DOC_DIR" - printf '' $(cargo tree | head -1 | cut -d' ' -f1) > "${DOC_DIR}/index.html" - echo "DOC_DIR=${DOC_DIR}" >> $GITHUB_ENV - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: ${{ env.DOC_DIR }} - deploy: - name: Deploy to GitHub Pages - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: [verify-tag, build] - if: needs.verify-tag.outputs.should_deploy == 'true' - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 + uses: arceos-hypervisor/axci/.github/workflows/deploy.yml@main + with: + verify_branch: true + verify_version: true diff --git a/components/axdevice/.github/workflows/push.yml b/components/axdevice/.github/workflows/push.yml new file mode 100644 index 000000000..3ff391a4a --- /dev/null +++ b/components/axdevice/.github/workflows/push.yml @@ -0,0 +1,16 @@ +name: Notify Parent Repository + +on: + push: + branches: + - main + - zcs + workflow_dispatch: + +jobs: + notify-parent: + name: Notify Parent Repository + # 调用 axci 仓库的可复用工作流 + uses: arceos-hypervisor/axci/.github/workflows/push.yml@main + secrets: + PARENT_REPO_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} diff --git a/components/axdevice/.github/workflows/release.yml b/components/axdevice/.github/workflows/release.yml index 2e857b48b..20f186395 100644 --- a/components/axdevice/.github/workflows/release.yml +++ b/components/axdevice/.github/workflows/release.yml @@ -1,3 +1,7 @@ +# Release Workflow +# References shared workflow from axci +# check + test must pass before release + name: Release on: @@ -6,155 +10,18 @@ on: - 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+-pre.[0-9]+' -permissions: - contents: write - jobs: - verify-tag: - name: Verify Tag - runs-on: ubuntu-latest - outputs: - should_release: ${{ steps.check.outputs.should_release }} - is_prerelease: ${{ steps.check.outputs.is_prerelease }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check tag type and branch - id: check - run: | - git fetch origin main master dev || true - - TAG="${{ github.ref_name }}" - BRANCHES=$(git branch -r --contains ${{ github.ref }}) - - echo "Tag: $TAG" - echo "Branches containing this tag: $BRANCHES" - - # Check if it's a prerelease tag - if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-pre\.[0-9]+$ ]]; then - echo "📦 Detected prerelease tag" - echo "is_prerelease=true" >> $GITHUB_OUTPUT - - if echo "$BRANCHES" | grep -q 'origin/dev'; then - echo "✓ Prerelease tag is on dev branch" - echo "should_release=true" >> $GITHUB_OUTPUT - else - echo "✗ Prerelease tag must be on dev branch, skipping release" - echo "should_release=false" >> $GITHUB_OUTPUT - fi - elif [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "📦 Detected stable release tag" - echo "is_prerelease=false" >> $GITHUB_OUTPUT - - if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then - echo "✓ Stable release tag is on main or master branch" - echo "should_release=true" >> $GITHUB_OUTPUT - else - echo "✗ Stable release tag must be on main or master branch, skipping release" - echo "should_release=false" >> $GITHUB_OUTPUT - fi - else - echo "✗ Unknown tag format, skipping release" - echo "is_prerelease=false" >> $GITHUB_OUTPUT - echo "should_release=false" >> $GITHUB_OUTPUT - fi - - - name: Verify version consistency - if: steps.check.outputs.should_release == 'true' - run: | - # Extract version from git tag (remove 'v' prefix) - TAG_VERSION="${{ github.ref_name }}" - TAG_VERSION="${TAG_VERSION#v}" - # Extract version from Cargo.toml - CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') - echo "Git tag version: $TAG_VERSION" - echo "Cargo.toml version: $CARGO_VERSION" - if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then - echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" - exit 1 - fi - echo "✓ Version check passed!" - check: - uses: ./.github/workflows/check.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_release == 'true' + uses: arceos-hypervisor/axci/.github/workflows/check.yml@main test: - uses: ./.github/workflows/test.yml - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' + uses: arceos-hypervisor/axci/.github/workflows/test.yml@main release: - name: Create GitHub Release - runs-on: ubuntu-latest - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Generate release notes - id: release_notes - run: | - CURRENT_TAG="${{ github.ref_name }}" - - # Get previous tag - PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -A1 "^${CURRENT_TAG}$" | tail -n1) - - if [ -z "$PREVIOUS_TAG" ] || [ "$PREVIOUS_TAG" == "$CURRENT_TAG" ]; then - echo "No previous tag found, this is the first release" - CHANGELOG="Initial release" - else - echo "Generating changelog from $PREVIOUS_TAG to $CURRENT_TAG" - - # Generate changelog with commit messages - CHANGELOG=$(git log --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${CURRENT_TAG}") - - if [ -z "$CHANGELOG" ]; then - CHANGELOG="No changes" - fi - fi - - # Write changelog to output file (multi-line) - { - echo "changelog<> $GITHUB_OUTPUT - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - draft: false - prerelease: ${{ needs.verify-tag.outputs.is_prerelease == 'true' }} - body: | - ## Changes - ${{ steps.release_notes.outputs.changelog }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - publish: - name: Publish to crates.io - runs-on: ubuntu-latest - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Dry run publish - run: cargo publish --dry-run - - - name: Publish to crates.io - run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + needs: [check, test] + uses: arceos-hypervisor/axci/.github/workflows/release.yml@main + with: + verify_branch: true + verify_version: true + secrets: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/components/axdevice/.github/workflows/test.yml b/components/axdevice/.github/workflows/test.yml index dc3b293d9..6a58ef8e5 100644 --- a/components/axdevice/.github/workflows/test.yml +++ b/components/axdevice/.github/workflows/test.yml @@ -1,3 +1,6 @@ +# Integration Test Workflow +# References shared workflow from axci + name: Test on: @@ -7,44 +10,8 @@ on: tags-ignore: - '**' pull_request: - workflow_call: + workflow_dispatch: jobs: - load-config: - name: Load CI Configuration - runs-on: ubuntu-latest - outputs: - targets: ${{ steps.config.outputs.targets }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Load configuration - id: config - run: | - TARGETS=$(jq -c '.targets' .github/config.json) - echo "targets=$TARGETS" >> $GITHUB_OUTPUT - test: - name: Test - runs-on: ubuntu-latest - needs: load-config - strategy: - fail-fast: false - matrix: - target: ${{ fromJson(needs.load-config.outputs.targets) }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - # - name: Run tests - # run: cargo test --target ${{ matrix.target }} --all-features -- --nocapture - - # - name: Run doc tests - # run: cargo test --target ${{ matrix.target }} --doc - - name: Run tests - run: echo "Tests are skipped!" + uses: arceos-hypervisor/axci/.github/workflows/test.yml@main diff --git a/components/axdevice/.gitignore b/components/axdevice/.gitignore index 55c0d4550..39ee77997 100644 --- a/components/axdevice/.gitignore +++ b/components/axdevice/.gitignore @@ -16,3 +16,11 @@ rusty-tags.vi # We ignore Cargo.lock because `axvcpu` is just a library Cargo.lock + +# Test results (generated by shared test framework) +/test-results/ +/test_repos/ +*.log + +# Downloaded test framework +/scripts/.axci/ diff --git a/components/axdevice/Cargo.toml b/components/axdevice/Cargo.toml index 7d35cec59..998e85c93 100644 --- a/components/axdevice/Cargo.toml +++ b/components/axdevice/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "axdevice" -version = "0.2.1" +version = "0.2.2" description = "A reusable, OS-agnostic device abstraction layer designed for virtual machines." homepage = "https://github.com/arceos-hypervisor/axdevice" repository = "https://github.com/arceos-hypervisor/axdevice" keywords = ["hypervisor"] license = "Apache-2.0" -authors = ["aarkegz "] +authors = ["Su Mingxian "] documentation = "https://docs.rs/axdevice" categories = ["virtualization"] edition = "2024" @@ -17,26 +17,19 @@ readme = "README.md" [dependencies] log = "0.4" cfg-if = "1.0" -spin = "0.9" +spin = "0.10" # System independent crates provided by ArceOS. -axerrno = "0.1.0" +axerrno = "0.2" memory_addr = "0.4" -axvmconfig = { version = "0.2", default-features = false } -axaddrspace = "0.1.4" -axdevice_base = "0.2.1" -range-alloc-arceos = "0.1.4-pre.1" +axvmconfig = { version = "0.2.2", default-features = false } +axaddrspace = "0.3" +axdevice_base = "0.2.2" +range-alloc-arceos = "0.1.4" [target.'cfg(target_arch = "aarch64")'.dependencies] -arm_vgic = { version = "0.2.1", features = ["vgicv3"] } +arm_vgic = { version = "0.2.2", features = ["vgicv3"] } [target.'cfg(target_arch = "riscv64")'.dependencies] -riscv_vplic = "0.2.1" - -[dev-dependencies] -axdevice_base = "0.2.1" -axaddrspace = "0.1.4" -memory_addr = "0.4" -axerrno = "0.1" -axvmconfig = "0.2" \ No newline at end of file +riscv_vplic = "0.2.2" diff --git a/components/axdevice/README.md b/components/axdevice/README.md index 12ebcf797..6a3ef6375 100644 --- a/components/axdevice/README.md +++ b/components/axdevice/README.md @@ -1,78 +1,154 @@ -# axdevice - -**axdevice** is a reusable, OS-agnostic device abstraction layer designed for virtual machines. It allows dynamic device configuration and MMIO emulation in `no_std` environments, making it suitable for hypervisors or operating systems targeting RISC-V or AArch64. - -## ✨ Highlights - -- 📦 **Componentized**: Designed as a modular crate to be integrated into any OS or hypervisor. -- 🧩 **Flexible device abstraction**: Supports dynamic device registration and MMIO handling. -- 🛠️ **No `std` required**: Uses `alloc` and `core` only, suitable for bare-metal development. -- 🧵 **Thread-safe**: Devices are stored using `Arc`, ready for multicore use. -- 🧱 **Easily extensible**: Just plug in device types via `axdevice_base::BaseDeviceOps`. - -## 📦 Structure - -- `config.rs`: Defines `AxVmDeviceConfig`, a wrapper for device configuration input. -- `device.rs`: Defines `AxVmDevices`, manages and dispatches MMIO to registered devices. - -## 📐 Dependency Graph - -```text - +-------------------+ - | axvmconfig | <- defines EmulatedDeviceConfig - +-------------------+ - | - v -+------------------+ uses +-----------------------+ -| axdevice +-------------->+ axdevice_base::trait | -| (this crate) | +-----------------------+ -+------------------+ ^ - | | - v | -+------------------+ | -| axaddrspace | -- GuestPhysAddr ----+ -+------------------+ +

axdevice

+ +

OS-Agnostic Virtual Device Abstraction Layer

+ +
+ +[![Crates.io](https://img.shields.io/crates/v/axdevice.svg)](https://crates.io/crates/axdevice) +[![Docs.rs](https://docs.rs/axdevice/badge.svg)](https://docs.rs/axdevice) +[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/arceos-hypervisor/axdevice/blob/main/LICENSE) + +
+ +English | [中文](README_CN.md) + +# Introduction + +`axdevice` is a reusable, OS-agnostic device abstraction layer for virtual machines. It provides unified management for emulated devices and dispatches guest accesses to MMIO, system-register, and port-based devices in `#![no_std]` environments. + +This crate currently exports two core types: + +- **`AxVmDeviceConfig`** - Wraps a list of `EmulatedDeviceConfig` items used to initialize VM devices +- **`AxVmDevices`** - Manages device collections, dispatches device access requests, and allocates IVC channels + +The crate is suitable for hypervisors and low-level OS components targeting AArch64 or RISC-V. + +## Quick Start + +### Requirements + +- Rust nightly toolchain +- Rust components: rust-src, clippy, rustfmt + +```bash +# Install rustup (if not installed) +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# Install nightly toolchain and components +rustup install nightly +rustup component add rust-src clippy rustfmt --toolchain nightly ``` -## 🔁 Usage Flow - -```text -[1] Load VM device config (Vec) - ↓ -[2] Create AxVmDeviceConfig - ↓ -[3] Pass into AxVmDevices::new() - ↓ -[4] MMIO access triggers handle_mmio_{read,write} - ↓ -[5] Device selected by GuestPhysAddr - ↓ -[6] Forwarded to BaseDeviceOps::handle_{read,write}() +### Run Check and Test + +```bash +# 1. Enter the repository +cd axdevice + +# 2. Code check (format + clippy + build) +./scripts/check.sh + +# 3. Run tests +./scripts/test.sh ``` -## 🚀 Example +## Integration + +### Installation + +Add to your `Cargo.toml`: + +```toml +[dependencies] +axdevice = "0.2.2" +``` + +### Example ```rust +use std::sync::{Arc, Mutex}; + +use axaddrspace::device::AccessWidth; +use axaddrspace::{GuestPhysAddr, GuestPhysAddrRange}; use axdevice::{AxVmDeviceConfig, AxVmDevices}; +use axdevice_base::BaseDeviceOps; +use axerrno::AxResult; +use axvmconfig::EmulatedDeviceType; + +struct MockMmioDevice { + range: GuestPhysAddrRange, + last_write: Mutex>, +} + +impl MockMmioDevice { + fn new(base: usize, size: usize) -> Self { + Self { + range: GuestPhysAddrRange::new( + GuestPhysAddr::from(base), + GuestPhysAddr::from(base + size), + ), + last_write: Mutex::new(None), + } + } +} + +impl BaseDeviceOps for MockMmioDevice { + fn address_range(&self) -> GuestPhysAddrRange { + self.range + } + + fn emu_type(&self) -> EmulatedDeviceType { + EmulatedDeviceType::IVCChannel + } + + fn handle_read(&self, _addr: GuestPhysAddr, _width: AccessWidth) -> AxResult { + Ok(0xDEAD_BEEF) + } + + fn handle_write(&self, addr: GuestPhysAddr, _width: AccessWidth, val: usize) -> AxResult { + let offset = addr.as_usize() - self.range.start.as_usize(); + assert_eq!(offset, 0x40); + *self.last_write.lock().unwrap() = Some(val); + Ok(()) + } +} + +fn main() { + let config = AxVmDeviceConfig::new(vec![]); + let mut devices = AxVmDevices::new(config); + + let mock = Arc::new(MockMmioDevice::new(0x1000_0000, 0x1000)); + devices.add_mmio_dev(mock.clone()); + + let width = AccessWidth::try_from(4).unwrap(); + let addr = GuestPhysAddr::from(0x1000_0040); + + devices.handle_mmio_write(addr, width, 0x1234_5678).unwrap(); + let value = devices.handle_mmio_read(addr, width).unwrap(); + + assert_eq!(value, 0xDEAD_BEEF); + assert_eq!(*mock.last_write.lock().unwrap(), Some(0x1234_5678)); +} +``` -// Step 1: Load configuration (e.g. from .toml or hypervisor setup) -let config = AxVmDeviceConfig::new(vec![/* EmulatedDeviceConfig */]); +### Documentation -// Step 2: Initialize devices -let devices = AxVmDevices::new(config); +Generate and view API documentation: -// Step 3: Emulate MMIO access -let _ = devices.handle_mmio_read(0x1000_0000, 4); -devices.handle_mmio_write(0x1000_0000, 4, 0xdead_beef); +```bash +cargo doc --no-deps --open ``` -## 📦 Dependencies +Online documentation: [docs.rs/axdevice](https://docs.rs/axdevice) + +# Contributing -- [`axvmconfig`](https://github.com/arceos-hypervisor/axvmconfig.git) -- [`axaddrspace`](https://github.com/arceos-hypervisor/axaddrspace.git) -- [`axdevice_base`](https://github.com/arceos-hypervisor/axdevice_crates.git) -- `log`, `alloc`, `cfg-if`, `axerrno` +1. Fork the repository and create a branch +2. Run local check: `./scripts/check.sh` +3. Run local tests: `./scripts/test.sh` +4. Submit PR and pass CI checks -## License +# License -Axdevice is licensed under the Apache License, Version 2.0. See the [LICENSE](./LICENSE) file for details. +Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details. diff --git a/components/axdevice/README_CN.md b/components/axdevice/README_CN.md index df25881a1..9b804b1af 100644 --- a/components/axdevice/README_CN.md +++ b/components/axdevice/README_CN.md @@ -1,81 +1,154 @@ -# axdevice - -**axdevice** 是一个可复用、与操作系统无关的设备抽象层,专为虚拟机设计,支持在 `no_std` 环境中进行设备配置与 MMIO 模拟。适用于开发 hypervisor 或嵌入式操作系统。 - -## ✨ 特性亮点 - -- 📦 **模块化设计**:适用于任意操作系统或虚拟化平台的组件库。 -- 🧩 **灵活设备抽象**:通过配置动态加载和注册设备。 -- 🛠️ **无标准库依赖**:适配裸机、EL2 等场景,仅依赖 `core` 与 `alloc`。 -- 🧵 **线程安全**:所有设备均用 `Arc` 管理,支持多核并发。 -- 🧱 **便于扩展**:接入自定义设备只需实现 `BaseDeviceOps` trait。 - -## 📦 模块结构 - -- `config.rs`: 定义 `AxVmDeviceConfig`,用于初始化设备配置。 -- `device.rs`: 定义 `AxVmDevices`,管理设备并处理 MMIO 读写。 - -## 📐 依赖图 - -```text - +-------------------+ - | axvmconfig | <- 提供 EmulatedDeviceConfig - +-------------------+ - | - v -+------------------+ uses +-----------------------+ -| axdevice +-------------->+ axdevice_base::trait | -| (当前模块) | +-----------------------+ -+------------------+ ^ - | | - v | -+------------------+ | -| axaddrspace | -- GuestPhysAddr ----+ -+------------------+ +

axdevice

+ +

面向虚拟机的操作系统无关设备抽象层

+ +
+ +[![Crates.io](https://img.shields.io/crates/v/axdevice.svg)](https://crates.io/crates/axdevice) +[![Docs.rs](https://docs.rs/axdevice/badge.svg)](https://docs.rs/axdevice) +[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/arceos-hypervisor/axdevice/blob/main/LICENSE) + +
+ +[English](README.md) | 中文 + +# Introduction + +`axdevice` 是一个可复用、与操作系统无关的虚拟机设备抽象层。它在 `#![no_std]` 环境中为模拟设备提供统一管理能力,并将访存请求分发到 MMIO、系统寄存器和端口类设备。 + +该 crate 当前导出两个核心类型: + +- **`AxVmDeviceConfig`** - 封装用于初始化虚拟机设备的 `EmulatedDeviceConfig` 列表 +- **`AxVmDevices`** - 管理设备集合、分发设备访问请求,并提供 IVC 通道分配能力 + +该 crate 适用于面向 AArch64 或 RISC-V 的 hypervisor 及底层操作系统组件。 + +## Quick Start + +### Requirements + +- Rust nightly 工具链 +- Rust 组件:rust-src、clippy、rustfmt + +```bash +# 安装 rustup(如果尚未安装) +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# 安装 nightly 工具链与所需组件 +rustup install nightly +rustup component add rust-src clippy rustfmt --toolchain nightly ``` -## 🔁 使用流程 - -```text -[1] 加载设备配置 Vec - ↓ -[2] 构造 AxVmDeviceConfig - ↓ -[3] AxVmDevices::new() 初始化所有设备 - ↓ -[4] guest发起 MMIO 访问 - ↓ -[5] 匹配设备地址范围 - ↓ -[6] 调用设备 trait 接口 handle_read / handle_write +### Run Check and Test + +```bash +# 1. 进入仓库目录 +cd axdevice + +# 2. 代码检查(格式化 + clippy + 构建) +./scripts/check.sh + +# 3. 运行测试 +./scripts/test.sh ``` -## 🚀 示例代码 +## Integration + +### Installation + +将以下依赖加入 `Cargo.toml`: + +```toml +[dependencies] +axdevice = "0.2.2" +``` + +### Example ```rust +use std::sync::{Arc, Mutex}; + +use axaddrspace::device::AccessWidth; +use axaddrspace::{GuestPhysAddr, GuestPhysAddrRange}; use axdevice::{AxVmDeviceConfig, AxVmDevices}; +use axdevice_base::BaseDeviceOps; +use axerrno::AxResult; +use axvmconfig::EmulatedDeviceType; + +struct MockMmioDevice { + range: GuestPhysAddrRange, + last_write: Mutex>, +} + +impl MockMmioDevice { + fn new(base: usize, size: usize) -> Self { + Self { + range: GuestPhysAddrRange::new( + GuestPhysAddr::from(base), + GuestPhysAddr::from(base + size), + ), + last_write: Mutex::new(None), + } + } +} + +impl BaseDeviceOps for MockMmioDevice { + fn address_range(&self) -> GuestPhysAddrRange { + self.range + } + + fn emu_type(&self) -> EmulatedDeviceType { + EmulatedDeviceType::IVCChannel + } + + fn handle_read(&self, _addr: GuestPhysAddr, _width: AccessWidth) -> AxResult { + Ok(0xDEAD_BEEF) + } + + fn handle_write(&self, addr: GuestPhysAddr, _width: AccessWidth, val: usize) -> AxResult { + let offset = addr.as_usize() - self.range.start.as_usize(); + assert_eq!(offset, 0x40); + *self.last_write.lock().unwrap() = Some(val); + Ok(()) + } +} + +fn main() { + let config = AxVmDeviceConfig::new(vec![]); + let mut devices = AxVmDevices::new(config); + + let mock = Arc::new(MockMmioDevice::new(0x1000_0000, 0x1000)); + devices.add_mmio_dev(mock.clone()); + + let width = AccessWidth::try_from(4).unwrap(); + let addr = GuestPhysAddr::from(0x1000_0040); + + devices.handle_mmio_write(addr, width, 0x1234_5678).unwrap(); + let value = devices.handle_mmio_read(addr, width).unwrap(); + + assert_eq!(value, 0xDEAD_BEEF); + assert_eq!(*mock.last_write.lock().unwrap(), Some(0x1234_5678)); +} +``` -let config = AxVmDeviceConfig::new(vec![/* EmulatedDeviceConfig */]); +### Documentation -let devices = AxVmDevices::new(config); +生成并查看 API 文档: -let _ = devices.handle_mmio_read(0x1000_0000, 4); -devices.handle_mmio_write(0x1000_0000, 4, 0xdead_beef); +```bash +cargo doc --no-deps --open ``` -## 🔧 依赖组件 - -- [`axvmconfig`](https://github.com/arceos-hypervisor/axvmconfig.git) -- [`axaddrspace`](https://github.com/arceos-hypervisor/axaddrspace.git) -- [`axdevice_base`](https://github.com/arceos-hypervisor/axdevice_crates.git) +在线文档: [docs.rs/axdevice](https://docs.rs/axdevice) -其他依赖: +# Contributing -- `log` -- `alloc` -- `cfg-if` -- `axerrno` +1. Fork 仓库并创建分支 +2. 本地运行检查:`./scripts/check.sh` +3. 本地运行测试:`./scripts/test.sh` +4. 提交 PR 并通过 CI 检查 -## License +# License -Axdevice 采用 Apache License 2.0 开源协议。详见 [LICENSE](./LICENSE) 文件。 +本项目基于 Apache License 2.0 许可证发布。详见 [LICENSE](LICENSE)。 diff --git a/components/axdevice/rust-toolchain.toml b/components/axdevice/rust-toolchain.toml new file mode 100644 index 000000000..d71709d74 --- /dev/null +++ b/components/axdevice/rust-toolchain.toml @@ -0,0 +1,10 @@ +[toolchain] +channel = "nightly-2026-02-25" +components = ["rust-src", "llvm-tools", "rustfmt", "clippy"] +profile = "minimal" +targets = [ + "aarch64-unknown-none-softfloat", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-none", + "riscv64gc-unknown-none-elf" +] diff --git a/components/axdevice/scripts/check.sh b/components/axdevice/scripts/check.sh new file mode 100755 index 000000000..2b4c57962 --- /dev/null +++ b/components/axdevice/scripts/check.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# +# axdevice 代码检查脚本 +# 下载并调用 axci 仓库中的检查脚本 +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +COMPONENT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +COMPONENT_NAME="$(basename "$COMPONENT_DIR")" +AXCI_DIR="${SCRIPT_DIR}/.axci" +AXCI_REPO="https://github.com/arceos-hypervisor/axci.git" + +# 下载或更新 axci 仓库 +download_axci() { + if [ -d "$AXCI_DIR" ]; then + echo "Updating axci repository..." + cd "$AXCI_DIR" && git pull --quiet + else + echo "Downloading axci repository..." + git clone --quiet "$AXCI_REPO" "$AXCI_DIR" + fi +} + +# 主函数 +main() { + download_axci + + # 在组件目录中运行检查 + cd "$COMPONENT_DIR" + exec bash "$AXCI_DIR/check.sh" --component-dir "$COMPONENT_DIR" "$@" +} + +main "$@" diff --git a/components/axdevice/scripts/test.sh b/components/axdevice/scripts/test.sh new file mode 100755 index 000000000..824f06a48 --- /dev/null +++ b/components/axdevice/scripts/test.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# +# axdevice 测试脚本 +# 下载并调用 axci 仓库中的测试框架 +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +COMPONENT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +AXCI_DIR="${SCRIPT_DIR}/.axci" +AXCI_REPO="https://github.com/arceos-hypervisor/axci.git" + +# 下载或更新 axci 仓库 +download_axci() { + if [ -d "$AXCI_DIR" ]; then + echo "Updating axci repository..." + cd "$AXCI_DIR" && git pull --quiet + else + echo "Downloading axci repository..." + git clone --quiet "$AXCI_REPO" "$AXCI_DIR" + fi +} + +# 主函数 +main() { + download_axci + + # 在组件目录中运行测试,自动指定当前组件 + cd "$COMPONENT_DIR" + exec bash "$AXCI_DIR/tests.sh" --component-dir "$COMPONENT_DIR" "$@" +} + +main "$@" diff --git a/components/axdevice_base/.github/config.json b/components/axdevice_base/.github/config.json index f347e101e..ee774f698 100644 --- a/components/axdevice_base/.github/config.json +++ b/components/axdevice_base/.github/config.json @@ -1,10 +1,16 @@ { + "component": { + "name": "axdevice_base", + "crate_name": "axdevice_base" + }, "targets": [ "aarch64-unknown-none-softfloat", - "x86_64-unknown-linux-gnu", "x86_64-unknown-none", "riscv64gc-unknown-none-elf" ], + "unit_test_targets": [ + "x86_64-unknown-linux-gnu" + ], "rust_components": [ "rust-src", "clippy", diff --git a/components/axdevice_base/.github/workflows/check.yml b/components/axdevice_base/.github/workflows/check.yml index 330fa15e9..018bbbeb2 100644 --- a/components/axdevice_base/.github/workflows/check.yml +++ b/components/axdevice_base/.github/workflows/check.yml @@ -1,66 +1,15 @@ -name: Quality Checks +# Quality Check Workflow +# References shared workflow from axci + +name: Check on: push: - branches: - - '**' - tags-ignore: - - '**' + branches: ['**'] + tags-ignore: ['**'] pull_request: - workflow_call: + workflow_dispatch: jobs: - load-config: - name: Load CI Configuration - runs-on: ubuntu-latest - outputs: - targets: ${{ steps.config.outputs.targets }} - rust_components: ${{ steps.config.outputs.rust_components }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Load configuration - id: config - run: | - TARGETS=$(jq -c '.targets' .github/config.json) - COMPONENTS=$(jq -r '.rust_components | join(", ")' .github/config.json) - - echo "targets=$TARGETS" >> $GITHUB_OUTPUT - echo "rust_components=$COMPONENTS" >> $GITHUB_OUTPUT - check: - name: Check - runs-on: ubuntu-latest - needs: load-config - strategy: - fail-fast: false - matrix: - target: ${{ fromJson(needs.load-config.outputs.targets) }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - with: - components: ${{ needs.load-config.outputs.rust_components }} - targets: ${{ matrix.target }} - - - name: Check rust version - run: rustc --version --verbose - - - name: Check code format - run: cargo fmt --all -- --check - - - name: Build - run: cargo build --target ${{ matrix.target }} --all-features - - - name: Run clippy - run: cargo clippy --target ${{ matrix.target }} --all-features -- -D warnings - - - name: Build documentation - env: - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - run: cargo doc --no-deps --target ${{ matrix.target }} --all-features + uses: arceos-hypervisor/axci/.github/workflows/check.yml@main diff --git a/components/axdevice_base/.github/workflows/deploy.yml b/components/axdevice_base/.github/workflows/deploy.yml index 882ba9310..37b00c09c 100644 --- a/components/axdevice_base/.github/workflows/deploy.yml +++ b/components/axdevice_base/.github/workflows/deploy.yml @@ -1,3 +1,6 @@ +# Deploy Workflow +# References shared workflow from axci + name: Deploy on: @@ -5,105 +8,9 @@ on: tags: - 'v[0-9]+.[0-9]+.[0-9]+' -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: 'pages' - cancel-in-progress: false - -env: - CARGO_TERM_COLOR: always - RUST_BACKTRACE: 1 - jobs: - verify-tag: - name: Verify Tag - runs-on: ubuntu-latest - outputs: - should_deploy: ${{ steps.check.outputs.should_deploy }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check if tag is on main or master branch - id: check - run: | - git fetch origin main master || true - BRANCHES=$(git branch -r --contains ${{ github.ref }}) - - if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then - echo "✓ Tag is on main or master branch" - echo "should_deploy=true" >> $GITHUB_OUTPUT - else - echo "✗ Tag is not on main or master branch, skipping deployment" - echo "Tag is on: $BRANCHES" - echo "should_deploy=false" >> $GITHUB_OUTPUT - fi - - - name: Verify version consistency - if: steps.check.outputs.should_deploy == 'true' - run: | - # Extract version from git tag (remove 'v' prefix) - TAG_VERSION="${{ github.ref_name }}" - TAG_VERSION="${TAG_VERSION#v}" - # Extract version from Cargo.toml - CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') - echo "Git tag version: $TAG_VERSION" - echo "Cargo.toml version: $CARGO_VERSION" - if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then - echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" - exit 1 - fi - echo "✓ Version check passed!" - - check: - uses: ./.github/workflows/check.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_deploy == 'true' - - test: - uses: ./.github/workflows/test.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_deploy == 'true' - - build: - name: Build documentation - runs-on: ubuntu-latest - needs: [verify-tag, check, test] - if: needs.verify-tag.outputs.should_deploy == 'true' - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Build docs - env: - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - run: | - cargo doc --no-deps --all-features - printf '' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: target/doc - deploy: - name: Deploy to GitHub Pages - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: [verify-tag, build] - if: needs.verify-tag.outputs.should_deploy == 'true' - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 + uses: arceos-hypervisor/axci/.github/workflows/deploy.yml@main + with: + verify_branch: true + verify_version: true diff --git a/components/axdevice_base/.github/workflows/push.yml b/components/axdevice_base/.github/workflows/push.yml new file mode 100644 index 000000000..3ff391a4a --- /dev/null +++ b/components/axdevice_base/.github/workflows/push.yml @@ -0,0 +1,16 @@ +name: Notify Parent Repository + +on: + push: + branches: + - main + - zcs + workflow_dispatch: + +jobs: + notify-parent: + name: Notify Parent Repository + # 调用 axci 仓库的可复用工作流 + uses: arceos-hypervisor/axci/.github/workflows/push.yml@main + secrets: + PARENT_REPO_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} diff --git a/components/axdevice_base/.github/workflows/release.yml b/components/axdevice_base/.github/workflows/release.yml index 2e857b48b..20f186395 100644 --- a/components/axdevice_base/.github/workflows/release.yml +++ b/components/axdevice_base/.github/workflows/release.yml @@ -1,3 +1,7 @@ +# Release Workflow +# References shared workflow from axci +# check + test must pass before release + name: Release on: @@ -6,155 +10,18 @@ on: - 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+-pre.[0-9]+' -permissions: - contents: write - jobs: - verify-tag: - name: Verify Tag - runs-on: ubuntu-latest - outputs: - should_release: ${{ steps.check.outputs.should_release }} - is_prerelease: ${{ steps.check.outputs.is_prerelease }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check tag type and branch - id: check - run: | - git fetch origin main master dev || true - - TAG="${{ github.ref_name }}" - BRANCHES=$(git branch -r --contains ${{ github.ref }}) - - echo "Tag: $TAG" - echo "Branches containing this tag: $BRANCHES" - - # Check if it's a prerelease tag - if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-pre\.[0-9]+$ ]]; then - echo "📦 Detected prerelease tag" - echo "is_prerelease=true" >> $GITHUB_OUTPUT - - if echo "$BRANCHES" | grep -q 'origin/dev'; then - echo "✓ Prerelease tag is on dev branch" - echo "should_release=true" >> $GITHUB_OUTPUT - else - echo "✗ Prerelease tag must be on dev branch, skipping release" - echo "should_release=false" >> $GITHUB_OUTPUT - fi - elif [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "📦 Detected stable release tag" - echo "is_prerelease=false" >> $GITHUB_OUTPUT - - if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then - echo "✓ Stable release tag is on main or master branch" - echo "should_release=true" >> $GITHUB_OUTPUT - else - echo "✗ Stable release tag must be on main or master branch, skipping release" - echo "should_release=false" >> $GITHUB_OUTPUT - fi - else - echo "✗ Unknown tag format, skipping release" - echo "is_prerelease=false" >> $GITHUB_OUTPUT - echo "should_release=false" >> $GITHUB_OUTPUT - fi - - - name: Verify version consistency - if: steps.check.outputs.should_release == 'true' - run: | - # Extract version from git tag (remove 'v' prefix) - TAG_VERSION="${{ github.ref_name }}" - TAG_VERSION="${TAG_VERSION#v}" - # Extract version from Cargo.toml - CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') - echo "Git tag version: $TAG_VERSION" - echo "Cargo.toml version: $CARGO_VERSION" - if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then - echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" - exit 1 - fi - echo "✓ Version check passed!" - check: - uses: ./.github/workflows/check.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_release == 'true' + uses: arceos-hypervisor/axci/.github/workflows/check.yml@main test: - uses: ./.github/workflows/test.yml - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' + uses: arceos-hypervisor/axci/.github/workflows/test.yml@main release: - name: Create GitHub Release - runs-on: ubuntu-latest - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Generate release notes - id: release_notes - run: | - CURRENT_TAG="${{ github.ref_name }}" - - # Get previous tag - PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -A1 "^${CURRENT_TAG}$" | tail -n1) - - if [ -z "$PREVIOUS_TAG" ] || [ "$PREVIOUS_TAG" == "$CURRENT_TAG" ]; then - echo "No previous tag found, this is the first release" - CHANGELOG="Initial release" - else - echo "Generating changelog from $PREVIOUS_TAG to $CURRENT_TAG" - - # Generate changelog with commit messages - CHANGELOG=$(git log --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${CURRENT_TAG}") - - if [ -z "$CHANGELOG" ]; then - CHANGELOG="No changes" - fi - fi - - # Write changelog to output file (multi-line) - { - echo "changelog<> $GITHUB_OUTPUT - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - draft: false - prerelease: ${{ needs.verify-tag.outputs.is_prerelease == 'true' }} - body: | - ## Changes - ${{ steps.release_notes.outputs.changelog }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - publish: - name: Publish to crates.io - runs-on: ubuntu-latest - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Dry run publish - run: cargo publish --dry-run - - - name: Publish to crates.io - run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + needs: [check, test] + uses: arceos-hypervisor/axci/.github/workflows/release.yml@main + with: + verify_branch: true + verify_version: true + secrets: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/components/axdevice_base/.github/workflows/test.yml b/components/axdevice_base/.github/workflows/test.yml index dc3b293d9..6a58ef8e5 100644 --- a/components/axdevice_base/.github/workflows/test.yml +++ b/components/axdevice_base/.github/workflows/test.yml @@ -1,3 +1,6 @@ +# Integration Test Workflow +# References shared workflow from axci + name: Test on: @@ -7,44 +10,8 @@ on: tags-ignore: - '**' pull_request: - workflow_call: + workflow_dispatch: jobs: - load-config: - name: Load CI Configuration - runs-on: ubuntu-latest - outputs: - targets: ${{ steps.config.outputs.targets }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Load configuration - id: config - run: | - TARGETS=$(jq -c '.targets' .github/config.json) - echo "targets=$TARGETS" >> $GITHUB_OUTPUT - test: - name: Test - runs-on: ubuntu-latest - needs: load-config - strategy: - fail-fast: false - matrix: - target: ${{ fromJson(needs.load-config.outputs.targets) }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - # - name: Run tests - # run: cargo test --target ${{ matrix.target }} --all-features -- --nocapture - - # - name: Run doc tests - # run: cargo test --target ${{ matrix.target }} --doc - - name: Run tests - run: echo "Tests are skipped!" + uses: arceos-hypervisor/axci/.github/workflows/test.yml@main diff --git a/components/axdevice_base/.gitignore b/components/axdevice_base/.gitignore index ff78c42af..22d7b02fd 100644 --- a/components/axdevice_base/.gitignore +++ b/components/axdevice_base/.gitignore @@ -2,3 +2,11 @@ /.vscode .DS_Store Cargo.lock + +# Test results (generated by shared test framework) +/test-results/ +/test_repos/ +*.log + +# Downloaded test framework +/scripts/.axci/ diff --git a/components/axdevice_base/Cargo.toml b/components/axdevice_base/Cargo.toml index ddbb66642..39b5f8575 100644 --- a/components/axdevice_base/Cargo.toml +++ b/components/axdevice_base/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "axdevice_base" -version = "0.2.1" +version = "0.2.2" edition = "2024" authors = ["Hui Xiao <1127751018@qq.com>"] description = "Basic traits and structures for emulated devices in ArceOS hypervisor." @@ -12,14 +12,12 @@ keywords = ["arceos", "hypervisor", "virtualization", "device", "no-std"] categories = ["no-std", "virtualization"] [dependencies] -# Serialization support serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } -# ArceOS core crates -axaddrspace = "0.1.4" -axerrno = "0.1" -axvmconfig = { version = "0.2", default-features = false } -memory_addr = "0.4" +# ArceOS and AxVisor crates. +axaddrspace = "0.3" +axerrno = "0.2" +axvmconfig = {version = "0.2", default-features = false} [dev-dependencies] diff --git a/components/axdevice_base/rust-toolchain.toml b/components/axdevice_base/rust-toolchain.toml new file mode 100644 index 000000000..d71709d74 --- /dev/null +++ b/components/axdevice_base/rust-toolchain.toml @@ -0,0 +1,10 @@ +[toolchain] +channel = "nightly-2026-02-25" +components = ["rust-src", "llvm-tools", "rustfmt", "clippy"] +profile = "minimal" +targets = [ + "aarch64-unknown-none-softfloat", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-none", + "riscv64gc-unknown-none-elf" +] diff --git a/components/axdevice_base/scripts/check.sh b/components/axdevice_base/scripts/check.sh new file mode 100755 index 000000000..d8bb79e11 --- /dev/null +++ b/components/axdevice_base/scripts/check.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# +# axdevice_base 代码检查脚本 +# 下载并调用 axci 仓库中的检查脚本 +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +COMPONENT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +COMPONENT_NAME="$(basename "$COMPONENT_DIR")" +AXCI_DIR="${SCRIPT_DIR}/.axci" +AXCI_REPO="https://github.com/arceos-hypervisor/axci.git" + +# 下载或更新 axci 仓库 +download_axci() { + if [ -d "$AXCI_DIR" ]; then + echo "Updating axci repository..." + cd "$AXCI_DIR" && git pull --quiet + else + echo "Downloading axci repository..." + git clone --quiet "$AXCI_REPO" "$AXCI_DIR" + fi +} + +# 主函数 +main() { + download_axci + + # 在组件目录中运行检查 + cd "$COMPONENT_DIR" + exec bash "$AXCI_DIR/check.sh" --component-dir "$COMPONENT_DIR" "$@" +} + +main "$@" diff --git a/components/axdevice_base/scripts/test.sh b/components/axdevice_base/scripts/test.sh new file mode 100755 index 000000000..8875c72f1 --- /dev/null +++ b/components/axdevice_base/scripts/test.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# +# axdevice_base 测试脚本 +# 下载并调用 axci 仓库中的测试框架 +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +COMPONENT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +AXCI_DIR="${SCRIPT_DIR}/.axci" +AXCI_REPO="https://github.com/arceos-hypervisor/axci.git" + +# 下载或更新 axci 仓库 +download_axci() { + if [ -d "$AXCI_DIR" ]; then + echo "Updating axci repository..." + cd "$AXCI_DIR" && git pull --quiet + else + echo "Downloading axci repository..." + git clone --quiet "$AXCI_REPO" "$AXCI_DIR" + fi +} + +# 主函数 +main() { + download_axci + + # 在组件目录中运行测试,自动指定当前组件 + cd "$COMPONENT_DIR" + exec bash "$AXCI_DIR/tests.sh" --component-dir "$COMPONENT_DIR" "$@" +} + +main "$@" diff --git a/components/axdevice_base/src/lib.rs b/components/axdevice_base/src/lib.rs index f38eaa79e..79d0c5572 100644 --- a/components/axdevice_base/src/lib.rs +++ b/components/axdevice_base/src/lib.rs @@ -332,6 +332,3 @@ pub trait BaseSysRegDeviceOps = BaseDeviceOps; /// /// Port I/O devices are only used on x86/x86_64 architectures. pub trait BasePortDeviceOps = BaseDeviceOps; - -#[cfg(test)] -mod test; diff --git a/components/axdevice_base/src/test.rs b/components/axdevice_base/tests/test.rs similarity index 96% rename from components/axdevice_base/src/test.rs rename to components/axdevice_base/tests/test.rs index 6ab99d480..a4357122a 100644 --- a/components/axdevice_base/src/test.rs +++ b/components/axdevice_base/tests/test.rs @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +extern crate alloc; + use alloc::{sync::Arc, vec, vec::Vec}; use axaddrspace::{GuestPhysAddr, GuestPhysAddrRange, device::AccessWidth}; +use axdevice_base::{BaseDeviceOps, EmuDeviceType, map_device_of_type}; use axerrno::AxResult; -use crate::{BaseDeviceOps, EmuDeviceType, map_device_of_type}; - const DEVICE_A_TEST_METHOD_ANSWER: usize = 42; struct DeviceA; diff --git a/components/axhvc/.github/config.json b/components/axhvc/.github/config.json index f347e101e..4bbc2c022 100644 --- a/components/axhvc/.github/config.json +++ b/components/axhvc/.github/config.json @@ -1,10 +1,17 @@ { + "component": { + "name": "axhvc", + "crate_name": "axhvc" + }, "targets": [ "aarch64-unknown-none-softfloat", "x86_64-unknown-linux-gnu", "x86_64-unknown-none", "riscv64gc-unknown-none-elf" ], + "unit_test_targets": [ + "x86_64-unknown-linux-gnu" + ], "rust_components": [ "rust-src", "clippy", diff --git a/components/axhvc/.github/workflows/check.yml b/components/axhvc/.github/workflows/check.yml index 330fa15e9..018bbbeb2 100644 --- a/components/axhvc/.github/workflows/check.yml +++ b/components/axhvc/.github/workflows/check.yml @@ -1,66 +1,15 @@ -name: Quality Checks +# Quality Check Workflow +# References shared workflow from axci + +name: Check on: push: - branches: - - '**' - tags-ignore: - - '**' + branches: ['**'] + tags-ignore: ['**'] pull_request: - workflow_call: + workflow_dispatch: jobs: - load-config: - name: Load CI Configuration - runs-on: ubuntu-latest - outputs: - targets: ${{ steps.config.outputs.targets }} - rust_components: ${{ steps.config.outputs.rust_components }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Load configuration - id: config - run: | - TARGETS=$(jq -c '.targets' .github/config.json) - COMPONENTS=$(jq -r '.rust_components | join(", ")' .github/config.json) - - echo "targets=$TARGETS" >> $GITHUB_OUTPUT - echo "rust_components=$COMPONENTS" >> $GITHUB_OUTPUT - check: - name: Check - runs-on: ubuntu-latest - needs: load-config - strategy: - fail-fast: false - matrix: - target: ${{ fromJson(needs.load-config.outputs.targets) }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - with: - components: ${{ needs.load-config.outputs.rust_components }} - targets: ${{ matrix.target }} - - - name: Check rust version - run: rustc --version --verbose - - - name: Check code format - run: cargo fmt --all -- --check - - - name: Build - run: cargo build --target ${{ matrix.target }} --all-features - - - name: Run clippy - run: cargo clippy --target ${{ matrix.target }} --all-features -- -D warnings - - - name: Build documentation - env: - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - run: cargo doc --no-deps --target ${{ matrix.target }} --all-features + uses: arceos-hypervisor/axci/.github/workflows/check.yml@main diff --git a/components/axhvc/.github/workflows/deploy.yml b/components/axhvc/.github/workflows/deploy.yml index 882ba9310..37b00c09c 100644 --- a/components/axhvc/.github/workflows/deploy.yml +++ b/components/axhvc/.github/workflows/deploy.yml @@ -1,3 +1,6 @@ +# Deploy Workflow +# References shared workflow from axci + name: Deploy on: @@ -5,105 +8,9 @@ on: tags: - 'v[0-9]+.[0-9]+.[0-9]+' -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: 'pages' - cancel-in-progress: false - -env: - CARGO_TERM_COLOR: always - RUST_BACKTRACE: 1 - jobs: - verify-tag: - name: Verify Tag - runs-on: ubuntu-latest - outputs: - should_deploy: ${{ steps.check.outputs.should_deploy }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check if tag is on main or master branch - id: check - run: | - git fetch origin main master || true - BRANCHES=$(git branch -r --contains ${{ github.ref }}) - - if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then - echo "✓ Tag is on main or master branch" - echo "should_deploy=true" >> $GITHUB_OUTPUT - else - echo "✗ Tag is not on main or master branch, skipping deployment" - echo "Tag is on: $BRANCHES" - echo "should_deploy=false" >> $GITHUB_OUTPUT - fi - - - name: Verify version consistency - if: steps.check.outputs.should_deploy == 'true' - run: | - # Extract version from git tag (remove 'v' prefix) - TAG_VERSION="${{ github.ref_name }}" - TAG_VERSION="${TAG_VERSION#v}" - # Extract version from Cargo.toml - CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') - echo "Git tag version: $TAG_VERSION" - echo "Cargo.toml version: $CARGO_VERSION" - if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then - echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" - exit 1 - fi - echo "✓ Version check passed!" - - check: - uses: ./.github/workflows/check.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_deploy == 'true' - - test: - uses: ./.github/workflows/test.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_deploy == 'true' - - build: - name: Build documentation - runs-on: ubuntu-latest - needs: [verify-tag, check, test] - if: needs.verify-tag.outputs.should_deploy == 'true' - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Build docs - env: - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - run: | - cargo doc --no-deps --all-features - printf '' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: target/doc - deploy: - name: Deploy to GitHub Pages - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: [verify-tag, build] - if: needs.verify-tag.outputs.should_deploy == 'true' - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 + uses: arceos-hypervisor/axci/.github/workflows/deploy.yml@main + with: + verify_branch: true + verify_version: true diff --git a/components/axhvc/.github/workflows/push.yml b/components/axhvc/.github/workflows/push.yml new file mode 100644 index 000000000..3ff391a4a --- /dev/null +++ b/components/axhvc/.github/workflows/push.yml @@ -0,0 +1,16 @@ +name: Notify Parent Repository + +on: + push: + branches: + - main + - zcs + workflow_dispatch: + +jobs: + notify-parent: + name: Notify Parent Repository + # 调用 axci 仓库的可复用工作流 + uses: arceos-hypervisor/axci/.github/workflows/push.yml@main + secrets: + PARENT_REPO_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} diff --git a/components/axhvc/.github/workflows/release.yml b/components/axhvc/.github/workflows/release.yml index 2e857b48b..20f186395 100644 --- a/components/axhvc/.github/workflows/release.yml +++ b/components/axhvc/.github/workflows/release.yml @@ -1,3 +1,7 @@ +# Release Workflow +# References shared workflow from axci +# check + test must pass before release + name: Release on: @@ -6,155 +10,18 @@ on: - 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+-pre.[0-9]+' -permissions: - contents: write - jobs: - verify-tag: - name: Verify Tag - runs-on: ubuntu-latest - outputs: - should_release: ${{ steps.check.outputs.should_release }} - is_prerelease: ${{ steps.check.outputs.is_prerelease }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check tag type and branch - id: check - run: | - git fetch origin main master dev || true - - TAG="${{ github.ref_name }}" - BRANCHES=$(git branch -r --contains ${{ github.ref }}) - - echo "Tag: $TAG" - echo "Branches containing this tag: $BRANCHES" - - # Check if it's a prerelease tag - if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-pre\.[0-9]+$ ]]; then - echo "📦 Detected prerelease tag" - echo "is_prerelease=true" >> $GITHUB_OUTPUT - - if echo "$BRANCHES" | grep -q 'origin/dev'; then - echo "✓ Prerelease tag is on dev branch" - echo "should_release=true" >> $GITHUB_OUTPUT - else - echo "✗ Prerelease tag must be on dev branch, skipping release" - echo "should_release=false" >> $GITHUB_OUTPUT - fi - elif [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "📦 Detected stable release tag" - echo "is_prerelease=false" >> $GITHUB_OUTPUT - - if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then - echo "✓ Stable release tag is on main or master branch" - echo "should_release=true" >> $GITHUB_OUTPUT - else - echo "✗ Stable release tag must be on main or master branch, skipping release" - echo "should_release=false" >> $GITHUB_OUTPUT - fi - else - echo "✗ Unknown tag format, skipping release" - echo "is_prerelease=false" >> $GITHUB_OUTPUT - echo "should_release=false" >> $GITHUB_OUTPUT - fi - - - name: Verify version consistency - if: steps.check.outputs.should_release == 'true' - run: | - # Extract version from git tag (remove 'v' prefix) - TAG_VERSION="${{ github.ref_name }}" - TAG_VERSION="${TAG_VERSION#v}" - # Extract version from Cargo.toml - CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') - echo "Git tag version: $TAG_VERSION" - echo "Cargo.toml version: $CARGO_VERSION" - if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then - echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" - exit 1 - fi - echo "✓ Version check passed!" - check: - uses: ./.github/workflows/check.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_release == 'true' + uses: arceos-hypervisor/axci/.github/workflows/check.yml@main test: - uses: ./.github/workflows/test.yml - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' + uses: arceos-hypervisor/axci/.github/workflows/test.yml@main release: - name: Create GitHub Release - runs-on: ubuntu-latest - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Generate release notes - id: release_notes - run: | - CURRENT_TAG="${{ github.ref_name }}" - - # Get previous tag - PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -A1 "^${CURRENT_TAG}$" | tail -n1) - - if [ -z "$PREVIOUS_TAG" ] || [ "$PREVIOUS_TAG" == "$CURRENT_TAG" ]; then - echo "No previous tag found, this is the first release" - CHANGELOG="Initial release" - else - echo "Generating changelog from $PREVIOUS_TAG to $CURRENT_TAG" - - # Generate changelog with commit messages - CHANGELOG=$(git log --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${CURRENT_TAG}") - - if [ -z "$CHANGELOG" ]; then - CHANGELOG="No changes" - fi - fi - - # Write changelog to output file (multi-line) - { - echo "changelog<> $GITHUB_OUTPUT - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - draft: false - prerelease: ${{ needs.verify-tag.outputs.is_prerelease == 'true' }} - body: | - ## Changes - ${{ steps.release_notes.outputs.changelog }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - publish: - name: Publish to crates.io - runs-on: ubuntu-latest - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Dry run publish - run: cargo publish --dry-run - - - name: Publish to crates.io - run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + needs: [check, test] + uses: arceos-hypervisor/axci/.github/workflows/release.yml@main + with: + verify_branch: true + verify_version: true + secrets: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/components/axhvc/.github/workflows/test.yml b/components/axhvc/.github/workflows/test.yml index dc3b293d9..6a58ef8e5 100644 --- a/components/axhvc/.github/workflows/test.yml +++ b/components/axhvc/.github/workflows/test.yml @@ -1,3 +1,6 @@ +# Integration Test Workflow +# References shared workflow from axci + name: Test on: @@ -7,44 +10,8 @@ on: tags-ignore: - '**' pull_request: - workflow_call: + workflow_dispatch: jobs: - load-config: - name: Load CI Configuration - runs-on: ubuntu-latest - outputs: - targets: ${{ steps.config.outputs.targets }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Load configuration - id: config - run: | - TARGETS=$(jq -c '.targets' .github/config.json) - echo "targets=$TARGETS" >> $GITHUB_OUTPUT - test: - name: Test - runs-on: ubuntu-latest - needs: load-config - strategy: - fail-fast: false - matrix: - target: ${{ fromJson(needs.load-config.outputs.targets) }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - # - name: Run tests - # run: cargo test --target ${{ matrix.target }} --all-features -- --nocapture - - # - name: Run doc tests - # run: cargo test --target ${{ matrix.target }} --doc - - name: Run tests - run: echo "Tests are skipped!" + uses: arceos-hypervisor/axci/.github/workflows/test.yml@main diff --git a/components/axhvc/.gitignore b/components/axhvc/.gitignore index ff78c42af..22d7b02fd 100644 --- a/components/axhvc/.gitignore +++ b/components/axhvc/.gitignore @@ -2,3 +2,11 @@ /.vscode .DS_Store Cargo.lock + +# Test results (generated by shared test framework) +/test-results/ +/test_repos/ +*.log + +# Downloaded test framework +/scripts/.axci/ diff --git a/components/axhvc/README.md b/components/axhvc/README.md index ce5cb55cf..c62e22d28 100644 --- a/components/axhvc/README.md +++ b/components/axhvc/README.md @@ -1,63 +1,118 @@ -# axhvc +

axhvc

-[![Crates.io](https://img.shields.io/crates/v/axhvc)](https://crates.io/crates/axhvc) +

AxVisor HyperCall Definitions

+ +
+ +[![Crates.io](https://img.shields.io/crates/v/axhvc.svg)](https://crates.io/crates/axhvc) [![Docs.rs](https://docs.rs/axhvc/badge.svg)](https://docs.rs/axhvc) -[![CI](https://github.com/arceos-hypervisor/axhvc/actions/workflows/ci.yml/badge.svg)](https://github.com/arceos-hypervisor/axhvc/actions/workflows/ci.yml) +[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/arceos-hypervisor/axhvc/blob/main/LICENSE) + +
+ +English | [中文](README_CN.md) + +# Introduction + +`axhvc` provides AxVisor hypercall definitions for guest-hypervisor communication. It is a lightweight `#![no_std]` crate intended for bare-metal guests, hypervisors, and low-level virtualization components across x86_64, RISC-V, and AArch64 platforms. + +This library exports three core public types: -AxVisor HyperCall definitions for guest-hypervisor communication. +- **`HyperCallCode`** - Enumerates all supported AxVisor hypercall operations +- **`InvalidHyperCallCode`** - Represents conversion errors for invalid numeric hypercall values +- **`HyperCallResult`** - Alias of `AxResult` used by hypercall handlers -## Overview +`HyperCallCode` supports `TryFrom` conversion and includes variants for hypervisor control and IVC channel management. -This crate provides the hypercall interface for [AxVisor](https://github.com/arceos-hypervisor/axvisor), a type-1 hypervisor based on [ArceOS](https://github.com/arceos-org/arceos). It defines the hypercall codes and result types used for communication between guest VMs and the hypervisor. +## Quick Start -## Features +### Requirements -- `no_std` compatible - suitable for bare-metal and embedded environments -- Defines all supported hypercall operations -- Provides type-safe hypercall codes with numeric enum conversion -- Cross-platform support (x86_64, RISC-V, AArch64) +- Rust nightly toolchain +- Rust components: rust-src, clippy, rustfmt -## Supported Hypercalls +```bash +# Install rustup (if not installed) +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# Install nightly toolchain and components +rustup install nightly +rustup component add rust-src clippy rustfmt --toolchain nightly +``` + +### Run Check and Test + +```bash +# 1. Enter the repository +cd axhvc + +# 2. Code check +./scripts/check.sh + +# 3. Run tests +./scripts/test.sh +``` -| Code | Name | Description | -|------|------|-------------| -| 0 | `HypervisorDisable` | Disable the hypervisor | -| 1 | `HyperVisorPrepareDisable` | Prepare to disable the hypervisor | -| 2 | `HyperVisorDebug` | Debug hypercall (development only) | -| 3 | `HIVCPublishChannel` | Publish an IVC shared memory channel | -| 4 | `HIVCSubscribChannel` | Subscribe to an IVC shared memory channel | -| 5 | `HIVCUnPublishChannel` | Unpublish an IVC shared memory channel | -| 6 | `HIVCUnSubscribChannel` | Unsubscribe from an IVC shared memory channel | +## Integration -## Usage +### Installation -Add this to your `Cargo.toml`: +Add to your `Cargo.toml`: ```toml [dependencies] -axhvc = "0.1" +axhvc = "0.2.0" ``` ### Example -```rust,ignore -use axhvc::{HyperCallCode, HyperCallResult}; - -fn handle_hypercall(code: HyperCallCode) -> HyperCallResult { - match code { - HyperCallCode::HypervisorDisable => { - // Handle hypervisor disable request - Ok(0) - } - HyperCallCode::HIVCPublishChannel => { - // Handle IVC channel publish request - Ok(0) - } - _ => Err(axerrno::AxError::Unsupported), - } +```rust +use axerrno::ax_err; +use axhvc::{HyperCallCode, HyperCallResult, InvalidHyperCallCode}; + +fn handle_hypercall(code: u32) -> Result { + let code = HyperCallCode::try_from(code)?; + + let result = match code { + HyperCallCode::HypervisorDisable => Ok(0), + HyperCallCode::HyperVisorPrepareDisable => Ok(0), + HyperCallCode::HIVCPublishChannel => Ok(0x1000), + _ => ax_err!(Unsupported), + }; + + Ok(result) +} + +fn main() { + let code = HyperCallCode::try_from(3u32).unwrap(); + assert_eq!(code as u32, 3); + + let result = handle_hypercall(code as u32).unwrap().unwrap(); + assert_eq!(result, 0x1000); + + let invalid = HyperCallCode::try_from(7u32); + assert!(invalid.is_err()); } ``` -## License +### Documentation + +Generate and view API documentation: + +```bash +cargo doc --no-deps --open +``` + +Online documentation: [docs.rs/axhvc](https://docs.rs/axhvc) + +# Contributing + +1. Fork the repository and create a branch +2. Run local check: `./scripts/check.sh` +3. Run local tests: `./scripts/test.sh` +4. Submit PR and pass CI checks + +# License -Axhvc is licensed under the Apache License, Version 2.0. See the [LICENSE](./LICENSE) file for details. \ No newline at end of file +Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details. diff --git a/components/axhvc/README_CN.md b/components/axhvc/README_CN.md new file mode 100644 index 000000000..cb93e9bd8 --- /dev/null +++ b/components/axhvc/README_CN.md @@ -0,0 +1,118 @@ +

axhvc

+ +

AxVisor HyperCall 定义库

+ +
+ +[![Crates.io](https://img.shields.io/crates/v/axhvc.svg)](https://crates.io/crates/axhvc) +[![Docs.rs](https://docs.rs/axhvc/badge.svg)](https://docs.rs/axhvc) +[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/arceos-hypervisor/axhvc/blob/main/LICENSE) + +
+ +[English](README.md) | 中文 + +# Introduction + +`axhvc` 提供 AxVisor 中 guest 与 hypervisor 通信所需的 hypercall 定义。它是一个轻量级的 `#![no_std]` crate,适用于裸机 guest、hypervisor 以及 x86_64、RISC-V、AArch64 等平台上的底层虚拟化组件。 + +该库导出三个核心公开类型: + +- **`HyperCallCode`** - 枚举所有受支持的 AxVisor hypercall 操作 +- **`InvalidHyperCallCode`** - 表示非法数值转换为 hypercall 编码时的错误 +- **`HyperCallResult`** - Hypercall 处理函数使用的 `AxResult` 类型别名 + +`HyperCallCode` 支持 `TryFrom` 转换,当前覆盖 hypervisor 控制和 IVC 通道管理两类操作。 + +## Quick Start + +### Requirements + +- Rust nightly 工具链 +- Rust 组件:rust-src、clippy、rustfmt + +```bash +# 安装 rustup(如果尚未安装) +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# 安装 nightly 工具链与所需组件 +rustup install nightly +rustup component add rust-src clippy rustfmt --toolchain nightly +``` + +### Run Check and Test + +```bash +# 1. 进入仓库目录 +cd axhvc + +# 2. 代码检查 +./scripts/check.sh + +# 3. 运行测试 +./scripts/test.sh +``` + +## Integration + +### Installation + +将以下依赖加入 `Cargo.toml`: + +```toml +[dependencies] +axhvc = "0.2.0" +``` + +### Example + +```rust +use axerrno::ax_err; +use axhvc::{HyperCallCode, HyperCallResult, InvalidHyperCallCode}; + +fn handle_hypercall(code: u32) -> Result { + let code = HyperCallCode::try_from(code)?; + + let result = match code { + HyperCallCode::HypervisorDisable => Ok(0), + HyperCallCode::HyperVisorPrepareDisable => Ok(0), + HyperCallCode::HIVCPublishChannel => Ok(0x1000), + _ => ax_err!(Unsupported), + }; + + Ok(result) +} + +fn main() { + let code = HyperCallCode::try_from(3u32).unwrap(); + assert_eq!(code as u32, 3); + + let result = handle_hypercall(code as u32).unwrap().unwrap(); + assert_eq!(result, 0x1000); + + let invalid = HyperCallCode::try_from(7u32); + assert!(invalid.is_err()); +} +``` + +### Documentation + +生成并查看 API 文档: + +```bash +cargo doc --no-deps --open +``` + +在线文档: [docs.rs/axhvc](https://docs.rs/axhvc) + +# Contributing + +1. Fork 仓库并创建分支 +2. 本地运行检查:`./scripts/check.sh` +3. 本地运行测试:`./scripts/test.sh` +4. 提交 PR 并通过 CI 检查 + +# License + +本项目基于 Apache License 2.0 许可证发布。详见 [LICENSE](LICENSE)。 diff --git a/components/axhvc/scripts/check.sh b/components/axhvc/scripts/check.sh new file mode 100755 index 000000000..01c72be57 --- /dev/null +++ b/components/axhvc/scripts/check.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# +# axhvc 代码检查脚本 +# 下载并调用 axci 仓库中的检查脚本 +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +COMPONENT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +COMPONENT_NAME="$(basename "$COMPONENT_DIR")" +AXCI_DIR="${SCRIPT_DIR}/.axci" +AXCI_REPO="https://github.com/arceos-hypervisor/axci.git" + +# 下载或更新 axci 仓库 +download_axci() { + if [ -d "$AXCI_DIR" ]; then + echo "Updating axci repository..." + cd "$AXCI_DIR" && git pull --quiet + else + echo "Downloading axci repository..." + git clone --quiet "$AXCI_REPO" "$AXCI_DIR" + fi +} + +# 主函数 +main() { + download_axci + + # 在组件目录中运行检查 + cd "$COMPONENT_DIR" + exec bash "$AXCI_DIR/check.sh" --component-dir "$COMPONENT_DIR" "$@" +} + +main "$@" diff --git a/components/axhvc/scripts/test.sh b/components/axhvc/scripts/test.sh new file mode 100755 index 000000000..421bed113 --- /dev/null +++ b/components/axhvc/scripts/test.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# +# axhvc 测试脚本 +# 下载并调用 axci 仓库中的测试框架 +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +COMPONENT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +AXCI_DIR="${SCRIPT_DIR}/.axci" +AXCI_REPO="https://github.com/arceos-hypervisor/axci.git" + +# 下载或更新 axci 仓库 +download_axci() { + if [ -d "$AXCI_DIR" ]; then + echo "Updating axci repository..." + cd "$AXCI_DIR" && git pull --quiet + else + echo "Downloading axci repository..." + git clone --quiet "$AXCI_REPO" "$AXCI_DIR" + fi +} + +# 主函数 +main() { + download_axci + + # 在组件目录中运行测试,自动指定当前组件 + cd "$COMPONENT_DIR" + exec bash "$AXCI_DIR/tests.sh" --component-dir "$COMPONENT_DIR" "$@" +} + +main "$@" diff --git a/components/axhvc/tests/test.rs b/components/axhvc/tests/test.rs new file mode 100644 index 000000000..16be1d375 --- /dev/null +++ b/components/axhvc/tests/test.rs @@ -0,0 +1,100 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use axhvc::{HyperCallCode, InvalidHyperCallCode}; + +#[test] +fn test_hypercall_code_from_u32_valid() { + assert_eq!( + HyperCallCode::try_from(0u32).unwrap(), + HyperCallCode::HypervisorDisable + ); + assert_eq!( + HyperCallCode::try_from(1u32).unwrap(), + HyperCallCode::HyperVisorPrepareDisable + ); + assert_eq!( + HyperCallCode::try_from(2u32).unwrap(), + HyperCallCode::HyperVisorDebug + ); + assert_eq!( + HyperCallCode::try_from(3u32).unwrap(), + HyperCallCode::HIVCPublishChannel + ); + assert_eq!( + HyperCallCode::try_from(4u32).unwrap(), + HyperCallCode::HIVCSubscribChannel + ); + assert_eq!( + HyperCallCode::try_from(5u32).unwrap(), + HyperCallCode::HIVCUnPublishChannel + ); + assert_eq!( + HyperCallCode::try_from(6u32).unwrap(), + HyperCallCode::HIVCUnSubscribChannel + ); +} + +#[test] +fn test_hypercall_code_from_u32_invalid() { + assert!(HyperCallCode::try_from(7u32).is_err()); + assert!(HyperCallCode::try_from(100u32).is_err()); + assert!(HyperCallCode::try_from(u32::MAX).is_err()); +} + +#[test] +fn test_hypercall_code_to_u32() { + assert_eq!(HyperCallCode::HypervisorDisable as u32, 0); + assert_eq!(HyperCallCode::HyperVisorPrepareDisable as u32, 1); + assert_eq!(HyperCallCode::HyperVisorDebug as u32, 2); + assert_eq!(HyperCallCode::HIVCPublishChannel as u32, 3); + assert_eq!(HyperCallCode::HIVCSubscribChannel as u32, 4); + assert_eq!(HyperCallCode::HIVCUnPublishChannel as u32, 5); + assert_eq!(HyperCallCode::HIVCUnSubscribChannel as u32, 6); +} + +#[test] +fn test_hypercall_code_equality() { + assert_eq!( + HyperCallCode::HypervisorDisable, + HyperCallCode::HypervisorDisable + ); + assert_ne!( + HyperCallCode::HypervisorDisable, + HyperCallCode::HyperVisorDebug + ); +} + +#[test] +fn test_invalid_hypercall_code_display() { + let err = InvalidHyperCallCode(0xFF); + assert_eq!(format!("{}", err), "invalid hypercall code: 0xff"); + + let err = InvalidHyperCallCode(7); + assert_eq!(format!("{}", err), "invalid hypercall code: 0x7"); +} + +#[test] +fn test_invalid_hypercall_code_debug() { + let err = InvalidHyperCallCode(42); + assert_eq!(format!("{:?}", err), "InvalidHyperCallCode(42)"); +} + +#[test] +fn test_invalid_hypercall_code_from_try_from() { + let result = HyperCallCode::try_from(999u32); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert_eq!(err.0, 999); +} diff --git a/components/axklib/.github/workflows/push.yml b/components/axklib/.github/workflows/push.yml new file mode 100644 index 000000000..a18de4a04 --- /dev/null +++ b/components/axklib/.github/workflows/push.yml @@ -0,0 +1,147 @@ +# ═══════════════════════════════════════════════════════════════════════════════ +# 组件仓库 GitHub Actions 配置模板 +# ═══════════════════════════════════════════════════════════════════════════════ +# +# 此文件用于子仓库,当子仓库有更新时通知主仓库进行 subtree pull 同步。 +# +# 【使用步骤】 +# ───────────────────────────────────────────────────────────────────────────── +# 1. 将此文件复制到子仓库的 .github/workflows/ 目录: +# cp scripts/push.yml <子仓库>/.github/workflows/push.yml +# +# 2. 在子仓库中配置 Secret: +# GitHub 仓库 → Settings → Secrets → Actions → New repository secret +# 名称: PARENT_REPO_TOKEN +# 值: 具有主仓库 repo 权限的 Personal Access Token +# +# 3. 修改下方 env 块中的一个变量(标注了「需要修改」的行): +# PARENT_REPO - 主仓库路径,例如 rcore-os/tgoskits +# (subtree 目录由主仓库自动从 git 历史中推断,无需手动指定) +# +# 【Token 权限要求】 +# ───────────────────────────────────────────────────────────────────────────── +# PARENT_REPO_TOKEN 需要 Classic Personal Access Token,权限包括: +# - repo (Full control of private repositories) +# 或 +# - Fine-grained token: Contents (Read and Write) +# +# 【触发条件】 +# ───────────────────────────────────────────────────────────────────────────── +# - 自动触发:推送到 dev 或 main 分支时 +# - 手动触发:Actions → Notify Parent Repository → Run workflow +# +# 【工作流程】 +# ───────────────────────────────────────────────────────────────────────────── +# 子仓库 push → 触发此工作流 → 调用主仓库 API → 主仓库 subtree pull +# +# 【注意事项】 +# ───────────────────────────────────────────────────────────────────────────── +# - 主仓库需要配置接收 repository_dispatch 事件的同步工作流 +# - 如果不需要子仓库到主仓库的同步,可以不使用此文件 +# +# ═══════════════════════════════════════════════════════════════════════════════ + +name: Notify Parent Repository + +# 当有新的推送时触发 +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Get repository info + id: repo + env: + GH_REPO_NAME: ${{ github.event.repository.name }} + GH_REF_NAME: ${{ github.ref_name }} + GH_SERVER_URL: ${{ github.server_url }} + GH_REPOSITORY: ${{ github.repository }} + run: | + # 直接使用 GitHub Actions 内置变量,通过 env 传入避免 shell 注入 + COMPONENT="$GH_REPO_NAME" + BRANCH="$GH_REF_NAME" + # 构造标准 HTTPS URL,供主仓库按 URL 精确匹配 repos.list + REPO_URL="${GH_SERVER_URL}/${GH_REPOSITORY}" + + echo "component=${COMPONENT}" >> $GITHUB_OUTPUT + echo "branch=${BRANCH}" >> $GITHUB_OUTPUT + echo "repo_url=${REPO_URL}" >> $GITHUB_OUTPUT + + echo "Component: ${COMPONENT}" + echo "Branch: ${BRANCH}" + echo "Repo URL: ${REPO_URL}" + + - name: Notify parent repository + env: + # ── 需要修改 ────────────────────────────────────────────────────────── + PARENT_REPO: "rcore-os/tgoskits" # 主仓库路径 + # ── 无需修改 ────────────────────────────────────────────────────────── + DISPATCH_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} + # 将用户可控内容通过 env 传入,避免直接插值到 shell 脚本 + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + GIT_ACTOR: ${{ github.actor }} + GIT_SHA: ${{ github.sha }} + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "Notifying parent repository about update in ${COMPONENT}:${BRANCH}" + + # 使用 jq 安全构建 JSON,避免 commit message 中任何特殊字符导致注入 + PAYLOAD=$(jq -n \ + --arg component "$COMPONENT" \ + --arg branch "$BRANCH" \ + --arg repo_url "$REPO_URL" \ + --arg commit "$GIT_SHA" \ + --arg message "$COMMIT_MESSAGE" \ + --arg author "$GIT_ACTOR" \ + '{ + event_type: "subtree-update", + client_payload: { + component: $component, + branch: $branch, + repo_url: $repo_url, + commit: $commit, + message: $message, + author: $author + } + }') + + curl --fail --show-error -X POST \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${DISPATCH_TOKEN}" \ + https://api.github.com/repos/${PARENT_REPO}/dispatches \ + -d "$PAYLOAD" + + echo "Notification sent successfully" + + - name: Create summary + env: + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + GIT_SHA: ${{ github.sha }} + GIT_ACTOR: ${{ github.actor }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "## Notification Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Component**: ${COMPONENT}" >> $GITHUB_STEP_SUMMARY + echo "- **Branch**: ${BRANCH}" >> $GITHUB_STEP_SUMMARY + echo "- **Repo URL**: ${REPO_URL}" >> $GITHUB_STEP_SUMMARY + echo "- **Commit**: \`${GIT_SHA}\`" >> $GITHUB_STEP_SUMMARY + echo "- **Author**: ${GIT_ACTOR}" >> $GITHUB_STEP_SUMMARY + echo "- **Status**: ✅ Notification sent" >> $GITHUB_STEP_SUMMARY diff --git a/components/axvcpu/.github/workflows/push.yml b/components/axvcpu/.github/workflows/push.yml new file mode 100644 index 000000000..a18de4a04 --- /dev/null +++ b/components/axvcpu/.github/workflows/push.yml @@ -0,0 +1,147 @@ +# ═══════════════════════════════════════════════════════════════════════════════ +# 组件仓库 GitHub Actions 配置模板 +# ═══════════════════════════════════════════════════════════════════════════════ +# +# 此文件用于子仓库,当子仓库有更新时通知主仓库进行 subtree pull 同步。 +# +# 【使用步骤】 +# ───────────────────────────────────────────────────────────────────────────── +# 1. 将此文件复制到子仓库的 .github/workflows/ 目录: +# cp scripts/push.yml <子仓库>/.github/workflows/push.yml +# +# 2. 在子仓库中配置 Secret: +# GitHub 仓库 → Settings → Secrets → Actions → New repository secret +# 名称: PARENT_REPO_TOKEN +# 值: 具有主仓库 repo 权限的 Personal Access Token +# +# 3. 修改下方 env 块中的一个变量(标注了「需要修改」的行): +# PARENT_REPO - 主仓库路径,例如 rcore-os/tgoskits +# (subtree 目录由主仓库自动从 git 历史中推断,无需手动指定) +# +# 【Token 权限要求】 +# ───────────────────────────────────────────────────────────────────────────── +# PARENT_REPO_TOKEN 需要 Classic Personal Access Token,权限包括: +# - repo (Full control of private repositories) +# 或 +# - Fine-grained token: Contents (Read and Write) +# +# 【触发条件】 +# ───────────────────────────────────────────────────────────────────────────── +# - 自动触发:推送到 dev 或 main 分支时 +# - 手动触发:Actions → Notify Parent Repository → Run workflow +# +# 【工作流程】 +# ───────────────────────────────────────────────────────────────────────────── +# 子仓库 push → 触发此工作流 → 调用主仓库 API → 主仓库 subtree pull +# +# 【注意事项】 +# ───────────────────────────────────────────────────────────────────────────── +# - 主仓库需要配置接收 repository_dispatch 事件的同步工作流 +# - 如果不需要子仓库到主仓库的同步,可以不使用此文件 +# +# ═══════════════════════════════════════════════════════════════════════════════ + +name: Notify Parent Repository + +# 当有新的推送时触发 +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Get repository info + id: repo + env: + GH_REPO_NAME: ${{ github.event.repository.name }} + GH_REF_NAME: ${{ github.ref_name }} + GH_SERVER_URL: ${{ github.server_url }} + GH_REPOSITORY: ${{ github.repository }} + run: | + # 直接使用 GitHub Actions 内置变量,通过 env 传入避免 shell 注入 + COMPONENT="$GH_REPO_NAME" + BRANCH="$GH_REF_NAME" + # 构造标准 HTTPS URL,供主仓库按 URL 精确匹配 repos.list + REPO_URL="${GH_SERVER_URL}/${GH_REPOSITORY}" + + echo "component=${COMPONENT}" >> $GITHUB_OUTPUT + echo "branch=${BRANCH}" >> $GITHUB_OUTPUT + echo "repo_url=${REPO_URL}" >> $GITHUB_OUTPUT + + echo "Component: ${COMPONENT}" + echo "Branch: ${BRANCH}" + echo "Repo URL: ${REPO_URL}" + + - name: Notify parent repository + env: + # ── 需要修改 ────────────────────────────────────────────────────────── + PARENT_REPO: "rcore-os/tgoskits" # 主仓库路径 + # ── 无需修改 ────────────────────────────────────────────────────────── + DISPATCH_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} + # 将用户可控内容通过 env 传入,避免直接插值到 shell 脚本 + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + GIT_ACTOR: ${{ github.actor }} + GIT_SHA: ${{ github.sha }} + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "Notifying parent repository about update in ${COMPONENT}:${BRANCH}" + + # 使用 jq 安全构建 JSON,避免 commit message 中任何特殊字符导致注入 + PAYLOAD=$(jq -n \ + --arg component "$COMPONENT" \ + --arg branch "$BRANCH" \ + --arg repo_url "$REPO_URL" \ + --arg commit "$GIT_SHA" \ + --arg message "$COMMIT_MESSAGE" \ + --arg author "$GIT_ACTOR" \ + '{ + event_type: "subtree-update", + client_payload: { + component: $component, + branch: $branch, + repo_url: $repo_url, + commit: $commit, + message: $message, + author: $author + } + }') + + curl --fail --show-error -X POST \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${DISPATCH_TOKEN}" \ + https://api.github.com/repos/${PARENT_REPO}/dispatches \ + -d "$PAYLOAD" + + echo "Notification sent successfully" + + - name: Create summary + env: + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + GIT_SHA: ${{ github.sha }} + GIT_ACTOR: ${{ github.actor }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "## Notification Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Component**: ${COMPONENT}" >> $GITHUB_STEP_SUMMARY + echo "- **Branch**: ${BRANCH}" >> $GITHUB_STEP_SUMMARY + echo "- **Repo URL**: ${REPO_URL}" >> $GITHUB_STEP_SUMMARY + echo "- **Commit**: \`${GIT_SHA}\`" >> $GITHUB_STEP_SUMMARY + echo "- **Author**: ${GIT_ACTOR}" >> $GITHUB_STEP_SUMMARY + echo "- **Status**: ✅ Notification sent" >> $GITHUB_STEP_SUMMARY diff --git a/components/axvcpu/Cargo.toml b/components/axvcpu/Cargo.toml index ecc5038bb..7d8885324 100644 --- a/components/axvcpu/Cargo.toml +++ b/components/axvcpu/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "axvcpu" authors = ["aarkegz "] -version = "0.2.2" +version = "0.3.0" edition = "2024" categories = ["virtualization", "no-std"] description = "Virtual CPU abstraction for ArceOS hypervisor" @@ -10,8 +10,8 @@ keywords = ["vcpu", "hypervisor", "arceos"] license = "Apache-2.0" [dependencies] -axerrno = "0.1.0" +axerrno = "0.2" memory_addr = "0.4" percpu = "0.2.3-preview.1" -axaddrspace = "0.1.4" -axvisor_api = "0.1" +axaddrspace = "0.3" +axvisor_api = "0.3" diff --git a/components/axvcpu/src/hal.rs b/components/axvcpu/src/hal.rs deleted file mode 100644 index 822614997..000000000 --- a/components/axvcpu/src/hal.rs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2025 The Axvisor Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/// Hardware abstraction layer interfaces for VCpu operations. -/// -/// This trait defines the interfaces that the underlying software (kernel or hypervisor) -/// must implement to support VCpu operations such as interrupt handling and memory management. -pub trait AxVCpuHal { - /// Memory management interfaces required by the VCpu subsystem. - /// Must implement the AxMmHal trait from the axaddrspace crate. - type MmHal: axaddrspace::AxMmHal; - - /// Fetches the current interrupt (IRQ) number from the hardware. - fn irq_fetch() -> usize { - 0 - } - - /// Dispatches an interrupt request (IRQ) to the underlying host OS. - /// - /// This function should handle the actual interrupt processing and delegation - /// to the appropriate interrupt handler in the host system. - /// - /// # Implementation Required - /// - /// The default implementation panics as this function **must** be implemented - /// by the underlying hypervisor or kernel to provide proper interrupt handling. - /// - /// # Safety - /// - /// Implementations should ensure proper interrupt handling and avoid - /// reentrancy issues during interrupt processing. - fn irq_hanlder() { - unimplemented!("irq_handler is not implemented"); - } -} diff --git a/components/axvcpu/src/lib.rs b/components/axvcpu/src/lib.rs index 38282ac27..cddca862f 100644 --- a/components/axvcpu/src/lib.rs +++ b/components/axvcpu/src/lib.rs @@ -35,7 +35,6 @@ extern crate alloc; // Core modules mod arch_vcpu; // Architecture-specific VCpu trait definition mod exit; // VM exit reason enumeration and handling -mod hal; // Hardware abstraction layer interfaces mod percpu; // Per-CPU virtualization state management mod test; // Unit tests for VCpu functionality mod vcpu; // Main VCpu implementation and state management @@ -43,6 +42,5 @@ mod vcpu; // Main VCpu implementation and state management // Public API exports pub use arch_vcpu::AxArchVCpu; // Architecture-specific VCpu trait pub use exit::AxVCpuExitReason; -pub use hal::AxVCpuHal; // Hardware abstraction layer trait pub use percpu::*; // Per-CPU state management types pub use vcpu::*; // Main VCpu types and functions // VM exit reasons diff --git a/components/axvisor_api/.github/config.json b/components/axvisor_api/.github/config.json new file mode 100644 index 000000000..f347e101e --- /dev/null +++ b/components/axvisor_api/.github/config.json @@ -0,0 +1,13 @@ +{ + "targets": [ + "aarch64-unknown-none-softfloat", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-none", + "riscv64gc-unknown-none-elf" + ], + "rust_components": [ + "rust-src", + "clippy", + "rustfmt" + ] +} diff --git a/components/axvisor_api/.github/workflows/check.yml b/components/axvisor_api/.github/workflows/check.yml new file mode 100644 index 000000000..330fa15e9 --- /dev/null +++ b/components/axvisor_api/.github/workflows/check.yml @@ -0,0 +1,66 @@ +name: Quality Checks + +on: + push: + branches: + - '**' + tags-ignore: + - '**' + pull_request: + workflow_call: + +jobs: + load-config: + name: Load CI Configuration + runs-on: ubuntu-latest + outputs: + targets: ${{ steps.config.outputs.targets }} + rust_components: ${{ steps.config.outputs.rust_components }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Load configuration + id: config + run: | + TARGETS=$(jq -c '.targets' .github/config.json) + COMPONENTS=$(jq -r '.rust_components | join(", ")' .github/config.json) + + echo "targets=$TARGETS" >> $GITHUB_OUTPUT + echo "rust_components=$COMPONENTS" >> $GITHUB_OUTPUT + + check: + name: Check + runs-on: ubuntu-latest + needs: load-config + strategy: + fail-fast: false + matrix: + target: ${{ fromJson(needs.load-config.outputs.targets) }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@nightly + with: + components: ${{ needs.load-config.outputs.rust_components }} + targets: ${{ matrix.target }} + + - name: Check rust version + run: rustc --version --verbose + + - name: Check code format + run: cargo fmt --all -- --check + + - name: Build + run: cargo build --target ${{ matrix.target }} --all-features + + - name: Run clippy + run: cargo clippy --target ${{ matrix.target }} --all-features -- -D warnings + + - name: Build documentation + env: + RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs + run: cargo doc --no-deps --target ${{ matrix.target }} --all-features diff --git a/components/axvisor_api/.github/workflows/ci.yml b/components/axvisor_api/.github/workflows/ci.yml deleted file mode 100644 index 5d66dd55f..000000000 --- a/components/axvisor_api/.github/workflows/ci.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: CI - -on: [push, pull_request] - -jobs: - ci: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - rust-toolchain: [nightly-2025-05-20, nightly] - targets: [x86_64-unknown-linux-gnu, x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none-softfloat] - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - with: - toolchain: ${{ matrix.rust-toolchain }} - components: rust-src, clippy, rustfmt - targets: ${{ matrix.targets }} - - name: Check rust version - run: rustc --version --verbose - - name: Check code format - run: cargo fmt --all -- --check - - name: Clippy - run: cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default - - name: Build - run: cargo build --target ${{ matrix.targets }} --all-features - - name: Unit test - if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }} - run: cargo test --target ${{ matrix.targets }} -- --nocapture - - doc: - runs-on: ubuntu-latest - strategy: - fail-fast: false - permissions: - contents: write - env: - default-branch: ${{ format('refs/heads/{0}', github.event.repository.default_branch) }} - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - with: - toolchain: nightly-2025-05-20 - - name: Build docs - continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }} - run: | - cargo doc --no-deps --all-features - printf '' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html - - name: Deploy to Github Pages - if: ${{ github.ref == env.default-branch }} - uses: JamesIves/github-pages-deploy-action@v4 - with: - single-commit: true - branch: gh-pages - folder: target/doc diff --git a/components/axvisor_api/.github/workflows/deploy.yml b/components/axvisor_api/.github/workflows/deploy.yml new file mode 100644 index 000000000..882ba9310 --- /dev/null +++ b/components/axvisor_api/.github/workflows/deploy.yml @@ -0,0 +1,109 @@ +name: Deploy + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: 'pages' + cancel-in-progress: false + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 + +jobs: + verify-tag: + name: Verify Tag + runs-on: ubuntu-latest + outputs: + should_deploy: ${{ steps.check.outputs.should_deploy }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check if tag is on main or master branch + id: check + run: | + git fetch origin main master || true + BRANCHES=$(git branch -r --contains ${{ github.ref }}) + + if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then + echo "✓ Tag is on main or master branch" + echo "should_deploy=true" >> $GITHUB_OUTPUT + else + echo "✗ Tag is not on main or master branch, skipping deployment" + echo "Tag is on: $BRANCHES" + echo "should_deploy=false" >> $GITHUB_OUTPUT + fi + + - name: Verify version consistency + if: steps.check.outputs.should_deploy == 'true' + run: | + # Extract version from git tag (remove 'v' prefix) + TAG_VERSION="${{ github.ref_name }}" + TAG_VERSION="${TAG_VERSION#v}" + # Extract version from Cargo.toml + CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') + echo "Git tag version: $TAG_VERSION" + echo "Cargo.toml version: $CARGO_VERSION" + if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then + echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" + exit 1 + fi + echo "✓ Version check passed!" + + check: + uses: ./.github/workflows/check.yml + needs: verify-tag + if: needs.verify-tag.outputs.should_deploy == 'true' + + test: + uses: ./.github/workflows/test.yml + needs: verify-tag + if: needs.verify-tag.outputs.should_deploy == 'true' + + build: + name: Build documentation + runs-on: ubuntu-latest + needs: [verify-tag, check, test] + if: needs.verify-tag.outputs.should_deploy == 'true' + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@nightly + + - name: Build docs + env: + RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs + run: | + cargo doc --no-deps --all-features + printf '' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: target/doc + + deploy: + name: Deploy to GitHub Pages + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: [verify-tag, build] + if: needs.verify-tag.outputs.should_deploy == 'true' + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/components/axvisor_api/.github/workflows/push.yml b/components/axvisor_api/.github/workflows/push.yml new file mode 100644 index 000000000..a18de4a04 --- /dev/null +++ b/components/axvisor_api/.github/workflows/push.yml @@ -0,0 +1,147 @@ +# ═══════════════════════════════════════════════════════════════════════════════ +# 组件仓库 GitHub Actions 配置模板 +# ═══════════════════════════════════════════════════════════════════════════════ +# +# 此文件用于子仓库,当子仓库有更新时通知主仓库进行 subtree pull 同步。 +# +# 【使用步骤】 +# ───────────────────────────────────────────────────────────────────────────── +# 1. 将此文件复制到子仓库的 .github/workflows/ 目录: +# cp scripts/push.yml <子仓库>/.github/workflows/push.yml +# +# 2. 在子仓库中配置 Secret: +# GitHub 仓库 → Settings → Secrets → Actions → New repository secret +# 名称: PARENT_REPO_TOKEN +# 值: 具有主仓库 repo 权限的 Personal Access Token +# +# 3. 修改下方 env 块中的一个变量(标注了「需要修改」的行): +# PARENT_REPO - 主仓库路径,例如 rcore-os/tgoskits +# (subtree 目录由主仓库自动从 git 历史中推断,无需手动指定) +# +# 【Token 权限要求】 +# ───────────────────────────────────────────────────────────────────────────── +# PARENT_REPO_TOKEN 需要 Classic Personal Access Token,权限包括: +# - repo (Full control of private repositories) +# 或 +# - Fine-grained token: Contents (Read and Write) +# +# 【触发条件】 +# ───────────────────────────────────────────────────────────────────────────── +# - 自动触发:推送到 dev 或 main 分支时 +# - 手动触发:Actions → Notify Parent Repository → Run workflow +# +# 【工作流程】 +# ───────────────────────────────────────────────────────────────────────────── +# 子仓库 push → 触发此工作流 → 调用主仓库 API → 主仓库 subtree pull +# +# 【注意事项】 +# ───────────────────────────────────────────────────────────────────────────── +# - 主仓库需要配置接收 repository_dispatch 事件的同步工作流 +# - 如果不需要子仓库到主仓库的同步,可以不使用此文件 +# +# ═══════════════════════════════════════════════════════════════════════════════ + +name: Notify Parent Repository + +# 当有新的推送时触发 +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Get repository info + id: repo + env: + GH_REPO_NAME: ${{ github.event.repository.name }} + GH_REF_NAME: ${{ github.ref_name }} + GH_SERVER_URL: ${{ github.server_url }} + GH_REPOSITORY: ${{ github.repository }} + run: | + # 直接使用 GitHub Actions 内置变量,通过 env 传入避免 shell 注入 + COMPONENT="$GH_REPO_NAME" + BRANCH="$GH_REF_NAME" + # 构造标准 HTTPS URL,供主仓库按 URL 精确匹配 repos.list + REPO_URL="${GH_SERVER_URL}/${GH_REPOSITORY}" + + echo "component=${COMPONENT}" >> $GITHUB_OUTPUT + echo "branch=${BRANCH}" >> $GITHUB_OUTPUT + echo "repo_url=${REPO_URL}" >> $GITHUB_OUTPUT + + echo "Component: ${COMPONENT}" + echo "Branch: ${BRANCH}" + echo "Repo URL: ${REPO_URL}" + + - name: Notify parent repository + env: + # ── 需要修改 ────────────────────────────────────────────────────────── + PARENT_REPO: "rcore-os/tgoskits" # 主仓库路径 + # ── 无需修改 ────────────────────────────────────────────────────────── + DISPATCH_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} + # 将用户可控内容通过 env 传入,避免直接插值到 shell 脚本 + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + GIT_ACTOR: ${{ github.actor }} + GIT_SHA: ${{ github.sha }} + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "Notifying parent repository about update in ${COMPONENT}:${BRANCH}" + + # 使用 jq 安全构建 JSON,避免 commit message 中任何特殊字符导致注入 + PAYLOAD=$(jq -n \ + --arg component "$COMPONENT" \ + --arg branch "$BRANCH" \ + --arg repo_url "$REPO_URL" \ + --arg commit "$GIT_SHA" \ + --arg message "$COMMIT_MESSAGE" \ + --arg author "$GIT_ACTOR" \ + '{ + event_type: "subtree-update", + client_payload: { + component: $component, + branch: $branch, + repo_url: $repo_url, + commit: $commit, + message: $message, + author: $author + } + }') + + curl --fail --show-error -X POST \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${DISPATCH_TOKEN}" \ + https://api.github.com/repos/${PARENT_REPO}/dispatches \ + -d "$PAYLOAD" + + echo "Notification sent successfully" + + - name: Create summary + env: + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + GIT_SHA: ${{ github.sha }} + GIT_ACTOR: ${{ github.actor }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "## Notification Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Component**: ${COMPONENT}" >> $GITHUB_STEP_SUMMARY + echo "- **Branch**: ${BRANCH}" >> $GITHUB_STEP_SUMMARY + echo "- **Repo URL**: ${REPO_URL}" >> $GITHUB_STEP_SUMMARY + echo "- **Commit**: \`${GIT_SHA}\`" >> $GITHUB_STEP_SUMMARY + echo "- **Author**: ${GIT_ACTOR}" >> $GITHUB_STEP_SUMMARY + echo "- **Status**: ✅ Notification sent" >> $GITHUB_STEP_SUMMARY diff --git a/components/axvisor_api/.github/workflows/release.yml b/components/axvisor_api/.github/workflows/release.yml new file mode 100644 index 000000000..2e857b48b --- /dev/null +++ b/components/axvisor_api/.github/workflows/release.yml @@ -0,0 +1,160 @@ +name: Release + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + - 'v[0-9]+.[0-9]+.[0-9]+-pre.[0-9]+' + +permissions: + contents: write + +jobs: + verify-tag: + name: Verify Tag + runs-on: ubuntu-latest + outputs: + should_release: ${{ steps.check.outputs.should_release }} + is_prerelease: ${{ steps.check.outputs.is_prerelease }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check tag type and branch + id: check + run: | + git fetch origin main master dev || true + + TAG="${{ github.ref_name }}" + BRANCHES=$(git branch -r --contains ${{ github.ref }}) + + echo "Tag: $TAG" + echo "Branches containing this tag: $BRANCHES" + + # Check if it's a prerelease tag + if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-pre\.[0-9]+$ ]]; then + echo "📦 Detected prerelease tag" + echo "is_prerelease=true" >> $GITHUB_OUTPUT + + if echo "$BRANCHES" | grep -q 'origin/dev'; then + echo "✓ Prerelease tag is on dev branch" + echo "should_release=true" >> $GITHUB_OUTPUT + else + echo "✗ Prerelease tag must be on dev branch, skipping release" + echo "should_release=false" >> $GITHUB_OUTPUT + fi + elif [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "📦 Detected stable release tag" + echo "is_prerelease=false" >> $GITHUB_OUTPUT + + if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then + echo "✓ Stable release tag is on main or master branch" + echo "should_release=true" >> $GITHUB_OUTPUT + else + echo "✗ Stable release tag must be on main or master branch, skipping release" + echo "should_release=false" >> $GITHUB_OUTPUT + fi + else + echo "✗ Unknown tag format, skipping release" + echo "is_prerelease=false" >> $GITHUB_OUTPUT + echo "should_release=false" >> $GITHUB_OUTPUT + fi + + - name: Verify version consistency + if: steps.check.outputs.should_release == 'true' + run: | + # Extract version from git tag (remove 'v' prefix) + TAG_VERSION="${{ github.ref_name }}" + TAG_VERSION="${TAG_VERSION#v}" + # Extract version from Cargo.toml + CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') + echo "Git tag version: $TAG_VERSION" + echo "Cargo.toml version: $CARGO_VERSION" + if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then + echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" + exit 1 + fi + echo "✓ Version check passed!" + + check: + uses: ./.github/workflows/check.yml + needs: verify-tag + if: needs.verify-tag.outputs.should_release == 'true' + + test: + uses: ./.github/workflows/test.yml + needs: [verify-tag, check] + if: needs.verify-tag.outputs.should_release == 'true' + + release: + name: Create GitHub Release + runs-on: ubuntu-latest + needs: [verify-tag, check] + if: needs.verify-tag.outputs.should_release == 'true' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Generate release notes + id: release_notes + run: | + CURRENT_TAG="${{ github.ref_name }}" + + # Get previous tag + PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -A1 "^${CURRENT_TAG}$" | tail -n1) + + if [ -z "$PREVIOUS_TAG" ] || [ "$PREVIOUS_TAG" == "$CURRENT_TAG" ]; then + echo "No previous tag found, this is the first release" + CHANGELOG="Initial release" + else + echo "Generating changelog from $PREVIOUS_TAG to $CURRENT_TAG" + + # Generate changelog with commit messages + CHANGELOG=$(git log --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${CURRENT_TAG}") + + if [ -z "$CHANGELOG" ]; then + CHANGELOG="No changes" + fi + fi + + # Write changelog to output file (multi-line) + { + echo "changelog<> $GITHUB_OUTPUT + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + draft: false + prerelease: ${{ needs.verify-tag.outputs.is_prerelease == 'true' }} + body: | + ## Changes + ${{ steps.release_notes.outputs.changelog }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + publish: + name: Publish to crates.io + runs-on: ubuntu-latest + needs: [verify-tag, check] + if: needs.verify-tag.outputs.should_release == 'true' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@nightly + + - name: Dry run publish + run: cargo publish --dry-run + + - name: Publish to crates.io + run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/components/axvisor_api/.github/workflows/test.yml b/components/axvisor_api/.github/workflows/test.yml new file mode 100644 index 000000000..dc3b293d9 --- /dev/null +++ b/components/axvisor_api/.github/workflows/test.yml @@ -0,0 +1,50 @@ +name: Test + +on: + push: + branches: + - '**' + tags-ignore: + - '**' + pull_request: + workflow_call: + +jobs: + load-config: + name: Load CI Configuration + runs-on: ubuntu-latest + outputs: + targets: ${{ steps.config.outputs.targets }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Load configuration + id: config + run: | + TARGETS=$(jq -c '.targets' .github/config.json) + echo "targets=$TARGETS" >> $GITHUB_OUTPUT + + test: + name: Test + runs-on: ubuntu-latest + needs: load-config + strategy: + fail-fast: false + matrix: + target: ${{ fromJson(needs.load-config.outputs.targets) }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@nightly + + # - name: Run tests + # run: cargo test --target ${{ matrix.target }} --all-features -- --nocapture + + # - name: Run doc tests + # run: cargo test --target ${{ matrix.target }} --doc + - name: Run tests + run: echo "Tests are skipped!" diff --git a/components/axvisor_api/CHANGELOG.md b/components/axvisor_api/CHANGELOG.md new file mode 100644 index 000000000..c6e8c0022 --- /dev/null +++ b/components/axvisor_api/CHANGELOG.md @@ -0,0 +1,38 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.2.0] - 2026-01-24 + +### Added + +- Added `api_def` and `api_impl` procedural macros for defining and implementing APIs. +- Added `arch` module with architecture-specific APIs for AArch64 GIC operations. +- Added `host` module with host system APIs. +- Added `memory` module with memory allocation and address translation APIs. +- Added `time` module with time and timer APIs. +- Added `vmm` module with virtual machine management APIs. +- Added `PhysFrame` type alias for automatic frame deallocation. +- Added comprehensive documentation and examples in crate-level docs. +- Added docs.rs configuration for multi-target documentation. + +### Changed + +- Improved Cargo.toml with complete metadata fields. +- Enhanced CI configuration with documentation checks. + +## [0.1.0] - Initial Release + +### Added + +- Initial implementation of the axvisor_api crate. +- Basic API definition and implementation framework using `crate_interface`. + +[Unreleased]: https://github.com/arceos-hypervisor/axvisor_api/compare/v0.2.0...HEAD +[0.2.0]: https://github.com/arceos-hypervisor/axvisor_api/compare/v0.1.0...v0.2.0 +[0.1.0]: https://github.com/arceos-hypervisor/axvisor_api/releases/tag/v0.1.0 diff --git a/components/axvisor_api/Cargo.toml b/components/axvisor_api/Cargo.toml index a30c70c70..6d09f46de 100644 --- a/components/axvisor_api/Cargo.toml +++ b/components/axvisor_api/Cargo.toml @@ -1,8 +1,6 @@ [workspace] resolver = "2" -members = [ - "axvisor_api_proc", -] +members = ["axvisor_api_proc"] [workspace.package] authors = ["Su Mingxian "] @@ -12,9 +10,11 @@ repository = "https://github.com/arceos-hypervisor/axvisor_api" [package] name = "axvisor_api" +version = "0.3.0" description = "Basic API for components of the Hypervisor on ArceOS" -version = "0.1.0" -keywords = ["axvisor", "api", "embedded"] +documentation = "https://docs.rs/axvisor_api" +readme = "README.md" +keywords = ["axvisor", "api", "embedded", "hypervisor"] categories = ["embedded", "no-std"] authors.workspace = true edition.workspace = true @@ -22,8 +22,27 @@ license.workspace = true repository.workspace = true [dependencies] -axvisor_api_proc = { path = "axvisor_api_proc", version = "0.1.0"} +# === Core Components === +# Procedural macro definitions +axvisor_api_proc = { path = "axvisor_api_proc", version = "0.3.0"} -crate_interface = "0.1" +# === Third-party Libraries === +# Address space abstraction +axaddrspace = "0.3" +# Interface definition tools +crate_interface = "0.3" +# Physical/virtual address types memory_addr = "0.4" -axaddrspace = "0.1.0" +# CPU mask +cpumask = "0.1.0" + + +[package.metadata.docs.rs] +all-features = true +default-target = "x86_64-unknown-linux-gnu" +targets = [ + "x86_64-unknown-linux-gnu", + "x86_64-unknown-none", + "riscv64gc-unknown-none-elf", + "aarch64-unknown-none-softfloat", +] diff --git a/components/axvisor_api/LICENSE.Apache2 b/components/axvisor_api/LICENSE similarity index 99% rename from components/axvisor_api/LICENSE.Apache2 rename to components/axvisor_api/LICENSE index 261eeb9e9..b1a313f58 100644 --- a/components/axvisor_api/LICENSE.Apache2 +++ b/components/axvisor_api/LICENSE @@ -48,7 +48,7 @@ "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner + submitted to the Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent diff --git a/components/axvisor_api/LICENSE.MulanPSL2 b/components/axvisor_api/LICENSE.MulanPSL2 deleted file mode 100644 index 5729580d9..000000000 --- a/components/axvisor_api/LICENSE.MulanPSL2 +++ /dev/null @@ -1,127 +0,0 @@ - 木兰宽松许可证, 第2版 - - 木兰宽松许可证, 第2版 - 2020年1月 http://license.coscl.org.cn/MulanPSL2 - - - 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: - - 0. 定义 - - “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 - - “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 - - “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 - - “法人实体”是指提交贡献的机构及其“关联实体”。 - - “关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 - - 1. 授予版权许可 - - 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 - - 2. 授予专利许可 - - 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 - - 3. 无商标许可 - - “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 - - 4. 分发限制 - - 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 - - 5. 免责声明与责任限制 - - “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 - - 6. 语言 - “本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。 - - 条款结束 - - 如何将木兰宽松许可证,第2版,应用到您的软件 - - 如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: - - 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; - - 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; - - 3, 请将如下声明文本放入每个源文件的头部注释中。 - - Copyright (c) [Year] [name of copyright holder] - [Software Name] is licensed under Mulan PSL v2. - You can use this software according to the terms and conditions of the Mulan PSL v2. - You may obtain a copy of Mulan PSL v2 at: - http://license.coscl.org.cn/MulanPSL2 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - See the Mulan PSL v2 for more details. - - - Mulan Permissive Software License,Version 2 - - Mulan Permissive Software License,Version 2 (Mulan PSL v2) - January 2020 http://license.coscl.org.cn/MulanPSL2 - - Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions: - - 0. Definition - - Software means the program and related documents which are licensed under this License and comprise all Contribution(s). - - Contribution means the copyrightable work licensed by a particular Contributor under this License. - - Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. - - Legal Entity means the entity making a Contribution and all its Affiliates. - - Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. - - 1. Grant of Copyright License - - Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. - - 2. Grant of Patent License - - Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. - - 3. No Trademark License - - No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4. - - 4. Distribution Restriction - - You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. - - 5. Disclaimer of Warranty and Limitation of Liability - - THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - - 6. Language - - THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. - - END OF THE TERMS AND CONDITIONS - - How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software - - To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps: - - i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; - - ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; - - iii Attach the statement to the appropriate annotated syntax at the beginning of each source file. - - - Copyright (c) [Year] [name of copyright holder] - [Software Name] is licensed under Mulan PSL v2. - You can use this software according to the terms and conditions of the Mulan PSL v2. - You may obtain a copy of Mulan PSL v2 at: - http://license.coscl.org.cn/MulanPSL2 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - See the Mulan PSL v2 for more details. diff --git a/components/axvisor_api/LICENSE.MulanPubL2 b/components/axvisor_api/LICENSE.MulanPubL2 deleted file mode 100644 index 2fcd23f58..000000000 --- a/components/axvisor_api/LICENSE.MulanPubL2 +++ /dev/null @@ -1,183 +0,0 @@ - 木兰公共许可证, 第2版 - -木兰公共许可证, 第2版 - -2021年5月 http://license.coscl.org.cn/MulanPubL-2.0 - -您对“贡献”的复制、使用、修改及分发受木兰公共许可证,第2版(以下简称“本许可证”)的如下条款的约束: - -0. 定义 - -“贡献” 是指由“贡献者”许可在“本许可证”下的受版权法保护的作品,包括最初“贡献者”许可在“本许可证”下的作品及后续“贡献者”许可在“本许可证”下的“衍生作品”。 - -“贡献者” 是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 - -“法人实体” 是指提交贡献的机构及其“关联实体”。 - -“关联实体” 是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的“控制”是指拥有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 - -“衍生作品” 是指基于“贡献”创作的作品,具体包括对全部或部分“贡献”进行修改、重写、翻译、注释、组合或与之链接(包括动态链接或静态链接)而形成的作品。仅与“贡献”进行进程间通信或系统调用的作品是独立作品,不属于“衍生作品”。 - -“对应源代码” 是指生成、安装和(对于可执行作品)运行目标代码所需的所有源文件和与之关联的接口定义文件,以及控制这些活动的脚本,但不包括编译环境、编译工具、云服务平台(如果有)。 - -“分发” 是指通过任何媒介向他人提供“贡献”或“衍生作品”的行为,以及利用“贡献”或“衍生作品”通过网络远程给用户提供服务的行为,例如:通过利用“贡献”或“衍生作品”搭建的云服务平台提供在线服务的行为。 - -1. 授予版权许可 - -每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、“分发”其“贡献”或“衍生作品”,不论修改与否。 - -2. 授予专利许可 - -每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销的情形除外)专利许可,供您使用、制造、委托制造、销售、许诺销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”中的专利权利要求,而不包括仅因您对“贡献”的修改而将必然会侵犯到的专利权利要求。如果您或您的“关联实体”直接或间接地,就“贡献”对任何人发起专利侵权诉讼(包括在诉讼中提出反诉请求或交叉请求)或发起其他专利维权行动,则“贡献者”根据“本许可证”授予您的专利许可自您发起专利诉讼或专利维权行动之日终止。 - -3. 无商标许可 - -“贡献者”在“本许可证”下不提供对其商品名称、商标、服务标识或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用的情形除外。 - -4. 分发限制 - -您可以将您接收到的“贡献”或您的“衍生作品”以源程序形式或可执行形式重新“分发”,但必须满足下列条件: - -(1)您必须向接收者提供“本许可证”的副本,并保留“贡献”中的版权、商标、专利及免责声明;并且, - -(2)如果您“分发”您接收到的“贡献”,您必须使用“本许可证”提供该“贡献”的源代码副本;如果您 “分发”您的“衍生作品”,您必须: - -(i)随“衍生作品”提供使用“本许可证”“分发”的您的“衍生作品”的“对应源代码”。如果您通过下载链接提供前述“对应源代码”,则您应将下载链接地址置于“衍生作品”或其随附文档中的明显位置,有效期自该“衍生作品”“分发”之日起不少于三年,并确保接收者可以获得“对应源代码”;或者, - -(ii)随“衍生作品”向接收者提供一个书面要约,表明您愿意提供根据“本许可证”“分发”的您“衍生作品”的“对应源代码”。该书面要约应置于“衍生作品”中的明显位置,并确保接收者根据书面要约可获取“对应源代码”的时间从您接到该请求之日起不得超过三个月,且有效期自该“衍生作品”“分发”之日起不少于三年。 - -5. 违约与终止 - -如果您违反“本许可证”,任何“贡献者”有权书面通知您终止其根据“本许可证”授予您的许可。该“贡献者”授予您的许可自您接到其终止通知之日起终止。仅在如下两种情形下,即使您收到“贡献者”的通知也并不终止其授予您的许可: - -(1)您在接到该终止通知之前已停止所有违反行为; - -(2)您是首次收到该“贡献者”根据“本许可证”发出的书面终止通知,并且您在收到该通知后30天内已停止所有违反行为。 - -只要您下游的接收者遵守“本许可证”的相关规定,即使您在“本许可证”下被授予的许可终止,不影响下游的接收者根据“本许可证”享有的权利。 - -6. 例外 - -如果您将“贡献”与采用GNU AFFERO GENERAL PUBLIC LICENSE Version 3(以下简称“AGPLv3”)或其后续版本的作品结合形成新的“衍生作品”,且根据“AGPLv3”或其后续版本的要求您有义务将新形成的“衍生作品”以“AGPLv3”或其后续版本进行许可的,您可以根据“AGPLv3”或其后续版本进行许可,只要您在“分发”该“衍生作品”的同时向接收者提供“本许可证”的副本,并保留“贡献”中的版权、商标、专利及免责声明。但任何“贡献者”不会因您选择“AGPLv3”或其后续版本而授予该“衍生作品”的接收者更多权利。 - -7. 免责声明与责任限制 - -“贡献”在提供时不带有任何明示或默示的担保。在任何情况下,“贡献者”或版权人不对任何人因使用“贡献”而引发的任何直接或间接损失承担任何责任,不论该等损失因何种原因导致或者基于何种法律理论,即使其曾被告知有该等损失的可能性。 - -8. 语言 - -“本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何不一致,以中文版为准。 - -条款结束 - -如何将木兰公共许可证,第2版,应用到您的软件 - -如果您希望将木兰公共许可证,第2版,应用到您的软件,为了方便接收者查阅,建议您完成如下三步: - -1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; - -2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; - -3, 请将如下声明文本放入每个源文件的头部注释中。 - -Copyright (c) [Year] [name of copyright holder] -[Software Name] is licensed under Mulan PubL v2. -You can use this software according to the terms and conditions of the Mulan PubL v2. -You may obtain a copy of Mulan PubL v2 at: - http://license.coscl.org.cn/MulanPubL-2.0 -THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, -EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, -MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -See the Mulan PubL v2 for more details. - - - Mulan Public License,Version 2 - -Mulan Public License,Version 2 (Mulan PubL v2) - -May 2021 http://license.coscl.org.cn/MulanPubL-2.0 - -Your reproduction, use, modification and Distribution of the Contribution shall be subject to Mulan Public License, Version 2 (this License) with following terms and conditions: - -0. Definition - -Contribution means the copyrightable work licensed by a particular Contributor under this License, including the work licensed by the initial Contributor under this License and its Derivative Work licensed by any subsequent Contributor under this License. - -Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. - -Legal Entity means the entity making a Contribution and all its Affiliates. - -Affiliates mmeans entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. - -Derivative Work means works created based on Contribution, specifically including works formed by modifying, rewriting, translating, annotating, combining or linking to all or part of Contribution (including dynamic linking or static linking). Works which only communicate with Contribution through inter-process communication or system call, are independent works, rather than Derivative Work. - -Corresponding Source Code means all the source code needed to generate, install, and (for an executable work) run the object code including the interface definition files associated with source files for the work, and scripts to control those activities, excluding of compilation environment and compilation tools, cloud services platform (if any). - -Distribute (or Distribution) means the act of making the Contribution or Derivative Work available to others through any medium, and using the Contribution or Derivative Work to provide online services to users, such as the act of providing online services through a cloud service platform built using Contributions or Derivative Works. - -1. Grant of Copyright License - -Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or Distribute its Contribution or Derivative Work, with modification or not. - -2. Grant of Patent License - -Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to use, make, have made, sell, offer for sale, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, excluding of any patent claims solely be infringed by your modification. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that any Contribution infringes patents, then any patent license granted to you under this License for the Contribution shall terminate as of the date such litigation or activity is filed or taken. - -3. No Trademark License - -No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4. - -4. Distribution Restriction - -You may Distribute the Contribution you received or your Derivative Work, whether in source or executable forms, provided that you meet the following conditions: - -1) You must provide recipients with a copy of this License and retain copyright, trademark, patent and disclaimer statements in the Contribution; and, - -2) If you Distribute the Contribution you received, you must provide copies of the Contribution’s source code under this License; - -If you Distribute your Derivative Work, you have to: - -(i) accompanying the Derivative work, provide recipients with Corresponding Source Code of your Derivative Work under this License. If you provide the Corresponding Source Code through a download link, you should place such link address prominently in the Derivative Work or its accompanying documents, and be valid no less than three years from your Distribution of the particular Derivative Work, and ensure that the recipients can acquire the Corresponding Source Code through the link; or, - -(ii) accompanying the Derivative Work, provide recipients with a written offer indicating your willingness to provide the Corresponding Source Code of the Derivative Work licensed under this License. Such written offer shall be placed prominently in the Derivative Work or its accompanying documents. Without reasonable excuse, the recipient shall be able to acquire the Corresponding Source code of the Derivative work for no more than three months from your receipt of a valid request, and be valid no less than three years from your Distribution of the particular Derivative Work. - -5. Breach and Termination - -If you breach this License, any Contributor has the right to notify you in writing to terminate its license granted to you under this License. The license granted to you by such Contributor terminates upon your receipt of such notice of termination. Notwithstanding the foregoing, your license will not be terminated even if you receive a notice of termination from Contributor, provided that: - -1) you have cured all the breaches prior to receiving such notice of termination; or, - -2) it’s your first time to receive a notice of termination from such Contributor pursuant to this License, and you have cured all the breaches within 30 days of receipt of such notice. - -Termination of your license under this License shall not affect the downstream recipient's rights under this License, provided that the downstream recipient complies with this License. - -6. Exceptions - -If you combine Contribution or your Derivative Work with a work licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 (hereinafter referred to as “AGPLv3”) or its subsequent versions, and according to the AGPLv3 or its subsequent versions, you have an obligation to make the combined work to be licensed under the corresponding license, you can license such combined work under the license, provided that when you Distribute the combined work, you also provide a copy of this License to the recipients, and retain copyright, trademarks, patents, and disclaimer statements in the Contribution. No Contributor will grant additional rights to the recipients of the combined work for your license under AGPLv3 or its subsequent versions. - -7. Disclaimer of Warranty and Limitation of liability - -CONTRIBUTION ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE CONTRIBUTION, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -8. Language - -THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. - -END OF THE TERMS AND CONDITIONS - -How to apply the Mulan Public License,Version 2 (Mulan PubL v2), to your software - -To apply the Mulan Public License,Version 2 to your work, for easy identification by recipients, you are suggested to complete following three steps: - -Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; -Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; -Attach the statement to the appropriate annotated syntax at the beginning of each source file. -Copyright (c) [Year] [name of copyright holder] -[Software Name] is licensed under Mulan PubL v2. -You can use this software according to the terms and conditions of the Mulan PubL v2. -You may obtain a copy of Mulan PubL v2 at: - http://license.coscl.org.cn/MulanPubL-2.0 -THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, -EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, -MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -See the Mulan PubL v2 for more details. diff --git a/components/axvisor_api/README.md b/components/axvisor_api/README.md index 05303f124..b0e3256c1 100644 --- a/components/axvisor_api/README.md +++ b/components/axvisor_api/README.md @@ -1,6 +1,9 @@ # axvisor\_api (Experimental Next-Generation Axvisor API) + [![CI](https://github.com/arceos-hypervisor/axvisor_api/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/arceos-hypervisor/axvisor_api/actions/workflows/ci.yml) -[![License](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-blue.svg)] +[![Crates.io](https://img.shields.io/crates/v/axvisor_api)](https://crates.io/crates/axvisor_api) +[![Docs.rs](https://docs.rs/axvisor_api/badge.svg)](https://docs.rs/axvisor_api) +[![License](https://img.shields.io/badge/License-GPL--3.0--or--later%20OR%20Apache--2.0%20OR%20MulanPSL--2.0-blue.svg)](LICENSE.Apache2) \> [中文README](README.zh-cn.md) < @@ -147,3 +150,7 @@ Besides the above drawbacks, the current implementation of `axvisor_api` has som Additionally, there are some platform-related issues that are independent of the `axvisor_api` design or implementation. For example: * **Platform-specific APIs**: Some APIs are strongly tied to specific platforms or even specific devices but are essential. For example, on ARM, the semi-virtualized GIC implementation relies heavily on the physical GIC driver. Including all such functionality in `axvisor_api` makes the modules bloated, but not doing so could hurt readability and maintainability. + +## License + +Axvisor_api is licensed under the Apache License, Version 2.0. See the [LICENSE](./LICENSE) file for details. diff --git a/components/axvisor_api/axvisor_api_proc/Cargo.toml b/components/axvisor_api/axvisor_api_proc/Cargo.toml index e01ae4181..f6e233ff7 100644 --- a/components/axvisor_api/axvisor_api_proc/Cargo.toml +++ b/components/axvisor_api/axvisor_api_proc/Cargo.toml @@ -1,7 +1,9 @@ [package] name = "axvisor_api_proc" -version = "0.1.0" +version = "0.3.0" description = "Procedural macros for the `axvisor_api` crate" +documentation = "https://docs.rs/axvisor_api_proc" +readme = "README.md" keywords = ["axvisor", "api", "embedded", "macros"] categories = ["development-tools::procedural-macro-helpers"] authors.workspace = true @@ -9,12 +11,15 @@ edition.workspace = true license.workspace = true repository.workspace = true - [lib] proc-macro = true [dependencies] -syn = { version = "2.0", features = ["full"] } -quote = "1.0" +# Procedural macro helper utilities proc-macro2 = "1.0" +# Crate name resolution proc-macro-crate = "3.1.0" +# Code generation +quote = "1.0" +# Rust syntax parsing +syn = { version = "2.0", features = ["full"] } diff --git a/components/axvisor_api/axvisor_api_proc/README.md b/components/axvisor_api/axvisor_api_proc/README.md index c783c4042..9cf65c298 100644 --- a/components/axvisor_api/axvisor_api_proc/README.md +++ b/components/axvisor_api/axvisor_api_proc/README.md @@ -2,4 +2,8 @@ **Attribute Macros for Automated API Trait Generation and Cross-Module Binding** -**DO NOT** use this crate directly, use the [axvisor_api] crate instead. \ No newline at end of file +**DO NOT** use this crate directly, use the [axvisor_api] crate instead. + +## License + +Axvisor_api is licensed under the Apache License, Version 2.0. See the [LICENSE](./LICENSE) file for details. diff --git a/components/axvisor_api/axvisor_api_proc/src/items.rs b/components/axvisor_api/axvisor_api_proc/src/items.rs deleted file mode 100644 index 92654cfd8..000000000 --- a/components/axvisor_api/axvisor_api_proc/src/items.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! Definitions of custom items used in the `api_mod!` and `api_mod_impl!` macros. -//! -//! `api_mod!` and `api_mod_impl!` have very similar structures. - -use syn::{ - Attribute, Block, Ident, Item, Signature, Token, Visibility, braced, - parse::{Parse, ParseStream}, - token::Brace, -}; - -/// An API function, defined with the `extern fn` syntax. It represents both the definition and the implementation. For -/// the definition, `T` is `Token![;]`, and for the implementation, `T` is `syn::Block`. -pub struct ItemApiFn { - /// Attributes of the function. - pub attrs: Vec, - #[expect(dead_code)] - /// The `extern` keyword. - pub extern_token: Token![extern], - /// The function signature. - pub sig: Signature, - /// The body of the function, or a semicolon. - pub body: T, -} - -impl Parse for ItemApiFn { - fn parse(input: ParseStream) -> syn::Result { - let attrs = input.call(Attribute::parse_outer)?; - let extern_token = input.parse()?; - let sig = input.parse()?; - let body = input.parse()?; - - Ok(Self { - attrs, - extern_token, - sig, - body, - }) - } -} - -/// An item in a [`ItemApiMod`], which can be a regular [`Item`] or an [API function](`ItemApiFn`). As `ItemApiFn`, this -/// enum represents both the definition and the implementation. -pub enum ApiModItem { - Regular(Item), - ApiFn(ItemApiFn), -} - -impl Parse for ApiModItem { - fn parse(input: ParseStream) -> syn::Result { - // Attributes will be parsed twice, but it's not a big deal. - let forked = input.fork(); - let _ = forked.call(Attribute::parse_outer)?; - let is_api_fn = forked.peek(Token![extern]) && forked.peek2(Token![fn]); - drop(forked); - - if is_api_fn { - Ok(Self::ApiFn(input.parse()?)) - } else { - Ok(Self::Regular(input.parse()?)) - } - } -} - -/// A module that contains the definition or implementation of API functions, and marked by `#[api_mod]` or -/// `#[api_mod_impl]`. -pub struct ItemApiMod { - /// Attributes of the module. - pub attrs: Vec, - /// Visibility of the module. - pub vis: Visibility, - /// The `mod` keyword. - pub mod_token: Token![mod], - /// The identifier of the module. - pub ident: Ident, - #[expect(dead_code)] - /// The brace token. - pub brace_token: Brace, - /// The items in the module. - pub items: Vec>, -} - -impl Parse for ItemApiMod { - fn parse(input: ParseStream) -> syn::Result { - let attrs = input.call(Attribute::parse_outer)?; - let vis = input.parse()?; - let mod_token = input.parse()?; - let ident = input.parse()?; - - let content; - let brace_token = braced!(content in input); - let mut items = vec![]; - - while !content.is_empty() { - items.push(content.parse()?); - } - - Ok(Self { - attrs, - vis, - mod_token, - ident, - brace_token, - items, - }) - } -} - -/// A module that contains the definition of API functions, marked by `#[api_mod]`. -pub type ItemApiModDef = ItemApiMod; -/// A module that contains the implementation of API functions, marked by `#[api_mod_impl]`. -pub type ItemApiModImpl = ItemApiMod; diff --git a/components/axvisor_api/axvisor_api_proc/src/lib.rs b/components/axvisor_api/axvisor_api_proc/src/lib.rs index a538a1006..001bb98f1 100644 --- a/components/axvisor_api/axvisor_api_proc/src/lib.rs +++ b/components/axvisor_api/axvisor_api_proc/src/lib.rs @@ -1,15 +1,72 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Procedural macros for the `axvisor_api` crate. +//! +//! This crate provides the procedural macros used to define and implement +//! AxVisor API interfaces. These macros are built on top of the +//! `crate_interface` crate and provide a convenient way to create +//! link-time-resolved API interfaces. +//! +//! # Macros +//! +//! - [`api_def`] - Define an API interface trait. +//! - [`api_impl`] - Implement an API interface. +//! +//! # Usage +//! +//! This crate is re-exported by `axvisor_api` and should not be used directly. +//! Instead, use the macros through `axvisor_api`: +//! +//! ```rust,ignore +//! use axvisor_api::{api_def, api_impl}; +//! +//! #[api_def] +//! pub trait MyApiIf { +//! fn my_function() -> u32; +//! } +//! +//! struct MyApiImpl; +//! +//! #[api_impl] +//! impl MyApiIf for MyApiImpl { +//! fn my_function() -> u32 { +//! 42 +//! } +//! } +//! ``` +//! +//! # How It Works +//! +//! The macros use `crate_interface` under the hood, which leverages Rust's +//! link-time symbol resolution to connect API definitions with their +//! implementations. This allows for a cleaner API without explicit generic +//! parameters. + use proc_macro::TokenStream as TokenStream1; use proc_macro_crate::{FoundCrate, crate_name}; use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned}; -use syn::{FnArg, Ident, Path, Token, spanned::Spanned}; - -mod items; - -use items::{ApiModItem, ItemApiFn, ItemApiModDef, ItemApiModImpl}; +use syn::{Ident, spanned::Spanned}; /// Find the path to the `axvisor_api` crate. -fn find_axvisor_api_crate() -> TokenStream { +/// +/// This function determines the correct path to use when referring to +/// `axvisor_api` from within the generated code, handling both the case +/// where we're inside the `axvisor_api` crate itself and when we're in +/// an external crate. +fn axvisor_api_crate() -> TokenStream { match crate_name("axvisor_api") { Ok(FoundCrate::Itself) => quote! { crate }, Ok(FoundCrate::Name(name)) => { @@ -20,254 +77,134 @@ fn find_axvisor_api_crate() -> TokenStream { } } -/// Capitalize the first letter of a string. +/// Get the namespace identifier used for AxVisor APIs. /// -/// From: `https://stackoverflow.com/questions/38406793/why-is-capitalizing-the-first-letter-of-a-string-so-convoluted-in-rust` -fn capitalize_first_letter(s: &str) -> String { - let mut c = s.chars(); - match c.next() { - None => String::new(), - Some(f) => f.to_uppercase().collect::() + c.as_str(), - } -} - -/// Get the name of the API trait for an API module. -fn get_api_trait_name(module_name: impl AsRef, span: Span) -> Ident { - let module_name = module_name.as_ref(); - let trait_name = format!("Axvisor{}ApiTrait", capitalize_first_letter(module_name)); - Ident::new(&trait_name, span) -} - -/// Get the extra doc comments for an API module definition. -fn get_api_mod_def_extra_doc_comments( - mod_ident: &Ident, - api_fn_items: &Vec<&ItemApiFn>, -) -> TokenStream { - if api_fn_items.is_empty() { - return quote! { - #[doc = ""] - #[doc = "This module does not contain any API functions to be implemented."] - }; - } - - let mod_name = mod_ident.to_string(); - let api_fn_count = api_fn_items.len(); - let api_fn_count_hint = format!( - "This module contains {} API function{} to be implemented:", - api_fn_count, - if api_fn_count == 1 { "" } else { "s" } - ); - let api_fn_list = api_fn_items - .iter() - .map(|f| format!("- [`{0}`]({1}::{0})", f.sig.ident.to_string(), mod_name)); - - quote! { - #[doc = ""] - #[doc = #api_fn_count_hint] - #( - #[doc = #api_fn_list] - )* - } +/// All AxVisor APIs share a common namespace to avoid conflicts with other +/// uses of `crate_interface`. +fn axvisor_api_namespace() -> Ident { + const AXVISOR_API_NS: &str = "AxVisorApi"; + Ident::new(AXVISOR_API_NS, Span::call_site()) } -fn get_api_fn_def_extra_doc_comments() -> TokenStream { - quote! { - #[doc = ""] - #[doc = "This function is an API function and **should be implemented somewhere**."] - } -} - -/// Process an API module definition. -fn process_api_mod_def(module: ItemApiModDef) -> TokenStream { - let attrs = &module.attrs; - let vis = &module.vis; - let mod_token = &module.mod_token; - let mod_ident = &module.ident; - - // Split the items into regular items and API functions - let mut regular_items = vec![]; - let mut api_fn_items = vec![]; - - for item in &module.items { - match item { - ApiModItem::Regular(item) => regular_items.push(item), - ApiModItem::ApiFn(item) => api_fn_items.push(item), - } - } - - let extra_doc_comments = get_api_mod_def_extra_doc_comments(&mod_ident, &api_fn_items); - - if api_fn_items.is_empty() { - return quote! { - #(#attrs)* - #extra_doc_comments - #vis #mod_token #mod_ident { - #(#regular_items)* - } - }; - } - - let axvisor_api_path = find_axvisor_api_crate(); - - // Generate the API trait - let trait_ident = get_api_trait_name(mod_ident.to_string(), mod_token.span()); - let api_fn_attrs = api_fn_items - .iter() - .map(|item| &item.attrs) - .collect::>(); - let api_fn_signatures = api_fn_items - .iter() - .map(|item| &item.sig) - .collect::>(); - - let trait_def = quote! { - #[doc(hidden)] - #[#axvisor_api_path::__priv::crate_interface::def_interface] - #[allow(non_camel_case_types)] - pub trait #trait_ident { - #(#(#api_fn_attrs)* #api_fn_signatures;)* - } - }; - - // Generate the API function implementations - let mut api_fn_impls = quote! {}; - for api_fn_item in api_fn_items { - let attrs = &api_fn_item.attrs; - let sig = &api_fn_item.sig; - let fn_name = &sig.ident; - let args = &sig - .inputs - .iter() - .map(|arg| match arg { - FnArg::Receiver(_) => panic!("API functions cannot have self arguments"), - FnArg::Typed(pat) => &pat.pat, +/// Macro to assert that an attribute has no arguments. +macro_rules! assert_empty_attr { + ($attr:expr) => { + if !$attr.is_empty() { + return (quote_spanned! { + TokenStream::from($attr).span() => compile_error!("This attribute does not accept any arguments") }) - .collect::>(); - - let extra_doc_comments = get_api_fn_def_extra_doc_comments(); - - api_fn_impls.extend(quote! { - #(#attrs)* - #extra_doc_comments - pub #sig { - #axvisor_api_path::__priv::crate_interface::call_interface!( - #trait_ident::#fn_name, #(#args),* - ) - } - }); - } - - quote! { - #(#attrs)* - #extra_doc_comments - #vis #mod_token #mod_ident { - #(#regular_items)* - - #api_fn_impls - - #trait_def + .into(); } - } -} - -/// Reuses the path to the module to be implemented, to make sure the `impl` block can find the correct trait. -fn get_implementee_reuse_ident(implementee: &Path) -> Ident { - let mut ident = String::from(if implementee.leading_colon.is_some() { - "__axvisor_api_implementee_abs" - } else { - "__axvisor_api_implementee_rel" - }); - - for seg in implementee.segments.iter() { - ident.push('_'); - ident.push_str(seg.ident.to_string().as_str()); - } - - Ident::new(&ident, implementee.span()) -} - -/// Process an API module implementation. -fn process_api_mod_impl(implementee: Path, input: ItemApiModImpl) -> TokenStream { - let attrs = &input.attrs; - let vis = &input.vis; - let mod_token = &input.mod_token; - let mod_ident = &input.ident; - - let implementee_name = match implementee.segments.last() { - Some(segment) => segment.ident.to_string(), - None => return quote! { compile_error!("Invalid implementee path") }, }; - let implementee_trait_ident = get_api_trait_name(&implementee_name, implementee.span()); - // we should reuse the implementee mod path besides the implementing mod, to make sure the `impl` block can find - // the corrent trait. - let implementee_reuse_ident = get_implementee_reuse_ident(&implementee); - - let axvisor_api_path = find_axvisor_api_crate(); - - let mut regular_items = vec![]; - let mut api_fn_items = vec![]; - for item in input.items { - match item { - ApiModItem::Regular(item) => regular_items.push(item), - ApiModItem::ApiFn(item) => api_fn_items.push(item), - } - } +} - let mut api_fn_impls = TokenStream::new(); - for api_fn_item in api_fn_items { - let attrs = &api_fn_item.attrs; - let sig = &api_fn_item.sig; - let body = &api_fn_item.body; +/// Define an AxVisor API interface. +/// +/// This attribute macro is applied to a trait definition to register it as +/// an AxVisor API interface. It generates caller functions for each method +/// in the trait, allowing the API to be called as regular functions. +/// +/// # Usage +/// +/// ```rust,ignore +/// use axvisor_api::api_def; +/// +/// #[api_def] +/// pub trait MyApiIf { +/// /// Get a value. +/// fn get_value() -> u32; +/// +/// /// Set a value. +/// fn set_value(value: u32); +/// } +/// +/// // After the macro expansion, you can call: +/// // my_module::get_value() +/// // my_module::set_value(42) +/// ``` +/// +/// # Generated Code +/// +/// The macro generates: +/// 1. The original trait definition with `crate_interface::def_interface` +/// attribute. +/// 2. Free-standing caller functions for each trait method at the same +/// module level. +/// +/// # Attributes +/// +/// This macro does not accept any arguments. +/// +/// # Implementation +/// +/// This macro uses `crate_interface::def_interface` internally with the +/// `gen_caller` option to generate the caller functions. +#[proc_macro_attribute] +pub fn api_def(attr: TokenStream1, input: TokenStream1) -> TokenStream1 { + assert_empty_attr!(attr); - api_fn_impls.extend(quote! { - #(#attrs)* - #sig #body - }); - } + let axvisor_api_path = axvisor_api_crate(); + let ns = axvisor_api_namespace(); + let input: TokenStream = syn::parse_macro_input!(input as TokenStream); quote! { - #[doc(hidden)] - use #implementee as #implementee_reuse_ident; - - #(#attrs)* - #vis #mod_token #mod_ident { - #(#regular_items)* - - #[doc(hidden)] - pub struct __Impl; - #[#axvisor_api_path::__priv::crate_interface::impl_interface] - impl super::#implementee_reuse_ident::#implementee_trait_ident for __Impl { - #api_fn_impls - } - } + #[#axvisor_api_path::__priv::crate_interface::def_interface(gen_caller, namespace = #ns)] + #input } + .into() } -#[proc_macro_attribute] -/// Define a module containing API functions. +/// Implement an AxVisor API interface. /// -/// The module can contain regular items and API functions. API functions are defined with the `extern fn` syntax. +/// This attribute macro is applied to an `impl` block that implements a +/// trait previously defined with [`api_def`]. It registers the implementation +/// so that calls to the API functions are resolved to this implementation +/// at link time. /// -/// **Does not work on outlined modules.** (i.e. `mod foo;` with content in `foo.rs`) -pub fn api_mod(attr: TokenStream1, input: TokenStream1) -> TokenStream1 { - if !attr.is_empty() { - return (quote_spanned! { - TokenStream::from(attr).span() => compile_error!("`api_mod` attribute does not accept any arguments") - }).into(); - } +/// # Usage +/// +/// ```rust,ignore +/// use axvisor_api::{api_def, api_impl}; +/// +/// #[api_def] +/// pub trait MyApiIf { +/// fn get_value() -> u32; +/// } +/// +/// struct MyApiImpl; +/// +/// #[api_impl] +/// impl MyApiIf for MyApiImpl { +/// fn get_value() -> u32 { +/// 42 +/// } +/// } +/// ``` +/// +/// # Requirements +/// +/// - The implemented trait must have been defined with [`api_def`]. +/// - The implementing type should be an empty struct (marker type). +/// - Only one implementation per API trait is allowed in the final binary. +/// +/// # Attributes +/// +/// This macro does not accept any arguments. +/// +/// # Implementation +/// +/// This macro uses `crate_interface::impl_interface` internally. +#[proc_macro_attribute] +pub fn api_impl(attr: TokenStream1, input: TokenStream1) -> TokenStream1 { + assert_empty_attr!(attr); - process_api_mod_def(syn::parse_macro_input!(input as ItemApiModDef)).into() -} + let axvisor_api_path = axvisor_api_crate(); + let ns = axvisor_api_namespace(); + let input: TokenStream = syn::parse_macro_input!(input as TokenStream); -#[proc_macro_attribute] -/// Implement the API functions defined in another module. -/// -/// The module should contain the implementation of the API functions defined in another module. The path to the module -/// defining the APIs should be passed as the argument. -pub fn api_mod_impl(attr: TokenStream1, input: TokenStream1) -> TokenStream1 { - process_api_mod_impl( - syn::parse_macro_input!(attr as Path), - syn::parse_macro_input!(input as ItemApiModImpl), - ) + quote! { + #[#axvisor_api_path::__priv::crate_interface::impl_interface(namespace = #ns)] + #input + } .into() } diff --git a/components/axvisor_api/examples/example.rs b/components/axvisor_api/examples/example.rs index bc6a356f5..25b5db3f6 100644 --- a/components/axvisor_api/examples/example.rs +++ b/components/axvisor_api/examples/example.rs @@ -1,31 +1,57 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + extern crate axvisor_api; extern crate memory_addr; -use axvisor_api::{__priv, api_mod, api_mod_impl}; +use axvisor_api::__priv; -#[api_mod] -/// Memory-related API pub mod some_demo { + use memory_addr::MemoryAddr; pub use memory_addr::PhysAddr; - /// Some function - extern fn some_func() -> PhysAddr; - /// Another function - extern fn another_func(addr: PhysAddr); + #[axvisor_api::api_def] + pub trait SomeDemoIf { + /// Some function provided by the implementer + fn some_func() -> PhysAddr; + /// Another function provided by the implementer + fn another_func(addr: PhysAddr); + } + + /// Some function provided by the API definer + pub fn provided_func() -> PhysAddr { + some_func().add(0x1000) + } } -#[api_mod_impl(some_demo)] -mod some_impl { - use memory_addr::{PhysAddr, pa}; +mod some_demo_impl { + use crate::some_demo::SomeDemoIf; - extern fn some_func() -> PhysAddr { - return pa!(0x42); - } + pub struct SomeDemoImpl; + + #[axvisor_api::api_impl] + impl SomeDemoIf for SomeDemoImpl { + fn some_func() -> memory_addr::PhysAddr { + memory_addr::pa!(0x42) + } - extern fn another_func(addr: PhysAddr) { - println!("Wow, the answer is {:?}", addr); + fn another_func(addr: memory_addr::PhysAddr) { + println!("Wow, the answer is {:?}", addr); + } } } fn main() { some_demo::another_func(some_demo::some_func()); + some_demo::another_func(some_demo::provided_func()); } diff --git a/components/axvisor_api/rust-toolchain.toml b/components/axvisor_api/rust-toolchain.toml index d3d6b8265..4a8cf3803 100644 --- a/components/axvisor_api/rust-toolchain.toml +++ b/components/axvisor_api/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] profile = "minimal" -channel = "nightly-2025-05-20" +channel = "nightly-2026-02-25" components = ["rust-src", "llvm-tools", "rustfmt", "clippy"] targets = [ "x86_64-unknown-none", diff --git a/components/axvisor_api/src/arch.rs b/components/axvisor_api/src/arch.rs new file mode 100644 index 000000000..977f63952 --- /dev/null +++ b/components/axvisor_api/src/arch.rs @@ -0,0 +1,148 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Architecture-specific APIs for the AxVisor hypervisor. +//! +//! This module provides APIs that are specific to certain CPU architectures. +//! Currently, it mainly contains AArch64 GIC (Generic Interrupt Controller) +//! related operations. +//! +//! # Supported Architectures +//! +//! - **AArch64**: GIC distributor and redistributor operations for virtual +//! interrupt injection and GIC initialization. +//! +//! # Usage +//! +//! The API functions in this module are conditionally compiled based on the +//! target architecture. On non-AArch64 platforms, this module is essentially +//! empty. +//! +//! # Implementation +//! +//! To implement these APIs, use the [`api_impl`](crate::api_impl) attribute +//! macro on an impl block: +//! +//! ```rust,ignore +//! struct ArchIfImpl; +//! +//! #[axvisor_api::api_impl] +//! impl axvisor_api::arch::ArchIf for ArchIfImpl { +//! // Implement the required functions... +//! } +//! ``` + +use super::{memory::PhysAddr, vmm::InterruptVector}; + +/// The API trait for architecture-specific functionalities. +/// +/// This trait defines the interface for architecture-specific operations +/// required by the hypervisor. Implementations should be provided by the +/// host system or HAL layer. +#[crate::api_def] +pub trait ArchIf { + /// Inject a virtual interrupt to the current virtual CPU using hardware + /// virtualization support. + /// + /// This function uses the GIC virtualization interface to directly inject + /// an interrupt into the guest without causing a VM exit. + /// + /// # Arguments + /// + /// * `vector` - The interrupt vector number to inject. + /// + /// # Platform Support + /// + /// This function is only available on AArch64 platforms with GICv2/v3 + /// virtualization extensions. + #[cfg(target_arch = "aarch64")] + fn hardware_inject_virtual_interrupt(vector: InterruptVector); + + /// Read the TYPER (Type Register) of the GIC distributor. + /// + /// The TYPER register provides information about the GIC implementation, + /// including the maximum number of SPIs supported and the number of + /// implemented CPU interfaces. + /// + /// # Returns + /// + /// The 32-bit value of the GICD_TYPER register. + /// + /// # Platform Support + /// + /// This function is only available on AArch64 platforms. + #[cfg(target_arch = "aarch64")] + fn read_vgicd_typer() -> u32; + + /// Read the IIDR (Implementer Identification Register) of the GIC + /// distributor. + /// + /// The IIDR register provides identification information about the GIC + /// implementation, including the implementer, revision, and variant. + /// + /// # Returns + /// + /// The 32-bit value of the GICD_IIDR register. + /// + /// # Platform Support + /// + /// This function is only available on AArch64 platforms. + #[cfg(target_arch = "aarch64")] + fn read_vgicd_iidr() -> u32; + + /// Get the base physical address of the GIC distributor in the host system. + /// + /// The GIC distributor is responsible for interrupt prioritization and + /// distribution to CPU interfaces. + /// + /// # Returns + /// + /// The physical address of the GICD (GIC Distributor) registers. + /// + /// # Platform Support + /// + /// This function is only available on AArch64 platforms. + #[cfg(target_arch = "aarch64")] + fn get_host_gicd_base() -> PhysAddr; + + /// Get the base physical address of the GIC redistributor in the host + /// system. + /// + /// The GIC redistributor (GICv3+) handles per-PE (Processing Element) + /// interrupt configuration and provides the LPI (Locality-specific + /// Peripheral Interrupt) configuration interface. + /// + /// # Returns + /// + /// The physical address of the GICR (GIC Redistributor) registers. + /// + /// # Platform Support + /// + /// This function is only available on AArch64 platforms with GICv3 or + /// later. + #[cfg(target_arch = "aarch64")] + fn get_host_gicr_base() -> PhysAddr; + + /// Retrieve the current pending interrupt for the CPU from the physical hardware. + #[cfg(target_arch = "aarch64")] + fn fetch_irq() -> u64; + /// Calls the IRQ handler of the underlying OS to handle a pending interrupt. + /// + /// TODO: Determine if this function should be exposed in other architectures (and moved to + /// `host` module) or remain architecture-specific. + /// + /// TODO: Consider whether this function should replace `AxVCpuExitReason::ExternalInterrupt`. + #[cfg(target_arch = "aarch64")] + fn handle_irq(); +} diff --git a/components/axvisor_api/src/host.rs b/components/axvisor_api/src/host.rs new file mode 100644 index 000000000..2e90bdca4 --- /dev/null +++ b/components/axvisor_api/src/host.rs @@ -0,0 +1,69 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Host system related APIs for the AxVisor hypervisor. +//! +//! This module provides APIs for querying information about the host system +//! on which the hypervisor is running. +//! +//! # Overview +//! +//! The host system APIs provide essential information about the underlying +//! hardware that the hypervisor needs to manage virtual machines effectively. +//! +//! # Available APIs +//! +//! - [`get_host_cpu_num`] - Get the total number of CPUs in the host system. +//! +//! # Implementation +//! +//! To implement these APIs, use the [`api_impl`](crate::api_impl) attribute +//! macro on an impl block: +//! +//! ```rust,ignore +//! struct HostIfImpl; +//! +//! #[axvisor_api::api_impl] +//! impl axvisor_api::host::HostIf for HostIfImpl { +//! fn get_host_cpu_num() -> usize { +//! // Return the number of CPUs from your platform +//! 4 +//! } +//! } +//! ``` + +/// The API trait for host system functionalities. +/// +/// This trait defines the interface for querying host system information. +/// Implementations should be provided by the host system or HAL layer. +#[crate::api_def] +pub trait HostIf { + /// Get the total number of CPUs (logical processors) in the host system. + /// + /// This function returns the number of CPUs available to the hypervisor, + /// which is typically the same as the number of physical or logical + /// processors in the system. + /// + /// # Returns + /// + /// The number of CPUs in the host system. + /// + /// # Example + /// + /// ```rust,ignore + /// let cpu_count = axvisor_api::host::get_host_cpu_num(); + /// println!("Host has {} CPUs", cpu_count); + /// ``` + fn get_host_cpu_num() -> usize; +} diff --git a/components/axvisor_api/src/lib.rs b/components/axvisor_api/src/lib.rs index 10a25aec5..c77cc0d48 100644 --- a/components/axvisor_api/src/lib.rs +++ b/components/axvisor_api/src/lib.rs @@ -1,274 +1,156 @@ -//! `axvisor_api` is a library that provides: -//! - a set of Rust API for all components of the `axvisor` Hypervisor, including: -//! -//! - memory management, -//! - time and timer management, -//! - interrupt management, -//! - ... -//! -//! these APIs are defined here, should be implemented by the axvisor Hypervisor, and can be use by all components. -//! -//! - a standard way to define and implement APIs, including the [`api_mod`] and the [`api_mod_impl`] attributes, which -//! the components can utilize to define and implement their own APIs. +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! `axvisor_api` is the bottom-most crate of the AxVisor Hypervisor project. It +//! provides a standardized set of APIs for the components of the Hypervisor and +//! grants them access to OS-level and Hypervisor-level functionalities, like +//! memory allocation, address conversion, time and timer management, cross-vcpu +//! operations, and so on. +//! +//! `axvisor_api` is designed for two main purposes: +//! - **Replace generic-based API-injection mechanism.** +//! +//! Generic-based API-injection mechanism, for example: +//! +//! ```rust +//! pub trait VCpuHal { +//! /* ... */ +//! } +//! pub struct VCpu { +//! /* ... */ +//! # _marker: core::marker::PhantomData, +//! } +//! ``` +//! +//! has been widely used previously here and in other related projects. It's +//! definitely great, with zero overhead, good readability, and no link-time +//! magic. However, it turns out that passing generic parameters down through +//! multiple layers of modules and components is quite inconvenient, and it's +//! always a pain to decide how to categorize the APIs into different traits +//! properly. +//! +//! Finally, the author decided to just use a big monolithic trait for every +//! component, and use `crate_interface` to eliminate the generic parameter. +//! (Although there are multiple traits in this crate, technically in the +//! current implementation the link-time symbol space is used as a big +//! monolithic trait.) Theoretically, this may slightly increase the +//! difficulty of re-using a single component in a new software project, but +//! the author believes that it will reduce the overall complexity when not +//! one but a few related components are re-used together. +//! +//! - **Enable portability of the Hypervisor across different unikernels.** +//! +//! Technically, the whole AxVisor Hypervisor can be ported to different +//! unikernels by implementing the APIs defined in this crate (although not +//! tested yet). If such porting is successful, this crate can also be used as +//! a tested hardware-and-unikernel abstraction layer for other hypervisor +//! projects. +//! +//! This crate also provides a standard way to define and implement APIs, with +//! the [`api_def`] and [`api_impl`] procedural macros. They are built on top of +//! the `crate_interface` crate, which provides the low-level functionalities +//! of defining and implementing crate-level interfaces. //! //! # How to define and implement APIs //! //! ## Define APIs //! -//! To define APIs, you can use the `api_mod` attribute to define a module containing the API functions. An API -//! function is defined with the `extern fn` syntax. Note that Vanilla Rust does not support defining extern functions -//! in such a way, so the definition of the API functions can easily be distinguished from the regular functions. +//! To define APIs, you can use the `api_def` attribute on a trait defining the +//! API, with each API function defined as a regular function in the trait. It's +//! recommended to pack the trait definition and related definitions (like type +//! aliases) into a module for better organization. //! //! ```rust, standalone_crate -//! # use axvisor_api::{api_mod, __priv}; // some inconviniece brought by proc-macro-name and doctest +//! # // some inconvenience brought by proc-macro-name and doctest +//! # use axvisor_api::__priv; //! # fn main() {} -//! #[api_mod] -//! /// Memory-related API -//! pub mod memory_demo { -//! pub use memory_addr::PhysAddr; +//! mod example { +//! # // some inconvenience brought by proc-macro-name and doctest +//! # use axvisor_api::api_def; +//! /// Example API definition +//! #[api_def] +//! pub trait ExampleIf { +//! /// An example API function +//! fn example_func(arg: usize) -> usize; +//! /// Another example API function +//! fn another_func(); +//! } +//! } //! -//! /// Allocate a frame -//! extern fn alloc_frame() -> Option; -//! /// Deallocate a frame -//! extern fn dealloc_frame(addr: PhysAddr); +//! fn use_example_api() { +//! let result = example::example_func(42); +//! example::another_func(); //! } //! ``` //! -//! Defined APIs can be invoked by other components: +//! `api_def` will generate a caller function for each API function defined in +//! the trait, at the same level as the trait definition. The generated callers +//! can be used to invoke the API functions, as demonstrated above. //! -//! ```rust, no_run, standalone_crate -//! # use axvisor_api::{api_mod, __priv}; // some inconviniece brought by proc-macro-name and doctest -//! # fn main() {} -//! # #[api_mod] -//! # /// Memory-related API -//! # pub mod memory_demo { -//! # pub use memory_addr::PhysAddr; -//! # -//! # /// Allocate a frame -//! # extern fn alloc_frame() -> Option; -//! # /// Deallocate a frame -//! # extern fn dealloc_frame(addr: PhysAddr); -//! # } -//! struct SomeComponent; +//! ## Implement APIs //! -//! impl SomeComponent { -//! fn some_method() { -//! let frame = memory_demo::alloc_frame().unwrap(); -//! // Do something with the frame -//! memory_demo::dealloc_frame(frame); +//! Defined APIs should be implemented somewhere, unless they are not used +//! anywhere. To implement APIs, the implementer should define an empty struct +//! and implement the API trait for the struct, with the `api_impl` attribute on +//! the `impl` block. For example, +//! +//! ```rust, standalone_crate +//! # // some inconvenience brought by proc-macro-name and doctest +//! # use axvisor_api::{api_impl, __priv}; +//! mod example { +//! # // some inconvenience brought by proc-macro-name and doctest +//! # use axvisor_api::{api_def, __priv}; +//! /// Example API definition +//! #[api_def] +//! pub trait ExampleIf { +//! /// An example API function +//! fn example_func(arg: usize) -> usize; +//! /// Another example API function +//! fn another_func(); //! } //! } -//! ``` -//! -//! ## Implement APIs -//! -//! Defined APIs should be implemented by the Hypervisor, or other components that are able and responsible to do so. To -//! implement APIs, you can use the `api_mod_impl` attribute, with the path of the module defining the APIs as the -//! argument, on a module containing the implementation of the API functions. The implementations of the API functions -//! are also defined with the `extern fn` syntax. //! -//! ```rust, no_run, standalone_crate -//! # use axvisor_api::{api_mod, api_mod_impl, __priv}; // some inconviniece brought by proc-macro-name and doctest -//! # fn main() {} -//! # #[api_mod] -//! # /// Memory-related API -//! # pub mod memory_demo { -//! # pub use memory_addr::PhysAddr; -//! # -//! # /// Allocate a frame -//! # extern fn alloc_frame() -> Option; -//! # /// Deallocate a frame -//! # extern fn dealloc_frame(addr: PhysAddr); -//! # } -//! #[api_mod_impl(memory_demo)] -//! mod memory_impl { -//! use memory_addr::PhysAddr; +//! struct ExampleImpl; //! -//! extern fn alloc_frame() -> Option { -//! // Implementation of the `alloc_frame` API -//! todo!() +//! #[api_impl] +//! impl example::ExampleIf for ExampleImpl { +//! fn example_func(arg: usize) -> usize { +//! arg + 1 //! } //! -//! extern fn dealloc_frame(addr: PhysAddr) { -//! // Implementation of the `dealloc_frame` API -//! todo!() +//! fn another_func() { +//! println!("Another function called"); //! } //! } -//! ``` //! -//! ## Tricks behind the macros -//! -//! [`api_mod`] and [`api_mod_impl`] are wrappers around the great [`crate_interface`] crate, with some macro tricks to -//! make the usage more convenient. +//! fn main() { +//! let result = example::example_func(42); +//! assert_eq!(result, 43); +//! example::another_func(); // prints "Another function called" +//! } +//! ``` //! #![no_std] -pub use axvisor_api_proc::{api_mod, api_mod_impl}; - -#[api_mod] -/// Memory-related API. -pub mod memory { - pub use memory_addr::{PhysAddr, VirtAddr}; - - // API interfaces +pub use axvisor_api_proc::{api_def, api_impl}; - /// Allocate a frame. - extern fn alloc_frame() -> Option; - /// Allocate a number of contiguous frames, with a specified alignment. - extern fn alloc_contiguous_frames( - num_frames: usize, - frame_align_pow2: usize, - ) -> Option; - /// Deallocate a frame. - extern fn dealloc_frame(addr: PhysAddr); - /// Deallocate a number of contiguous frames. - extern fn dealloc_contiguous_frames(first_addr: PhysAddr, num_frames: usize); - /// Convert a physical address to a virtual address. - extern fn phys_to_virt(addr: PhysAddr) -> VirtAddr; - /// Convert a virtual address to a physical address. - extern fn virt_to_phys(addr: VirtAddr) -> PhysAddr; - - // Re-exports - // TODO: determine whether it's proper and acceptable to place this definition here in this mod. - /// [`AxMmHal`](axaddrspace::AxMmHal) implementation by axvisor_api. - #[doc(hidden)] - pub struct AxMmHalApiImpl; - - impl axaddrspace::AxMmHal for AxMmHalApiImpl { - fn alloc_frame() -> Option { - alloc_frame() - } - - fn dealloc_frame(addr: PhysAddr) { - dealloc_frame(addr) - } - - fn phys_to_virt(addr: PhysAddr) -> VirtAddr { - phys_to_virt(addr) - } - - fn virt_to_phys(addr: VirtAddr) -> PhysAddr { - virt_to_phys(addr) - } - } - - /// A physical frame which will be automatically deallocated when dropped. - pub type PhysFrame = axaddrspace::PhysFrame; -} - -#[api_mod] -/// Time-and-timer-related API. -pub mod time { - extern crate alloc; - use alloc::boxed::Box; - use core::time::Duration; - - /// Time value. - pub type TimeValue = Duration; - /// Nanoseconds count. - pub type Nanos = u64; - /// Tick count. - pub type Ticks = u64; - /// Cancel token, used to cancel a scheduled timer event. - pub type CancelToken = usize; - - /// Get the current tick count. - extern fn current_ticks() -> Ticks; - /// Get the current time in nanoseconds. - pub fn current_time_nanos() -> Nanos { - ticks_to_nanos(current_ticks()) - } - /// Get the current time. - pub fn current_time() -> TimeValue { - Duration::from_nanos(current_time_nanos()) - } - - /// Convert ticks to nanoseconds. - extern fn ticks_to_nanos(ticks: Ticks) -> Nanos; - /// Convert ticks to time. - pub fn ticks_to_time(ticks: Ticks) -> TimeValue { - Duration::from_nanos(ticks_to_nanos(ticks)) - } - /// Convert nanoseconds to ticks. - extern fn nanos_to_ticks(nanos: Nanos) -> Ticks; - /// Convert time to ticks. - pub fn time_to_ticks(time: TimeValue) -> Ticks { - nanos_to_ticks(time.as_nanos() as Nanos) - } - - /// Register a timer. - extern fn register_timer( - deadline: TimeValue, - callback: Box, - ) -> CancelToken; - /// Cancel a timer. - extern fn cancel_timer(token: CancelToken); -} - -#[api_mod] -/// Virtual machine management API. -pub mod vmm { - /// Virtual machine ID. - pub type VMId = usize; - /// Virtual CPU ID. - pub type VCpuId = usize; - /// Interrupt vector. - pub type InterruptVector = u8; - - /// Get the ID of the current virtual machine. - extern fn current_vm_id() -> VMId; - /// Get the ID of the current virtual CPU. - extern fn current_vcpu_id() -> VCpuId; - /// Get the number of virtual CPUs in a virtual machine. - extern fn vcpu_num(vm_id: VMId) -> Option; - /// Get the mask of active virtual CPUs in a virtual machine. - extern fn active_vcpus(vm_id: VMId) -> Option; - /// Get the number of virtual CPUs in the current virtual machine. - pub fn current_vm_vcpu_num() -> usize { - vcpu_num(current_vm_id()).unwrap() - } - /// Get the mask of active virtual CPUs in the current virtual machine. - pub fn current_vm_active_vcpus() -> usize { - active_vcpus(current_vm_id()).unwrap() - } - - /// Inject an interrupt to a virtual CPU. - extern fn inject_interrupt(vm_id: VMId, vcpu_id: VCpuId, vector: InterruptVector); - /// Notify that a virtual CPU timer has expired. - /// - /// TODO: determine whether we can skip this function. - extern fn notify_vcpu_timer_expired(vm_id: VMId, vcpu_id: VCpuId); -} - -#[api_mod] -pub mod host { - /// Get the total number of cpus in the host system. - extern fn get_host_cpu_num() -> usize; -} - -#[api_mod] -pub mod arch { - use super::vmm::InterruptVector; - - #[cfg(target_arch = "aarch64")] - /// AArch64-specific API. Inject a virtual interrupt to the current virtual CPU using gich. - extern fn hardware_inject_virtual_interrupt(vector: InterruptVector); - - #[cfg(target_arch = "aarch64")] - /// AArch64-specific API. Get the TYPER register of the GIC distributor. Used in virtual GIC initialization. - extern fn read_vgicd_typer() -> u32; - #[cfg(target_arch = "aarch64")] - /// AArch64-specific API. Get the IIDR register of the GIC distributor. Used in virtual GIC initialization. - extern fn read_vgicd_iidr() -> u32; - - #[cfg(target_arch = "aarch64")] - /// AArch64-specific API. Get the base address of the GIC distributor in the host system. - extern fn get_host_gicd_base() -> crate::memory::PhysAddr; - #[cfg(target_arch = "aarch64")] - /// AArch64-specific API. Get the base address of the GIC redistributor in the host system. - extern fn get_host_gicr_base() -> crate::memory::PhysAddr; -} +pub mod arch; +pub mod host; +pub mod memory; +pub mod time; +pub mod vmm; #[doc(hidden)] pub mod __priv { diff --git a/components/axvisor_api/src/memory.rs b/components/axvisor_api/src/memory.rs new file mode 100644 index 000000000..941807c07 --- /dev/null +++ b/components/axvisor_api/src/memory.rs @@ -0,0 +1,211 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Memory allocation and address translation APIs for the AxVisor hypervisor. +//! +//! This module provides APIs for physical memory management, including frame +//! allocation/deallocation and physical-virtual address translation. +//! +//! # Overview +//! +//! The memory APIs are fundamental to the hypervisor's operation, enabling: +//! - Physical frame allocation for guest memory +//! - Contiguous frame allocation for DMA and other hardware requirements +//! - Address translation between physical and virtual addresses +//! +//! # Re-exports +//! +//! This module re-exports [`PhysAddr`] and [`VirtAddr`] from the `memory_addr` +//! crate for convenience. +//! +//! # Types +//! +//! - [`PhysFrame`] - A physical frame that is automatically deallocated when +//! dropped. +//! +//! # Implementation +//! +//! To implement these APIs, use the [`api_impl`](crate::api_impl) attribute +//! macro on an impl block: +//! +//! ```rust,ignore +//! struct MemoryIfImpl; +//! +//! #[axvisor_api::api_impl] +//! impl axvisor_api::memory::MemoryIf for MemoryIfImpl { +//! fn alloc_frame() -> Option { +//! // Allocate a physical frame from your allocator +//! } +//! // ... implement other functions +//! } +//! ``` + +pub use memory_addr::{PhysAddr, VirtAddr}; + +/// The API trait for memory allocation and address translation functionalities. +/// +/// This trait defines the core memory management interface required by the +/// hypervisor. Implementations should be provided by the host system or HAL +/// layer. +#[crate::api_def] +pub trait MemoryIf { + /// Allocate a single physical frame (4KB page). + /// + /// # Returns + /// + /// - `Some(PhysAddr)` - The physical address of the allocated frame. + /// - `None` - If allocation fails (e.g., out of memory). + /// + /// # Example + /// + /// ```rust,ignore + /// use axvisor_api::memory::{alloc_frame, dealloc_frame}; + /// + /// if let Some(frame) = alloc_frame() { + /// // Use the frame... + /// dealloc_frame(frame); + /// } + /// ``` + fn alloc_frame() -> Option; + + /// Allocate a number of contiguous physical frames with a specified + /// alignment. + /// + /// This function is useful for allocating memory for DMA buffers or other + /// hardware that requires contiguous physical memory. + /// + /// # Arguments + /// + /// * `num_frames` - The number of contiguous frames to allocate. + /// * `frame_align_pow2` - The alignment requirement as a power of 2 + /// (e.g., 0 for 4KB alignment, 1 for 8KB alignment). + /// + /// # Returns + /// + /// - `Some(PhysAddr)` - The physical address of the first allocated frame. + /// - `None` - If allocation fails. + fn alloc_contiguous_frames(num_frames: usize, frame_align_pow2: usize) -> Option; + + /// Deallocate a frame previously allocated by [`alloc_frame`]. + /// + /// # Arguments + /// + /// * `addr` - The physical address of the frame to deallocate. + /// + /// # Safety + /// + /// The caller must ensure that: + /// - The address was previously returned by [`alloc_frame`]. + /// - The frame has not been deallocated yet. + /// - No references to the frame's memory exist after deallocation. + fn dealloc_frame(addr: PhysAddr); + + /// Deallocate contiguous frames previously allocated by + /// [`alloc_contiguous_frames`]. + /// + /// # Arguments + /// + /// * `first_addr` - The physical address of the first frame. + /// * `num_frames` - The number of frames to deallocate. + /// + /// # Safety + /// + /// The caller must ensure that: + /// - The address and count match a previous [`alloc_contiguous_frames`] + /// call. + /// - The frames have not been deallocated yet. + /// - No references to the frames' memory exist after deallocation. + fn dealloc_contiguous_frames(first_addr: PhysAddr, num_frames: usize); + + /// Convert a physical address to a virtual address. + /// + /// This function performs the physical-to-virtual address translation + /// based on the host's memory mapping. + /// + /// # Arguments + /// + /// * `addr` - The physical address to convert. + /// + /// # Returns + /// + /// The corresponding virtual address. + /// + /// # Panics + /// + /// May panic if the physical address is not mapped. + fn phys_to_virt(addr: PhysAddr) -> VirtAddr; + + /// Convert a virtual address to a physical address. + /// + /// This function performs the virtual-to-physical address translation + /// based on the host's memory mapping. + /// + /// # Arguments + /// + /// * `addr` - The virtual address to convert. + /// + /// # Returns + /// + /// The corresponding physical address. + /// + /// # Panics + /// + /// May panic if the virtual address is not mapped. + fn virt_to_phys(addr: VirtAddr) -> PhysAddr; +} + +/// [`AxMmHal`](axaddrspace::AxMmHal) implementation by axvisor_api. +/// +/// This struct provides an implementation of the `AxMmHal` trait from the +/// `axaddrspace` crate, delegating to the axvisor_api memory functions. +#[doc(hidden)] +#[derive(Debug)] +pub struct AxMmHalApiImpl; + +impl axaddrspace::AxMmHal for AxMmHalApiImpl { + fn alloc_frame() -> Option { + alloc_frame() + } + + fn dealloc_frame(addr: PhysAddr) { + dealloc_frame(addr) + } + + fn phys_to_virt(addr: PhysAddr) -> VirtAddr { + phys_to_virt(addr) + } + + fn virt_to_phys(addr: VirtAddr) -> PhysAddr { + virt_to_phys(addr) + } +} + +/// A physical frame which will be automatically deallocated when dropped. +/// +/// This type alias provides a convenient RAII wrapper around physical frame +/// allocation. When a `PhysFrame` is dropped, it automatically deallocates +/// the underlying physical memory. +/// +/// # Example +/// +/// ```rust,ignore +/// use axvisor_api::memory::PhysFrame; +/// +/// fn allocate_guest_memory() -> Option { +/// PhysFrame::alloc() +/// } +/// +/// // The frame will be automatically deallocated when it goes out of scope +/// ``` +pub type PhysFrame = axaddrspace::PhysFrame; diff --git a/components/axvisor_api/src/test.rs b/components/axvisor_api/src/test.rs index 4aff434df..86d96b4f1 100644 --- a/components/axvisor_api/src/test.rs +++ b/components/axvisor_api/src/test.rs @@ -1,54 +1,80 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use memory_addr::{pa, va}; -/// A demonstration of the `memory` API implementation. -#[crate::api_mod_impl(crate::memory)] mod memory_impl { - use core::sync::atomic::AtomicUsize; + extern crate std; // in test only + use memory_addr::{PhysAddr, VirtAddr, pa, va}; + use std::sync::{ + Mutex, MutexGuard, + atomic::{AtomicUsize, Ordering}, + }; static ALLOCATED: AtomicUsize = AtomicUsize::new(0); static RETURNED_SUM: AtomicUsize = AtomicUsize::new(0); + static LOCK: Mutex<()> = Mutex::new(()); pub const VA_PA_OFFSET: usize = 0x1000; - extern fn alloc_frame() -> Option { - let value = ALLOCATED.fetch_add(1, core::sync::atomic::Ordering::SeqCst); + pub struct MemoryIfImpl; - Some(pa!(value * 0x1000)) - } + #[crate::api_impl] + impl crate::memory::MemoryIf for MemoryIfImpl { + fn alloc_frame() -> Option { + let value = ALLOCATED.fetch_add(1, Ordering::Relaxed); - extern fn alloc_contiguous_frames( - _num_frames: usize, - _frame_align_pow2: usize, - ) -> Option { - unimplemented!(); - } + Some(pa!(value * 0x1000)) + } - extern fn dealloc_frame(addr: PhysAddr) { - RETURNED_SUM.fetch_add(addr.as_usize(), core::sync::atomic::Ordering::SeqCst); - } + fn alloc_contiguous_frames( + _num_frames: usize, + _frame_align_pow2: usize, + ) -> Option { + unimplemented!(); + } + + fn dealloc_frame(addr: PhysAddr) { + RETURNED_SUM.fetch_add(addr.as_usize(), Ordering::Relaxed); + } + + fn dealloc_contiguous_frames(_first_addr: PhysAddr, _num_frames: usize) { + unimplemented!(); + } + + fn phys_to_virt(addr: PhysAddr) -> VirtAddr { + va!(addr.as_usize() + VA_PA_OFFSET) // Example implementation + } - extern fn dealloc_contiguous_frames(_first_addr: PhysAddr, _num_frames: usize) { - unimplemented!(); + fn virt_to_phys(addr: VirtAddr) -> PhysAddr { + pa!(addr.as_usize() - VA_PA_OFFSET) // Example implementation + } } /// Get the sum of all returned physical addresses. /// /// Note that this function demonstrates that non-API functions work well in a module with the `api_mod_impl` attribute. pub fn get_returned_sum() -> usize { - RETURNED_SUM.load(core::sync::atomic::Ordering::SeqCst) + RETURNED_SUM.load(Ordering::Relaxed) } - pub fn clear() { - ALLOCATED.store(0, core::sync::atomic::Ordering::SeqCst); - RETURNED_SUM.store(0, core::sync::atomic::Ordering::SeqCst); - } - - extern fn phys_to_virt(addr: PhysAddr) -> VirtAddr { - va!(addr.as_usize() + VA_PA_OFFSET) // Example implementation - } - - extern fn virt_to_phys(addr: VirtAddr) -> PhysAddr { - pa!(addr.as_usize() - VA_PA_OFFSET) // Example implementation + /// Start a test by acquiring the lock and resetting the internal state. + pub fn enter_test() -> MutexGuard<'static, ()> { + let guard = LOCK.lock().unwrap(); + ALLOCATED.store(0, Ordering::Relaxed); + RETURNED_SUM.store(0, Ordering::Relaxed); + guard } } @@ -56,7 +82,7 @@ mod memory_impl { pub fn test_memory() { use crate::memory; - memory_impl::clear(); + let guard = memory_impl::enter_test(); let frame1 = memory::alloc_frame(); let frame2 = memory::alloc_frame(); @@ -75,14 +101,15 @@ pub fn test_memory() { assert_eq!(memory::phys_to_virt(pa!(0)), va!(memory_impl::VA_PA_OFFSET)); assert_eq!(memory::virt_to_phys(va!(memory_impl::VA_PA_OFFSET)), pa!(0)); + + drop(guard); } #[test] pub fn test_memory_phys_frame() { - use crate::memory; - use crate::memory::PhysFrame; + use crate::memory::{self, PhysFrame}; - memory_impl::clear(); + let guard = memory_impl::enter_test(); let _ = memory::alloc_frame(); let frame1 = PhysFrame::alloc().unwrap(); @@ -99,4 +126,6 @@ pub fn test_memory_phys_frame() { assert_eq!(memory_impl::get_returned_sum(), 0x5000); drop(frame1); assert_eq!(memory_impl::get_returned_sum(), 0x6000); + + drop(guard); } diff --git a/components/axvisor_api/src/time.rs b/components/axvisor_api/src/time.rs new file mode 100644 index 000000000..eab1bce6f --- /dev/null +++ b/components/axvisor_api/src/time.rs @@ -0,0 +1,215 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Time and timer APIs for the AxVisor hypervisor. +//! +//! This module provides APIs for time measurement and timer management, +//! which are essential for implementing virtual timers and time-related +//! virtualization features. +//! +//! # Overview +//! +//! The time APIs provide: +//! - Current time and tick count queries +//! - Conversion between ticks, nanoseconds, and duration +//! - Timer registration and cancellation +//! +//! # Types +//! +//! - [`TimeValue`] - A time value represented as [`Duration`]. +//! - [`Nanos`] - Nanoseconds count (u64). +//! - [`Ticks`] - Tick count (u64). +//! - [`CancelToken`] - Token used to cancel a registered timer. +//! +//! # Helper Functions +//! +//! In addition to the core API trait, this module provides helper functions: +//! - [`current_time_nanos`] - Get the current time in nanoseconds. +//! - [`current_time`] - Get the current time as a [`TimeValue`]. +//! - [`ticks_to_time`] - Convert ticks to [`TimeValue`]. +//! - [`time_to_ticks`] - Convert [`TimeValue`] to ticks. +//! +//! # Implementation +//! +//! To implement these APIs, use the [`api_impl`](crate::api_impl) attribute +//! macro on an impl block: +//! +//! ```rust,ignore +//! struct TimeIfImpl; +//! +//! #[axvisor_api::api_impl] +//! impl axvisor_api::time::TimeIf for TimeIfImpl { +//! fn current_ticks() -> Ticks { +//! // Read the hardware timer counter +//! } +//! // ... implement other functions +//! } +//! ``` + +extern crate alloc; + +use alloc::boxed::Box; +use core::time::Duration; + +/// Time value type. +/// +/// Represents a point in time or a duration as a [`Duration`]. +pub type TimeValue = Duration; + +/// Nanoseconds count type. +/// +/// Used for high-precision time measurements in nanoseconds. +pub type Nanos = u64; + +/// Tick count type. +/// +/// Represents the raw hardware timer counter value. +pub type Ticks = u64; + +/// Cancel token type for timer cancellation. +/// +/// This token is returned when registering a timer and can be used to cancel +/// the timer before it fires. +pub type CancelToken = usize; + +/// The API trait for time and timer functionalities. +/// +/// This trait defines the core time management interface required by the +/// hypervisor. Implementations should be provided by the host system or HAL +/// layer. +#[crate::api_def] +pub trait TimeIf { + /// Get the current tick count from the hardware timer. + /// + /// The tick count is a monotonically increasing counter that can be + /// converted to time using [`ticks_to_nanos`]. + /// + /// # Returns + /// + /// The current hardware timer counter value. + fn current_ticks() -> Ticks; + + /// Convert a tick count to nanoseconds. + /// + /// # Arguments + /// + /// * `ticks` - The tick count to convert. + /// + /// # Returns + /// + /// The equivalent time in nanoseconds. + fn ticks_to_nanos(ticks: Ticks) -> Nanos; + + /// Convert nanoseconds to a tick count. + /// + /// # Arguments + /// + /// * `nanos` - The nanoseconds to convert. + /// + /// # Returns + /// + /// The equivalent tick count. + fn nanos_to_ticks(nanos: Nanos) -> Ticks; + + /// Register a timer that will fire at the specified deadline. + /// + /// When the deadline is reached, the callback function will be called + /// with the actual time at which it was invoked. + /// + /// # Arguments + /// + /// * `deadline` - The time at which the timer should fire. + /// * `callback` - The function to call when the timer fires. It receives + /// the actual time as an argument. + /// + /// # Returns + /// + /// A [`CancelToken`] that can be used to cancel the timer with + /// [`cancel_timer`]. + /// + /// # Example + /// + /// ```rust,ignore + /// use axvisor_api::time::{register_timer, current_time, TimeValue}; + /// use core::time::Duration; + /// + /// let deadline = current_time() + Duration::from_millis(100); + /// let token = register_timer(deadline, Box::new(|actual_time| { + /// println!("Timer fired at {:?}", actual_time); + /// })); + /// ``` + fn register_timer( + deadline: TimeValue, + callback: Box, + ) -> CancelToken; + + /// Cancel a previously registered timer. + /// + /// If the timer has already fired, this function has no effect. + /// + /// # Arguments + /// + /// * `token` - The cancel token returned by [`register_timer`]. + fn cancel_timer(token: CancelToken); +} + +/// Get the current time in nanoseconds. +/// +/// This is a convenience function that combines [`current_ticks`] and +/// [`ticks_to_nanos`]. +/// +/// # Returns +/// +/// The current time in nanoseconds since an unspecified epoch. +pub fn current_time_nanos() -> Nanos { + ticks_to_nanos(current_ticks()) +} + +/// Get the current time as a [`TimeValue`]. +/// +/// This is a convenience function that returns the current time as a +/// [`Duration`]. +/// +/// # Returns +/// +/// The current time as a [`TimeValue`] (Duration). +pub fn current_time() -> TimeValue { + Duration::from_nanos(current_time_nanos()) +} + +/// Convert ticks to a [`TimeValue`]. +/// +/// # Arguments +/// +/// * `ticks` - The tick count to convert. +/// +/// # Returns +/// +/// The equivalent time as a [`TimeValue`] (Duration). +pub fn ticks_to_time(ticks: Ticks) -> TimeValue { + Duration::from_nanos(ticks_to_nanos(ticks)) +} + +/// Convert a [`TimeValue`] to ticks. +/// +/// # Arguments +/// +/// * `time` - The time value to convert. +/// +/// # Returns +/// +/// The equivalent tick count. +pub fn time_to_ticks(time: TimeValue) -> Ticks { + nanos_to_ticks(time.as_nanos() as Nanos) +} diff --git a/components/axvisor_api/src/vmm.rs b/components/axvisor_api/src/vmm.rs new file mode 100644 index 000000000..8de149c15 --- /dev/null +++ b/components/axvisor_api/src/vmm.rs @@ -0,0 +1,209 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Virtual machine management APIs for the AxVisor hypervisor. +//! +//! This module provides APIs for managing virtual machines (VMs) and virtual +//! CPUs (vCPUs), including querying VM/vCPU information and interrupt +//! injection. +//! +//! # Overview +//! +//! The VMM (Virtual Machine Monitor) APIs enable: +//! - Querying the current VM and vCPU context +//! - Getting information about VMs and their vCPUs +//! - Injecting interrupts into virtual CPUs +//! - Timer expiration notifications +//! +//! # Types +//! +//! - [`VMId`] - Virtual machine identifier. +//! - [`VCpuId`] - Virtual CPU identifier. +//! - [`InterruptVector`] - Interrupt vector number. +//! +//! # Helper Functions +//! +//! In addition to the core API trait, this module provides helper functions: +//! - [`current_vm_vcpu_num`] - Get the vCPU count of the current VM. +//! - [`current_vm_active_vcpus`] - Get the active vCPU mask of the current VM. +//! +//! # Implementation +//! +//! To implement these APIs, use the [`api_impl`](crate::api_impl) attribute +//! macro on an impl block: +//! +//! ```rust,ignore +//! struct VmmIfImpl; +//! +//! #[axvisor_api::api_impl] +//! impl axvisor_api::vmm::VmmIf for VmmIfImpl { +//! fn current_vm_id() -> VMId { +//! // Return the current VM's ID +//! } +//! // ... implement other functions +//! } +//! ``` + +/// Virtual machine identifier type. +/// +/// Each virtual machine is assigned a unique identifier that can be used +/// to reference it in API calls. +pub type VMId = usize; + +/// Virtual CPU identifier type. +/// +/// Each vCPU within a VM is assigned a unique identifier (0-indexed). +pub type VCpuId = usize; + +/// Interrupt vector type. +/// +/// Represents the interrupt vector number to be injected into a guest. +pub type InterruptVector = u8; + +/// The maximum number of virtual CPUs supported in a virtual machine. +pub const MAX_VCPU_NUM: usize = 64; + +/// A set of virtual CPUs. +pub type VCpuSet = cpumask::CpuMask; + +/// The API trait for virtual machine management functionalities. +/// +/// This trait defines the core VM management interface required by the +/// hypervisor components. Implementations should be provided by the VMM +/// layer. +#[crate::api_def] +pub trait VmmIf { + /// Notify that a virtual CPU timer has expired. + /// Get the identifier of the current virtual machine. + /// + /// This function returns the VM ID of the VM that the calling context + /// belongs to. + /// + /// # Returns + /// + /// The current VM's identifier. + fn current_vm_id() -> VMId; + + /// Get the identifier of the current virtual CPU. + /// + /// This function returns the vCPU ID within the current VM context. + /// + /// # Returns + /// + /// The current vCPU's identifier (0-indexed within the VM). + fn current_vcpu_id() -> VCpuId; + + /// Get the number of virtual CPUs in a virtual machine. + /// + /// # Arguments + /// + /// * `vm_id` - The identifier of the virtual machine to query. + /// + /// # Returns + /// + /// - `Some(count)` - The number of vCPUs in the specified VM. + /// - `None` - If the VM ID is invalid. + fn vcpu_num(vm_id: VMId) -> Option; + + /// Get the bitmask of active virtual CPUs in a virtual machine. + /// + /// Each bit in the returned value represents a vCPU, where bit N is set + /// if vCPU N is active (online and running). + /// + /// # Arguments + /// + /// * `vm_id` - The identifier of the virtual machine to query. + /// + /// # Returns + /// + /// - `Some(mask)` - The active vCPU bitmask for the specified VM. + /// - `None` - If the VM ID is invalid. + fn active_vcpus(vm_id: VMId) -> Option; + + /// Inject an interrupt into a specific virtual CPU. + /// + /// This function queues an interrupt to be delivered to the specified + /// vCPU when it is next scheduled. + /// + /// # Arguments + /// + /// * `vm_id` - The identifier of the target virtual machine. + /// * `vcpu_id` - The identifier of the target virtual CPU. + /// * `vector` - The interrupt vector to inject. + /// + /// # Example + /// + /// ```rust,ignore + /// use axvisor_api::vmm::{inject_interrupt, current_vm_id}; + /// + /// // Inject timer interrupt (vector 0x20) to vCPU 0 of the current VM + /// inject_interrupt(current_vm_id(), 0, 0x20); + /// ``` + fn inject_interrupt(vm_id: VMId, vcpu_id: VCpuId, vector: InterruptVector); + + /// Inject an interrupt to a set of virtual CPUs. + fn inject_interrupt_to_cpus(vm_id: VMId, vcpu_set: VCpuSet, vector: InterruptVector); + + /// Notify that a virtual CPU's timer has expired. + /// + /// This function is called when a vCPU's virtual timer expires and needs + /// to be handled. + /// + /// # Arguments + /// + /// * `vm_id` - The identifier of the virtual machine. + /// * `vcpu_id` - The identifier of the virtual CPU whose timer expired. + /// + /// # Note + /// + /// This API may be revised in future versions as the timer virtualization + /// design evolves. + fn notify_vcpu_timer_expired(vm_id: VMId, vcpu_id: VCpuId); +} + +/// Get the number of virtual CPUs in the current virtual machine executing on +/// the current physical CPU. +/// +/// This is a convenience function that combines [`current_vm_id`] and +/// [`vcpu_num`]. +/// +/// # Returns +/// +/// The number of vCPUs in the current VM. +/// +/// # Panics +/// +/// Panics if called outside of a valid VM context (when [`current_vm_id`] +/// returns an invalid ID). +pub fn current_vm_vcpu_num() -> usize { + vcpu_num(current_vm_id()).unwrap() +} + +/// Get the bitmask of active virtual CPUs in the current virtual machine +/// executing on the current physical CPU. +/// +/// This is a convenience function that combines [`current_vm_id`] and +/// [`active_vcpus`]. +/// +/// # Returns +/// +/// The active vCPU bitmask for the current VM. +/// +/// # Panics +/// +/// Panics if called outside of a valid VM context (when [`current_vm_id`] +/// returns an invalid ID). +pub fn current_vm_active_vcpus() -> usize { + active_vcpus(current_vm_id()).unwrap() +} diff --git a/components/axvm/.github/workflows/push.yml b/components/axvm/.github/workflows/push.yml new file mode 100644 index 000000000..a18de4a04 --- /dev/null +++ b/components/axvm/.github/workflows/push.yml @@ -0,0 +1,147 @@ +# ═══════════════════════════════════════════════════════════════════════════════ +# 组件仓库 GitHub Actions 配置模板 +# ═══════════════════════════════════════════════════════════════════════════════ +# +# 此文件用于子仓库,当子仓库有更新时通知主仓库进行 subtree pull 同步。 +# +# 【使用步骤】 +# ───────────────────────────────────────────────────────────────────────────── +# 1. 将此文件复制到子仓库的 .github/workflows/ 目录: +# cp scripts/push.yml <子仓库>/.github/workflows/push.yml +# +# 2. 在子仓库中配置 Secret: +# GitHub 仓库 → Settings → Secrets → Actions → New repository secret +# 名称: PARENT_REPO_TOKEN +# 值: 具有主仓库 repo 权限的 Personal Access Token +# +# 3. 修改下方 env 块中的一个变量(标注了「需要修改」的行): +# PARENT_REPO - 主仓库路径,例如 rcore-os/tgoskits +# (subtree 目录由主仓库自动从 git 历史中推断,无需手动指定) +# +# 【Token 权限要求】 +# ───────────────────────────────────────────────────────────────────────────── +# PARENT_REPO_TOKEN 需要 Classic Personal Access Token,权限包括: +# - repo (Full control of private repositories) +# 或 +# - Fine-grained token: Contents (Read and Write) +# +# 【触发条件】 +# ───────────────────────────────────────────────────────────────────────────── +# - 自动触发:推送到 dev 或 main 分支时 +# - 手动触发:Actions → Notify Parent Repository → Run workflow +# +# 【工作流程】 +# ───────────────────────────────────────────────────────────────────────────── +# 子仓库 push → 触发此工作流 → 调用主仓库 API → 主仓库 subtree pull +# +# 【注意事项】 +# ───────────────────────────────────────────────────────────────────────────── +# - 主仓库需要配置接收 repository_dispatch 事件的同步工作流 +# - 如果不需要子仓库到主仓库的同步,可以不使用此文件 +# +# ═══════════════════════════════════════════════════════════════════════════════ + +name: Notify Parent Repository + +# 当有新的推送时触发 +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Get repository info + id: repo + env: + GH_REPO_NAME: ${{ github.event.repository.name }} + GH_REF_NAME: ${{ github.ref_name }} + GH_SERVER_URL: ${{ github.server_url }} + GH_REPOSITORY: ${{ github.repository }} + run: | + # 直接使用 GitHub Actions 内置变量,通过 env 传入避免 shell 注入 + COMPONENT="$GH_REPO_NAME" + BRANCH="$GH_REF_NAME" + # 构造标准 HTTPS URL,供主仓库按 URL 精确匹配 repos.list + REPO_URL="${GH_SERVER_URL}/${GH_REPOSITORY}" + + echo "component=${COMPONENT}" >> $GITHUB_OUTPUT + echo "branch=${BRANCH}" >> $GITHUB_OUTPUT + echo "repo_url=${REPO_URL}" >> $GITHUB_OUTPUT + + echo "Component: ${COMPONENT}" + echo "Branch: ${BRANCH}" + echo "Repo URL: ${REPO_URL}" + + - name: Notify parent repository + env: + # ── 需要修改 ────────────────────────────────────────────────────────── + PARENT_REPO: "rcore-os/tgoskits" # 主仓库路径 + # ── 无需修改 ────────────────────────────────────────────────────────── + DISPATCH_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} + # 将用户可控内容通过 env 传入,避免直接插值到 shell 脚本 + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + GIT_ACTOR: ${{ github.actor }} + GIT_SHA: ${{ github.sha }} + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "Notifying parent repository about update in ${COMPONENT}:${BRANCH}" + + # 使用 jq 安全构建 JSON,避免 commit message 中任何特殊字符导致注入 + PAYLOAD=$(jq -n \ + --arg component "$COMPONENT" \ + --arg branch "$BRANCH" \ + --arg repo_url "$REPO_URL" \ + --arg commit "$GIT_SHA" \ + --arg message "$COMMIT_MESSAGE" \ + --arg author "$GIT_ACTOR" \ + '{ + event_type: "subtree-update", + client_payload: { + component: $component, + branch: $branch, + repo_url: $repo_url, + commit: $commit, + message: $message, + author: $author + } + }') + + curl --fail --show-error -X POST \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${DISPATCH_TOKEN}" \ + https://api.github.com/repos/${PARENT_REPO}/dispatches \ + -d "$PAYLOAD" + + echo "Notification sent successfully" + + - name: Create summary + env: + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + GIT_SHA: ${{ github.sha }} + GIT_ACTOR: ${{ github.actor }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "## Notification Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Component**: ${COMPONENT}" >> $GITHUB_STEP_SUMMARY + echo "- **Branch**: ${BRANCH}" >> $GITHUB_STEP_SUMMARY + echo "- **Repo URL**: ${REPO_URL}" >> $GITHUB_STEP_SUMMARY + echo "- **Commit**: \`${GIT_SHA}\`" >> $GITHUB_STEP_SUMMARY + echo "- **Author**: ${GIT_ACTOR}" >> $GITHUB_STEP_SUMMARY + echo "- **Status**: ✅ Notification sent" >> $GITHUB_STEP_SUMMARY diff --git a/components/axvm/Cargo.toml b/components/axvm/Cargo.toml index 4f1ed96c9..fc82bffc6 100644 --- a/components/axvm/Cargo.toml +++ b/components/axvm/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "axvm" authors = ["aarkegz "] -version = "0.2.3" +version = "0.3.0" edition = "2024" categories = ["virtualization", "no-std"] description = "Virtual Machine resource management crate for ArceOS's hypervisor variant." @@ -12,12 +12,12 @@ license = "Apache-2.0" [features] default = ["vmx"] vmx = [] -4-level-ept = ["axaddrspace/4-level-ept"] # TODO: Realize 4-level-ept on x86_64 and riscv64. +4-level-ept = [] # TODO: Realize 4-level-ept on x86_64 and riscv64. [dependencies] log = "0.4" cfg-if = "1.0" -spin = "0.9" +spin = "0.10" # System independent crates provided by ArceOS. axerrno = "0.2" @@ -29,18 +29,19 @@ page_table_multiarch = "0.6" percpu = { version = "0.2.3-preview.1", features = ["arm-el2"] } # System dependent modules provided by ArceOS-Hypervisor. -axvcpu = "0.2.2" -axaddrspace = "0.1.5" -axdevice = "0.2.1" -axdevice_base = "=0.2.1" -axvmconfig = { version = "0.2", default-features = false } +axvcpu = "0.3" +axaddrspace = "0.3" +axvisor_api = "0.3" +axdevice = "0.2.2" +axdevice_base = "0.2.2" +axvmconfig = { version = "0.2.2", default-features = false } [target.'cfg(target_arch = "x86_64")'.dependencies] -x86_vcpu = "0.2.1" +x86_vcpu = "0.3" [target.'cfg(target_arch = "riscv64")'.dependencies] -riscv_vcpu = "0.2.1" +riscv_vcpu = "0.3" [target.'cfg(target_arch = "aarch64")'.dependencies] -arm_vcpu = "0.2.1" -arm_vgic = { version = "0.2.1", features = ["vgicv3"] } +arm_vcpu = "0.3" +arm_vgic = { version = "0.2.2", features = ["vgicv3"] } diff --git a/components/axvm/rust-toolchain.toml b/components/axvm/rust-toolchain.toml new file mode 100644 index 000000000..4a8cf3803 --- /dev/null +++ b/components/axvm/rust-toolchain.toml @@ -0,0 +1,10 @@ +[toolchain] +profile = "minimal" +channel = "nightly-2026-02-25" +components = ["rust-src", "llvm-tools", "rustfmt", "clippy"] +targets = [ + "x86_64-unknown-none", + "riscv64gc-unknown-none-elf", + "aarch64-unknown-none-softfloat", + "loongarch64-unknown-none-softfloat", +] \ No newline at end of file diff --git a/components/axvm/src/hal.rs b/components/axvm/src/hal.rs index 787c074f4..5c727cdfe 100644 --- a/components/axvm/src/hal.rs +++ b/components/axvm/src/hal.rs @@ -12,39 +12,41 @@ // See the License for the specific language governing permissions and // limitations under the License. -use axaddrspace::{HostPhysAddr, HostVirtAddr}; -use axerrno::AxResult; - -/// The interfaces which the underlying software (kernel or hypervisor) must implement. -pub trait AxVMHal: Sized { - /// The low-level **OS-dependent** helpers that must be provided for physical address management. - type PagingHandler: page_table_multiarch::PagingHandler; - - /// Converts a virtual address to the corresponding physical address. - fn virt_to_phys(vaddr: HostVirtAddr) -> HostPhysAddr; - - /// Current time in nanoseconds. - fn current_time_nanos() -> u64; - - /// Current VM ID. - fn current_vm_id() -> usize; - - /// Current Virtual CPU ID. - fn current_vcpu_id() -> usize; - - /// Current Physical CPU ID. - fn current_pcpu_id() -> usize; - - /// Get the Physical CPU ID where the specified VCPU of the current VM resides. - /// - /// Returns an error if the VCPU is not found. - fn vcpu_resides_on(vm_id: usize, vcpu_id: usize) -> AxResult; - - /// Inject an IRQ to the specified VCPU. - /// - /// This method should find the physical CPU where the specified VCPU resides and inject the IRQ - /// to it on that physical CPU with [`axvcpu::AxVCpu::inject_interrupt`]. - /// - /// Returns an error if the VCPU is not found. - fn inject_irq_to_vcpu(vm_id: usize, vcpu_id: usize, irq: usize) -> AxResult; +use memory_addr::{PAGE_SIZE_4K, PhysAddr, VirtAddr}; +use page_table_multiarch::PagingHandler; + +pub struct PagingHandlerImpl; + +impl PagingHandler for PagingHandlerImpl { + fn alloc_frames(num: usize, align: usize) -> Option { + let align_frames = if align.is_multiple_of(PAGE_SIZE_4K) { + align / PAGE_SIZE_4K + } else { + panic!("align must be multiple of PAGE_SIZE_4K") + }; + + let align_frames_pow2 = if align_frames.is_power_of_two() { + align_frames.trailing_zeros() + } else { + panic!("align must be a power of 2") + }; + + axvisor_api::memory::alloc_contiguous_frames(num, align_frames_pow2 as _) + } + + fn dealloc_frames(paddr: PhysAddr, num: usize) { + axvisor_api::memory::dealloc_contiguous_frames(paddr, num); + } + + fn alloc_frame() -> Option { + axvisor_api::memory::alloc_frame() + } + + fn dealloc_frame(paddr: PhysAddr) { + axvisor_api::memory::dealloc_frame(paddr) + } + + fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { + axvisor_api::memory::phys_to_virt(paddr) + } } diff --git a/components/axvm/src/lib.rs b/components/axvm/src/lib.rs index a76337ab6..8251301df 100644 --- a/components/axvm/src/lib.rs +++ b/components/axvm/src/lib.rs @@ -29,11 +29,10 @@ mod vm; pub mod config; -pub use hal::AxVMHal; pub use vm::{AxVCpuRef, AxVM, AxVMRef, VMMemoryRegion, VMStatus}; /// The architecture-independent per-CPU type. -pub type AxVMPerCpu = axvcpu::AxPerCpu>; +pub type AxVMPerCpu = axvcpu::AxPerCpu; /// Whether the hardware has virtualization support. pub fn has_hardware_support() -> bool { diff --git a/components/axvm/src/vm.rs b/components/axvm/src/vm.rs index b4312a55c..d8bf3dfd1 100644 --- a/components/axvm/src/vm.rs +++ b/components/axvm/src/vm.rs @@ -20,18 +20,19 @@ use axaddrspace::{ }; use axdevice::{AxVmDeviceConfig, AxVmDevices}; use axerrno::{AxError, AxResult, ax_err, ax_err_type}; -use axvcpu::{AxVCpu, AxVCpuExitReason, AxVCpuHal}; +use axvcpu::{AxVCpu, AxVCpuExitReason}; +use axvisor_api::vmm::InterruptVector; use cpumask::CpuMask; use memory_addr::{align_down_4k, align_up_4k}; use spin::{Mutex, Once}; -#[cfg(target_arch = "riscv64")] +#[cfg(not(target_arch = "x86_64"))] use crate::vcpu::AxVCpuCreateConfig; #[cfg(target_arch = "aarch64")] -use crate::vcpu::{AxVCpuCreateConfig, get_sysreg_device}; +use crate::vcpu::get_sysreg_device; use crate::{ - AxVMHal, config::{AxVMConfig, PhysCpuList}, + hal::PagingHandlerImpl, has_hardware_support, vcpu::AxArchVCpuImpl, }; @@ -40,23 +41,20 @@ const VM_ASPACE_BASE: usize = 0x0; const VM_ASPACE_SIZE: usize = 0x7fff_ffff_f000; /// A vCPU with architecture-independent interface. -#[allow(type_alias_bounds)] -type VCpu = AxVCpu>; +type VCpu = AxVCpu; /// A reference to a vCPU. -#[allow(type_alias_bounds)] -pub type AxVCpuRef = Arc>; +pub type AxVCpuRef = Arc; /// A reference to a VM. -#[allow(type_alias_bounds)] -pub type AxVMRef = Arc>; // we know the bound is not enforced here, we keep it for clarity +pub type AxVMRef = Arc; -struct AxVMInnerConst { +struct AxVMInnerConst { phys_cpu_ls: PhysCpuList, - vcpu_list: Box<[AxVCpuRef]>, + vcpu_list: Box<[AxVCpuRef]>, devices: AxVmDevices, } -unsafe impl Send for AxVMInnerConst {} -unsafe impl Sync for AxVMInnerConst {} +unsafe impl Send for AxVMInnerConst {} +unsafe impl Sync for AxVMInnerConst {} /// Represents a memory region in a virtual machine. #[derive(Debug, Clone)] @@ -83,13 +81,12 @@ impl VMMemoryRegion { } } -struct AxVMInnerMut { +struct AxVMInnerMut { // Todo: use more efficient lock. - address_space: AddrSpace, + address_space: AddrSpace, memory_regions: Vec, config: AxVMConfig, vm_status: VMStatus, - _marker: core::marker::PhantomData, } /// VM status enumeration representing the lifecycle states of a virtual machine @@ -144,19 +141,20 @@ impl fmt::Display for VMStatus { const TEMP_MAX_VCPU_NUM: usize = 64; /// A Virtual Machine. -pub struct AxVM { +pub struct AxVM { id: usize, - inner_const: Once>, - inner_mut: Mutex>, + inner_const: Once, + inner_mut: Mutex, } -impl AxVM { +impl AxVM { /// Creates a new VM with the given configuration. /// Returns an error if the configuration is invalid. /// The VM is not started until `boot` is called. - pub fn new(config: AxVMConfig) -> AxResult> { + pub fn new(config: AxVMConfig) -> AxResult { let address_space = - AddrSpace::new_empty(GuestPhysAddr::from(VM_ASPACE_BASE), VM_ASPACE_SIZE)?; + // TODO: read level from config + AddrSpace::new_empty(4, GuestPhysAddr::from(VM_ASPACE_BASE), VM_ASPACE_SIZE)?; let result = Arc::new(Self { id: config.id(), @@ -166,7 +164,6 @@ impl AxVM { config, memory_regions: Vec::new(), vm_status: VMStatus::Loading, - _marker: core::marker::PhantomData, }), }); @@ -188,7 +185,7 @@ impl AxVM { let dtb_addr = inner_mut.config.image_config().dtb_load_gpa; let vcpu_id_pcpu_sets = inner_mut.config.phys_cpu_ls.get_vcpu_affinities_pcpu_ids(); - info!("dtb_load_gpa: {:?}", dtb_addr); + info!("dtb_load_gpa: {dtb_addr:?}"); debug!("id: {}, VCpuIdPCpuSets: {vcpu_id_pcpu_sets:#x?}", self.id()); let mut vcpu_list = Vec::with_capacity(vcpu_id_pcpu_sets.len()); @@ -204,6 +201,10 @@ impl AxVM { dtb_addr: dtb_addr.unwrap_or_default().as_usize(), }; + // FIXME: VCpu is neither `Send` nor `Sync` by design, check whether + // 1. we should make it `Send` and `Sync`, or + // 2. we can guarantee that no cross-thread access is performed + #[allow(clippy::arc_with_non_send_sync)] vcpu_list.push(Arc::new(VCpu::new( self.id(), vcpu_id, @@ -277,14 +278,10 @@ impl AxVM { )?; } - #[cfg(target_arch = "aarch64")] + #[cfg_attr(not(target_arch = "aarch64"), expect(unused_mut))] let mut devices = axdevice::AxVmDevices::new(AxVmDeviceConfig { emu_configs: inner_mut.config.emu_devices().to_vec(), }); - #[cfg(not(target_arch = "aarch64"))] - let devices = axdevice::AxVmDevices::new(AxVmDeviceConfig { - emu_configs: inner_mut.config.emu_devices().to_vec(), - }); #[cfg(target_arch = "aarch64")] { @@ -345,6 +342,9 @@ impl AxVM { passthrough_timer: passthrough, } }; + #[cfg(not(target_arch = "aarch64"))] + #[allow(clippy::let_unit_value)] + let setup_config = ::SetupConfig::default(); let entry = if vcpu.id() == 0 { inner_mut.config.bsp_entry() @@ -357,10 +357,7 @@ impl AxVM { vcpu.setup( entry, inner_mut.address_space.page_table_root(), - #[cfg(target_arch = "aarch64")] setup_config, - #[cfg(not(target_arch = "aarch64"))] - (), )?; } info!("VM setup: id={}", self.id()); @@ -382,7 +379,7 @@ impl AxVM { /// Retrieves the vCPU corresponding to the given vcpu_id for the VM. /// Returns None if the vCPU does not exist. #[inline] - pub fn vcpu(&self, vcpu_id: usize) -> Option> { + pub fn vcpu(&self, vcpu_id: usize) -> Option { self.vcpu_list().get(vcpu_id).cloned() } @@ -392,7 +389,7 @@ impl AxVM { self.inner_const().vcpu_list.len() } - fn inner_const(&self) -> &AxVMInnerConst { + fn inner_const(&self) -> &AxVMInnerConst { self.inner_const .get() .expect("VM inner_const not initialized") @@ -400,7 +397,7 @@ impl AxVM { /// Returns a reference to the list of vCPUs corresponding to the VM. #[inline] - pub fn vcpu_list(&self) -> &[AxVCpuRef] { + pub fn vcpu_list(&self) -> &[AxVCpuRef] { &self.inner_const().vcpu_list } @@ -592,13 +589,12 @@ impl AxVM { // It is not supported to inject interrupt to a vcpu in another VM yet. // // It may be supported in the future, as a essential feature for cross-VM communication. - if H::current_vm_id() != self.id() { + let current_running_vm = axvisor_api::vmm::current_vm_id(); + if current_running_vm != vm_id { panic!("Injecting interrupt to a vcpu in another VM is not supported"); } - for target_vcpu in &targets { - H::inject_irq_to_vcpu(vm_id, target_vcpu, irq)?; - } + axvisor_api::vmm::inject_interrupt_to_cpus(vm_id, targets, irq as InterruptVector); Ok(()) } @@ -755,7 +751,7 @@ impl AxVM { let s = unsafe { core::slice::from_raw_parts_mut(hva, layout.size()) }; let hva = HostVirtAddr::from_mut_ptr_of(hva); - let hpa = H::virt_to_phys(hva); + let hpa = axvisor_api::memory::virt_to_phys(hva); let gpa = gpa.unwrap_or_else(|| hpa.as_usize().into()); @@ -923,7 +919,7 @@ impl AxVM { } } -impl Drop for AxVM { +impl Drop for AxVM { fn drop(&mut self) { info!("Dropping VM[{}]", self.id()); diff --git a/components/axvmconfig/.github/workflows/push.yml b/components/axvmconfig/.github/workflows/push.yml new file mode 100644 index 000000000..a18de4a04 --- /dev/null +++ b/components/axvmconfig/.github/workflows/push.yml @@ -0,0 +1,147 @@ +# ═══════════════════════════════════════════════════════════════════════════════ +# 组件仓库 GitHub Actions 配置模板 +# ═══════════════════════════════════════════════════════════════════════════════ +# +# 此文件用于子仓库,当子仓库有更新时通知主仓库进行 subtree pull 同步。 +# +# 【使用步骤】 +# ───────────────────────────────────────────────────────────────────────────── +# 1. 将此文件复制到子仓库的 .github/workflows/ 目录: +# cp scripts/push.yml <子仓库>/.github/workflows/push.yml +# +# 2. 在子仓库中配置 Secret: +# GitHub 仓库 → Settings → Secrets → Actions → New repository secret +# 名称: PARENT_REPO_TOKEN +# 值: 具有主仓库 repo 权限的 Personal Access Token +# +# 3. 修改下方 env 块中的一个变量(标注了「需要修改」的行): +# PARENT_REPO - 主仓库路径,例如 rcore-os/tgoskits +# (subtree 目录由主仓库自动从 git 历史中推断,无需手动指定) +# +# 【Token 权限要求】 +# ───────────────────────────────────────────────────────────────────────────── +# PARENT_REPO_TOKEN 需要 Classic Personal Access Token,权限包括: +# - repo (Full control of private repositories) +# 或 +# - Fine-grained token: Contents (Read and Write) +# +# 【触发条件】 +# ───────────────────────────────────────────────────────────────────────────── +# - 自动触发:推送到 dev 或 main 分支时 +# - 手动触发:Actions → Notify Parent Repository → Run workflow +# +# 【工作流程】 +# ───────────────────────────────────────────────────────────────────────────── +# 子仓库 push → 触发此工作流 → 调用主仓库 API → 主仓库 subtree pull +# +# 【注意事项】 +# ───────────────────────────────────────────────────────────────────────────── +# - 主仓库需要配置接收 repository_dispatch 事件的同步工作流 +# - 如果不需要子仓库到主仓库的同步,可以不使用此文件 +# +# ═══════════════════════════════════════════════════════════════════════════════ + +name: Notify Parent Repository + +# 当有新的推送时触发 +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Get repository info + id: repo + env: + GH_REPO_NAME: ${{ github.event.repository.name }} + GH_REF_NAME: ${{ github.ref_name }} + GH_SERVER_URL: ${{ github.server_url }} + GH_REPOSITORY: ${{ github.repository }} + run: | + # 直接使用 GitHub Actions 内置变量,通过 env 传入避免 shell 注入 + COMPONENT="$GH_REPO_NAME" + BRANCH="$GH_REF_NAME" + # 构造标准 HTTPS URL,供主仓库按 URL 精确匹配 repos.list + REPO_URL="${GH_SERVER_URL}/${GH_REPOSITORY}" + + echo "component=${COMPONENT}" >> $GITHUB_OUTPUT + echo "branch=${BRANCH}" >> $GITHUB_OUTPUT + echo "repo_url=${REPO_URL}" >> $GITHUB_OUTPUT + + echo "Component: ${COMPONENT}" + echo "Branch: ${BRANCH}" + echo "Repo URL: ${REPO_URL}" + + - name: Notify parent repository + env: + # ── 需要修改 ────────────────────────────────────────────────────────── + PARENT_REPO: "rcore-os/tgoskits" # 主仓库路径 + # ── 无需修改 ────────────────────────────────────────────────────────── + DISPATCH_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} + # 将用户可控内容通过 env 传入,避免直接插值到 shell 脚本 + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + GIT_ACTOR: ${{ github.actor }} + GIT_SHA: ${{ github.sha }} + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "Notifying parent repository about update in ${COMPONENT}:${BRANCH}" + + # 使用 jq 安全构建 JSON,避免 commit message 中任何特殊字符导致注入 + PAYLOAD=$(jq -n \ + --arg component "$COMPONENT" \ + --arg branch "$BRANCH" \ + --arg repo_url "$REPO_URL" \ + --arg commit "$GIT_SHA" \ + --arg message "$COMMIT_MESSAGE" \ + --arg author "$GIT_ACTOR" \ + '{ + event_type: "subtree-update", + client_payload: { + component: $component, + branch: $branch, + repo_url: $repo_url, + commit: $commit, + message: $message, + author: $author + } + }') + + curl --fail --show-error -X POST \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${DISPATCH_TOKEN}" \ + https://api.github.com/repos/${PARENT_REPO}/dispatches \ + -d "$PAYLOAD" + + echo "Notification sent successfully" + + - name: Create summary + env: + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + GIT_SHA: ${{ github.sha }} + GIT_ACTOR: ${{ github.actor }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "## Notification Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Component**: ${COMPONENT}" >> $GITHUB_STEP_SUMMARY + echo "- **Branch**: ${BRANCH}" >> $GITHUB_STEP_SUMMARY + echo "- **Repo URL**: ${REPO_URL}" >> $GITHUB_STEP_SUMMARY + echo "- **Commit**: \`${GIT_SHA}\`" >> $GITHUB_STEP_SUMMARY + echo "- **Author**: ${GIT_ACTOR}" >> $GITHUB_STEP_SUMMARY + echo "- **Status**: ✅ Notification sent" >> $GITHUB_STEP_SUMMARY diff --git a/components/fxmac_rs/.github/workflows/check.yaml b/components/fxmac_rs/.github/workflows/check.yaml new file mode 100644 index 000000000..17419001f --- /dev/null +++ b/components/fxmac_rs/.github/workflows/check.yaml @@ -0,0 +1,39 @@ +name: Quality Checks + +on: + workflow_call: + +jobs: + check: + name: Quality Checks + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + targets: [aarch64-unknown-none-softfloat, aarch64-unknown-none] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2025-01-18 + components: rust-src, clippy, rustfmt + targets: ${{ matrix.targets }} + + - name: Check rust version + run: rustc --version --verbose + + - name: Check code format + run: cargo fmt --all -- --check + + - name: Build + run: cargo build --target ${{ matrix.targets }} + + - name: Run clippy + run: cargo clippy --target ${{ matrix.targets }} -- -D warnings + + - name: Build documentation + run: cargo doc --no-deps --target ${{ matrix.targets }} diff --git a/components/fxmac_rs/.github/workflows/deploy.yaml b/components/fxmac_rs/.github/workflows/deploy.yaml new file mode 100644 index 000000000..cef44929f --- /dev/null +++ b/components/fxmac_rs/.github/workflows/deploy.yaml @@ -0,0 +1,63 @@ +name: Deploy + +on: + push: + branches: + - '**' + tags-ignore: + - 'v*' + - 'v*-pre.*' + pull_request: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: 'pages' + cancel-in-progress: false + +jobs: + quality-check: + uses: ./.github/workflows/check.yaml + + build-doc: + name: Build documentation + runs-on: ubuntu-latest + needs: quality-check + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2025-01-18 + components: rust-src + targets: aarch64-unknown-none-softfloat + + - name: Build docs + run: cargo doc --no-deps --target aarch64-unknown-none-softfloat + + - name: Create index redirect + run: | + printf '' > target/aarch64-unknown-none-softfloat/doc/index.html + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: target/aarch64-unknown-none-softfloat/doc + + deploy-doc: + name: Deploy to GitHub Pages + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build-doc + if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/components/fxmac_rs/.gitignore b/components/fxmac_rs/.gitignore new file mode 100644 index 000000000..10eceb8e9 --- /dev/null +++ b/components/fxmac_rs/.gitignore @@ -0,0 +1,24 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds +*.pdb + +# IDE / Editor +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# macOS +.DS_Store diff --git a/components/fxmac_rs/Cargo.lock b/components/fxmac_rs/Cargo.lock new file mode 100644 index 000000000..af7f2f10e --- /dev/null +++ b/components/fxmac_rs/Cargo.lock @@ -0,0 +1,79 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aarch64-cpu" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a21cd0131c25c438e19cd6a774adf7e3f64f7f4d723022882facc2dee0f8bc9" +dependencies = [ + "tock-registers", +] + +[[package]] +name = "crate_interface" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70272a03a2cef15589bac05d3d15c023752f5f8f2da8be977d983a9d9e6250fb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fxmac_rs" +version = "0.2.1" +dependencies = [ + "aarch64-cpu", + "crate_interface", + "log", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tock-registers" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b9e2fdb3a1e862c0661768b7ed25390811df1947a8acbfbefe09b47078d93c4" + +[[package]] +name = "unicode-ident" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" diff --git a/components/fxmac_rs/Cargo.toml b/components/fxmac_rs/Cargo.toml new file mode 100644 index 000000000..0b90f7b45 --- /dev/null +++ b/components/fxmac_rs/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "fxmac_rs" +version = "0.2.1" +edition = "2021" +authors = ["xiaoluoyuan@163.com"] +description = "FXMAC Ethernet driver in Rust for PhytiumPi (Phytium Pi) board, supporting DMA-based packet transmission and reception." +documentation = "https://docs.rs/fxmac_rs" +repository = "https://github.com/elliott10/fxmac_rs" +homepage = "https://github.com/elliott10/fxmac_rs" +readme = "README.md" +license = "GPL-2.0-only" +keywords = ["ethernet", "driver", "phytium", "aarch64", "no_std"] +categories = ["embedded", "hardware-support", "no-std"] + +[features] +debug = [] + +[dependencies] +log = "0.4" +crate_interface = "0.3" + +[target.'cfg(target_arch = "aarch64")'.dependencies] +aarch64-cpu = "10" diff --git a/components/axvisor_api/LICENSE.GPLv3 b/components/fxmac_rs/LICENSE similarity index 100% rename from components/axvisor_api/LICENSE.GPLv3 rename to components/fxmac_rs/LICENSE diff --git a/components/fxmac_rs/README.md b/components/fxmac_rs/README.md new file mode 100644 index 000000000..37f781266 --- /dev/null +++ b/components/fxmac_rs/README.md @@ -0,0 +1,77 @@ +# FXMAC ethernet driver +fxmac ethernet driver in Rust on PhytiumPi board. + +## Quick Start + +For instance, [fxmac_rs ethernet driver in Rust on ArceOS](https://github.com/arceos-org/arceos/blob/7e52baa8bed7a9dbfc59acfb9e07d3f71551d651/modules/axdriver/src/drivers.rs#L133) + +![fxmac_rs on arceos](doc/PhytiumPi-ethernet-arceos.jpg) + +* Initialize ethernet driver +``` +pub struct FXmacDriver; + +#[crate_interface::impl_interface] +impl axdriver_net::fxmac::KernelFunc for FXmacDriver{ + /// 虚拟地址转换成物理地址 + fn virt_to_phys(addr: usize) -> usize { + } + + /// 物理地址转换成虚拟地址 + fn phys_to_virt(addr: usize) -> usize { + } + + /// 申请页对齐的DMA连续内存页 + /// 返回((cpu virtual address, dma physical address)) + fn dma_alloc_coherent(pages: usize) -> (usize, usize) { + } + + /// 释放DMA内存页 + fn dma_free_coherent(vaddr: usize, pages: usize) { + } + + /// 使能并注册网卡中断 + fn dma_request_irq(_irq: usize, _handler: fn()) { + warn!("unimplemented dma_request_irq for fxmax"); + } +} + +let hwaddr: [u8; 6] = [0x55, 0x44, 0x33, 0x22, 0x11, 0x00]; +let fxmac_device: &'static mut FXmac = fxmac_rs::fxmac::xmac_init(&hwaddr); +``` + +* Sending network packets +``` +let mut tx_vec = Vec::new(); +tx_vec.push(packet.to_vec()); +FXmacLwipPortTx(fxmac_device, tx_vec); +``` + +* Receiving network packets +``` +let recv_packets = FXmacRecvHandler(fxmac_device); + +``` + +### Build + +``` +cargo build --target=aarch64-unknown-none-softfloat +``` + +## Testing (建议) + +由于该驱动强依赖硬件与 DMA,建议分层补充测试并按能力选择运行环境: + +- 单元测试/文档测试(主机可跑):`cargo test --all-features`、`cargo test --doc` +- 功能测试(mock/模拟):对 BD ring、地址转换与错误分支做行为验证 +- 系统测试(真实板子):在 PhytiumPi 上验证链路协商、收发路径与中断处理 + +## About ethernet +PHY: Motorcomm YT8521 + +![yt8521](doc/phtpi-eth.jpg) + +## Reference +* [phytium-standalone-sdk](https://gitee.com/phytium_embedded/phytium-standalone-sdk/tree/master) *sdk驱动的代码质量及逻辑写得一言难尽啊 * +* [Linux 5.10](https://gitee.com/phytium_embedded/phytium-linux-kernel/blob/linux-5.10/drivers/net/ethernet/cadence/macb_main.c) diff --git a/components/fxmac_rs/doc/PHY-Motorcomm-YT8521SH.pdf b/components/fxmac_rs/doc/PHY-Motorcomm-YT8521SH.pdf new file mode 100644 index 000000000..3c5b0dc8e Binary files /dev/null and b/components/fxmac_rs/doc/PHY-Motorcomm-YT8521SH.pdf differ diff --git a/components/fxmac_rs/doc/PhytiumPi-ethernet-arceos.jpg b/components/fxmac_rs/doc/PhytiumPi-ethernet-arceos.jpg new file mode 100644 index 000000000..31c8eeae1 Binary files /dev/null and b/components/fxmac_rs/doc/PhytiumPi-ethernet-arceos.jpg differ diff --git a/components/fxmac_rs/doc/phtpi-eth.jpg b/components/fxmac_rs/doc/phtpi-eth.jpg new file mode 100644 index 000000000..9f9cbb82b Binary files /dev/null and b/components/fxmac_rs/doc/phtpi-eth.jpg differ diff --git a/components/fxmac_rs/rust-toolchain b/components/fxmac_rs/rust-toolchain new file mode 100644 index 000000000..25382af09 --- /dev/null +++ b/components/fxmac_rs/rust-toolchain @@ -0,0 +1,5 @@ +[toolchain] +channel = "nightly-2025-01-18" +components = [ "llvm-tools-preview" ] +targets = [ "aarch64-unknown-none-softfloat" ] +profile = "minimal" diff --git a/components/fxmac_rs/src/fxmac.rs b/components/fxmac_rs/src/fxmac.rs new file mode 100644 index 000000000..6f4266762 --- /dev/null +++ b/components/fxmac_rs/src/fxmac.rs @@ -0,0 +1,1948 @@ +//! Core FXMAC Ethernet controller functionality. +//! +//! This module provides the main data structures and functions for controlling +//! the FXMAC Ethernet MAC controller. + +use core::sync::atomic::Ordering; + +use crate::fxmac_const::*; +use crate::fxmac_dma::*; +use crate::fxmac_intr::*; +use crate::fxmac_phy::*; +use crate::utils::*; +use alloc::boxed::Box; + +/// Handler type for DMA send (TX) interrupts. +pub const FXMAC_HANDLER_DMASEND: u32 = 1; +/// Handler type for DMA receive (RX) interrupts. +pub const FXMAC_HANDLER_DMARECV: u32 = 2; +/// Handler type for error interrupts. +pub const FXMAC_HANDLER_ERROR: u32 = 3; +/// Handler type for link status change interrupts. +pub const FXMAC_HANDLER_LINKCHANGE: u32 = 4; +/// Handler type for TX descriptor queue restart. +pub const FXMAC_HANDLER_RESTART: u32 = 5; + +/// Link status: down. +pub const FXMAC_LINKDOWN: u32 = 0; +/// Link status: up. +pub const FXMAC_LINKUP: u32 = 1; +/// Link status: negotiating. +pub const FXMAC_NEGOTIATING: u32 = 2; + +/// FXMAC0 peripheral clock frequency in Hz. +pub const FXMAC0_PCLK: u32 = 50000000; +/// FXMAC0 hotplug IRQ number. +pub const FXMAC0_HOTPLUG_IRQ_NUM: u32 = 53 + 30; +/// Maximum number of hardware queues supported. +pub const FXMAC_QUEUE_MAX_NUM: u32 = 4; + +/// Mask for upper 32 bits of 64-bit address. +pub const ULONG64_HI_MASK: u64 = 0xFFFFFFFF00000000; +/// Mask for lower 32 bits of 64-bit address. +pub const ULONG64_LO_MASK: u64 = !ULONG64_HI_MASK; + +/// Component is initialized and ready. +pub const FT_COMPONENT_IS_READY: u32 = 0x11111111; +/// Component is started. +pub const FT_COMPONENT_IS_STARTED: u32 = 0x22222222; + +/// Memory page size in bytes. +pub const PAGE_SIZE: usize = 4096; +/// Base address of FXMAC0 controller. +pub(crate) const FXMAC_IOBASE: u64 = 0x3200c000; + +/// Main FXMAC Ethernet controller instance. +/// +/// This structure holds all state information for an FXMAC controller instance, +/// including configuration, DMA queues, and runtime status. +/// +/// # Thread Safety +/// +/// This structure implements `Send` and `Sync` for use across threads, but +/// external synchronization is required for concurrent access to mutable state. +/// +/// # Example +/// +/// ```ignore +/// let hwaddr: [u8; 6] = [0x55, 0x44, 0x33, 0x22, 0x11, 0x00]; +/// let fxmac: &'static mut FXmac = xmac_init(&hwaddr); +/// +/// // Check link status +/// if fxmac.link_status == FXMAC_LINKUP { +/// println!("Network link is up!"); +/// } +/// ``` +pub struct FXmac { + /// Hardware configuration settings. + pub config: FXmacConfig, + /// Device initialization state (FT_COMPONENT_IS_READY when initialized). + pub is_ready: u32, + /// Device running state (FT_COMPONENT_IS_STARTED when active). + pub is_started: u32, + /// Current link status (FXMAC_LINKUP, FXMAC_LINKDOWN, or FXMAC_NEGOTIATING). + pub link_status: u32, + /// Currently enabled MAC options. + pub options: u32, + /// Interrupt mask for enabled interrupts. + pub mask: u32, + /// Capability mask bits. + pub caps: u32, + /// Network buffer management (lwIP port compatibility). + pub lwipport: FXmacLwipPort, + /// Transmit buffer descriptor queue. + pub tx_bd_queue: FXmacQueue, + /// Receive buffer descriptor queue. + pub rx_bd_queue: FXmacQueue, + /// Hardware module identification number. + pub moudle_id: u32, + /// Maximum transmission unit size. + pub max_mtu_size: u32, + /// Maximum frame size including headers. + pub max_frame_size: u32, + /// PHY address on the MDIO bus. + pub phy_address: u32, + /// Receive buffer mask for speed settings. + pub rxbuf_mask: u32, +} + +// SAFETY: FXmac can be sent between threads as long as proper synchronization +// is used for concurrent access. +unsafe impl Send for FXmac {} +// SAFETY: FXmac can be shared between threads with external synchronization. +unsafe impl Sync for FXmac {} + +/// Hardware configuration for the FXMAC controller. +/// +/// This structure contains all hardware-level configuration parameters +/// required to initialize and operate the FXMAC Ethernet controller. +pub struct FXmacConfig { + /// Instance identifier for multi-controller setups. + pub instance_id: u32, + /// Base address of the MAC controller registers. + pub base_address: u64, + /// Base address for extended mode configuration. + pub extral_mode_base: u64, + /// Base address for loopback configuration. + pub extral_loopback_base: u64, + /// PHY interface type (SGMII, RGMII, etc.). + pub interface: FXmacPhyInterface, + /// Link speed in Mbps (10, 100, 1000, etc.). + pub speed: u32, + /// Duplex mode: 1 for full-duplex, 0 for half-duplex. + pub duplex: u32, + /// Auto-negotiation enable: 1 to enable, 0 to disable. + pub auto_neg: u32, + /// Peripheral clock frequency in Hz. + pub pclk_hz: u32, + /// Maximum number of hardware queues. + pub max_queue_num: u32, + /// TX queue index (0 to FXMAC_QUEUE_MAX_NUM-1). + pub tx_queue_id: u32, + /// RX queue index (0 to FXMAC_QUEUE_MAX_NUM-1). + pub rx_queue_id: u32, + /// Hotplug IRQ number. + pub hotplug_irq_num: u32, + /// DMA burst length setting. + pub dma_brust_length: u32, + /// Default network configuration options. + pub network_default_config: u32, + /// IRQ numbers for each hardware queue. + pub queue_irq_num: [u32; FXMAC_QUEUE_MAX_NUM as usize], + /// Capability flags (e.g., tail pointer support). + pub caps: u32, + /// MAC address (6 bytes). + pub mac: [u8; 6], +} + +/// Hardware queue structure for TX/RX operations. +pub struct FXmacQueue { + /// Queue identifier. + pub queue_id: u32, + /// Buffer descriptor ring for this queue. + pub bdring: FXmacBdRing, +} + +/// PHY interface mode definitions. +/// +/// Specifies the physical layer interface type used for communication +/// between the MAC controller and the PHY chip. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum FXmacPhyInterface { + /// SGMII (Serial Gigabit Media Independent Interface). + FXMAC_PHY_INTERFACE_MODE_SGMII = 0, + /// RMII (Reduced Media Independent Interface). + FXMAC_PHY_INTERFACE_MODE_RMII = 1, + /// RGMII (Reduced Gigabit Media Independent Interface). + FXMAC_PHY_INTERFACE_MODE_RGMII = 2, + /// XGMII (10 Gigabit Media Independent Interface). + FXMAC_PHY_INTERFACE_MODE_XGMII = 3, + /// USXGMII (Universal Serial 10 Gigabit Media Independent Interface). + FXMAC_PHY_INTERFACE_MODE_USXGMII = 4, + /// 5GBASE-R interface mode. + FXMAC_PHY_INTERFACE_MODE_5GBASER = 5, + /// 2500BASE-X interface mode. + FXMAC_PHY_INTERFACE_MODE_2500BASEX = 6, +} + +/// Reads a memory-mapped register via a physical address. +/// +/// The address is translated using the platform's [`KernelFunc::phys_to_virt`] +/// implementation before a volatile read is performed. +pub fn read_reg(src: *const T) -> T { + unsafe { + core::ptr::read_volatile( + crate_interface::call_interface!(crate::KernelFunc::phys_to_virt(src as usize)) + as *const T, + ) + } +} + +/// Writes a value to a memory-mapped register via a physical address. +/// +/// The address is translated using the platform's [`KernelFunc::phys_to_virt`] +/// implementation before a volatile write is performed. +pub fn write_reg(dst: *mut T, value: T) { + unsafe { + core::ptr::write_volatile( + crate_interface::call_interface!(crate::KernelFunc::phys_to_virt(dst as usize)) + as *mut T, + value, + ); + } +} + +/// Initializes the FXMAC Ethernet controller. +/// +/// This function performs complete hardware initialization of the FXMAC controller, +/// including: +/// - Hardware reset and configuration +/// - PHY initialization and link establishment +/// - DMA buffer descriptor ring setup +/// - Interrupt handler registration +/// - MAC address configuration +/// +/// # Arguments +/// +/// * `hwaddr` - A 6-byte MAC address to assign to the controller. +/// +/// # Returns +/// +/// A static mutable reference to the initialized [`FXmac`] instance. +/// +/// # Panics +/// +/// This function may panic if: +/// - PHY initialization fails +/// - DMA memory allocation fails +/// +/// # Example +/// +/// ```ignore +/// // Define the MAC address +/// let hwaddr: [u8; 6] = [0x55, 0x44, 0x33, 0x22, 0x11, 0x00]; +/// +/// // Initialize the controller +/// let fxmac = xmac_init(&hwaddr); +/// +/// // The controller is now ready for packet transmission and reception +/// assert_eq!(fxmac.is_started, FT_COMPONENT_IS_STARTED); +/// ``` +/// +/// # Note +/// +/// The returned reference has `'static` lifetime and is stored in a global +/// atomic pointer. Only one instance should be active at a time. +pub fn xmac_init(hwaddr: &[u8; 6]) -> &'static mut FXmac { + /* + FXmacConfig mac_config: + mac_config.instance_id=0, + mac_config.base_address=0x3200c000, + mac_config.extral_mode_base=0x3200dc00, + mac_config.extral_loopback_base=0x3200dc04, + mac_config.interface=0, + mac_config.speed=100, + mac_config.duplex=1, + mac_config.auto_neg=0, + mac_config.pclk_hz=50000000, + mac_config.max_queue_num=4, + mac_config.tx_queue_id=0, + mac_config.rx_queue_id=0 + mac_config.hotplug_irq_num=83, + mac_config.dma_brust_length=16, + mac_config.network_default_config=0x37f0, + mac_config.queue_irq_num[0]=87, + mac_config.caps=0 + */ + let mut mac_config: FXmacConfig = FXmacConfig { + instance_id: FXMAC0_ID, + base_address: FXMAC0_BASE_ADDR as u64, + extral_mode_base: FXMAC0_MODE_SEL_BASE_ADDR as u64, + extral_loopback_base: FXMAC0_LOOPBACK_SEL_BASE_ADDR as u64, + interface: FXmacPhyInterface::FXMAC_PHY_INTERFACE_MODE_SGMII, + speed: 100, + duplex: 1, + auto_neg: 0, + pclk_hz: FXMAC0_PCLK, + max_queue_num: 4, // .max_queue_num = 16 + tx_queue_id: 0, + rx_queue_id: 0, + hotplug_irq_num: FXMAC0_HOTPLUG_IRQ_NUM, + dma_brust_length: 16, + network_default_config: FXMAC_DEFAULT_OPTIONS, + queue_irq_num: [ + FXMAC0_QUEUE0_IRQ_NUM, + FXMAC0_QUEUE1_IRQ_NUM, + FXMAC0_QUEUE2_IRQ_NUM, + FXMAC0_QUEUE3_IRQ_NUM, + ], + caps: 0, + mac: *hwaddr, + }; + + let mut xmac = FXmac { + config: mac_config, + is_ready: FT_COMPONENT_IS_READY, + is_started: 0, + link_status: FXMAC_LINKDOWN, + options: 0, + mask: 0, + caps: 0, + lwipport: FXmacLwipPort { + buffer: FXmacNetifBuffer::default(), + feature: FXMAC_LWIP_PORT_CONFIG_MULTICAST_ADDRESS_FILITER, + hwaddr: *hwaddr, + recv_flg: 0, + }, + tx_bd_queue: FXmacQueue { + queue_id: 0, + bdring: FXmacBdRing::default(), + }, + rx_bd_queue: FXmacQueue { + queue_id: 0, + bdring: FXmacBdRing::default(), + }, + moudle_id: 0, + max_mtu_size: 0, + max_frame_size: 0, + phy_address: 0, + rxbuf_mask: 0, + }; + + // xmac_config: interface=FXMAC_PHY_INTERFACE_MODE_SGMII, autonegotiation=0, phy_speed=FXMAC_PHY_SPEED_100M, phy_duplex=FXMAC_PHY_FULL_DUPLEX + // FXmacDmaReset, moudle_id=12, max_frame_size=1518, max_queue_num=4 (或16), dma_brust_length=16 + // network_default_config = 0x37f0, base_address=0x3200c000,FXMAC_RXBUF_HASH_MASK: GENMASK(30, 29)= 0x60000000 (0b 0110_0000_0000_0000_0000000000000000) + + // mii_interface = 1 = FXMAC_LWIP_PORT_INTERFACE_SGMII; + + // FXmacLwipPortInit(): + /* step 1: initialize instance */ + /* step 2: depend on config set some options : JUMBO / IGMP */ + /* step 3: FXmacSelectClk */ + /* step 4: FXmacInitInterface */ + /* step 5: initialize phy */ + /* step 6: initialize dma */ + /* step 7: initialize interrupt */ + /* step 8: start mac */ + + let mut status: u32 = 0; + + // Reset the hardware and set default options + //xmac.link_status = FXMAC_LINKDOWN; + //xmac.is_ready = FT_COMPONENT_IS_READY; + + FXmacReset(&mut xmac); + + // irq_handler = (FXmacIrqHandler)FXmacIrqStubHandler; + // interrupts bit mask + xmac.mask = FXMAC_IXR_LINKCHANGE_MASK + | FXMAC_IXR_TX_ERR_MASK + | FXMAC_IXR_RX_ERR_MASK + | FXMAC_IXR_RXCOMPL_MASK; // FXMAC_INTR_MASK // 这里打开收包中断,关闭发包中断 + + if (xmac.config.caps & FXMAC_CAPS_TAILPTR) != 0 { + FXmacSetOptions(&mut xmac, FXMAC_TAIL_PTR_OPTION, 0); + xmac.mask &= !FXMAC_IXR_TXUSED_MASK; + } + + // xmac.lwipport.feature = LWIP_PORT_MODE_MULTICAST_ADDRESS_FILITER; + FxmacFeatureSetOptions(xmac.lwipport.feature, &mut xmac); + + status = FXmacSetMacAddress(&xmac.lwipport.hwaddr, 0); + + //mac_config.interface = FXMAC_PHY_INTERFACE_MODE_SGMII; + + if xmac.config.interface != FXmacPhyInterface::FXMAC_PHY_INTERFACE_MODE_USXGMII { + /* initialize phy */ + status = FXmacPhyInit(&mut xmac, XMAC_PHY_RESET_ENABLE); + if status != 0 { + warn!("FXmacPhyInit is error"); + } + } else { + info!("interface == FXMAC_PHY_INTERFACE_MODE_USXGMII"); + } + + FXmacSelectClk(&mut xmac); + FXmacInitInterface(&mut xmac); + + // initialize dma + let mut dmacrreg: u32 = read_reg((xmac.config.base_address + FXMAC_DMACR_OFFSET) as *const u32); + dmacrreg &= !(FXMAC_DMACR_BLENGTH_MASK); + dmacrreg |= FXMAC_DMACR_INCR16_AHB_AXI_BURST; /* Attempt to use bursts of up to 16. */ + write_reg( + (xmac.config.base_address + FXMAC_DMACR_OFFSET) as *mut u32, + dmacrreg, + ); + + FXmacInitDma(&mut xmac); + + // initialize interrupt + // 网卡中断初始化设置 + FXmacSetupIsr(&mut xmac); + + // end of FXmacLwipPortInit() + + if (xmac.lwipport.feature & FXMAC_LWIP_PORT_CONFIG_UNICAST_ADDRESS_FILITER) != 0 { + debug!("Set unicast hash table"); + FXmac_SetHash(&mut xmac, hwaddr); + } + + /* 注册了 lwip_port->ops: + ethernetif_link_detect() + ethernetif_input() + ethernetif_deinit() + ethernetif_start() -> FXmacLwipPortStart() -> FXmacStart() + ethernetif_debug() + */ + + // ethernetif_start() + // start mac + FXmacStart(&mut xmac); + + // 开始发包的函数:FXmacLwipPortTx()->FXmacSgsend() -> FXmacSendHandler() -> FXmacProcessSentBds() + // 触发中断函数:FXmacIntrHandler() + // 收包handle: FXmacRecvIsrHandler()->FXmacRecvHandler + + //XMAC.store(Box::into_raw(Box::new(xmac)), Ordering::Relaxed); + + // Box::leak方法,它可以将一个变量从内存中泄漏, 将其变为'static生命周期,因此可以赋值给全局静态变量 + let xmac_ref = Box::leak(Box::new(xmac)); + XMAC.store(xmac_ref as *mut FXmac, Ordering::Relaxed); + + xmac_ref +} + +/// Starts the Ethernet controller. +/// +/// This enables TX/RX paths based on configured options, starts DMA channels, +/// and enables the device interrupt mask. +/// +/// # Panics +/// +/// Panics if the instance is not in the ready state. +pub fn FXmacStart(instance_p: &mut FXmac) { + assert!(instance_p.is_ready == FT_COMPONENT_IS_READY); + + /* clear any existed int status */ + write_reg( + (instance_p.config.base_address + FXMAC_ISR_OFFSET) as *mut u32, + FXMAC_IXR_ALL_MASK, + ); + + /* Enable transmitter if not already enabled */ + if (instance_p.config.network_default_config & FXMAC_TRANSMITTER_ENABLE_OPTION) != 0 { + let reg_val = + read_reg((instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *const u32); + if (reg_val & FXMAC_NWCTRL_TXEN_MASK) == 0 { + write_reg( + (instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *mut u32, + reg_val | FXMAC_NWCTRL_TXEN_MASK, + ); + } + } + + /* Enable receiver if not already enabled */ + if (instance_p.config.network_default_config & FXMAC_RECEIVER_ENABLE_OPTION) != 0 { + let reg_val = + read_reg((instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *const u32); + info!("Enable receiver, FXMAC_NWCTRL_OFFSET = {:#x}", reg_val); + if (reg_val & FXMAC_NWCTRL_RXEN_MASK) == 0 { + write_reg( + (instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *mut u32, + reg_val | FXMAC_NWCTRL_RXEN_MASK, + ); + } + } + info!( + "FXMAC_NWCTRL_OFFSET = {:#x}", + read_reg((instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *const u32) + ); + + info!("Enable TX and RX by Mask={:#x}", instance_p.mask); + + // 使能网卡中断: Enable TX and RX interrupt + //FXMAC_INT_ENABLE(instance_p, instance_p->mask); + // Enable interrupts specified in 'Mask'. The corresponding interrupt for each bit set to 1 in 'Mask', will be enabled. + write_reg( + (instance_p.config.base_address + FXMAC_IER_OFFSET) as *mut u32, + instance_p.mask & FXMAC_IXR_ALL_MASK, + ); + + // Mark as started + instance_p.is_started = FT_COMPONENT_IS_STARTED; +} + +/// Gracefully stops the Ethernet MAC. +/// +/// This disables interrupts, stops DMA channels, and shuts down TX/RX paths. +/// +/// # Panics +/// +/// Panics if the instance is not in the ready state. +pub fn FXmacStop(instance_p: &mut FXmac) { + assert!(instance_p.is_ready == FT_COMPONENT_IS_READY); + // Disable all interrupts + write_reg( + (instance_p.config.base_address + FXMAC_IDR_OFFSET) as *mut u32, + FXMAC_IXR_ALL_MASK, + ); + + /* Disable the receiver & transmitter */ + let mut reg_val: u32 = + read_reg((instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *const u32); + reg_val &= !FXMAC_NWCTRL_RXEN_MASK; + reg_val &= !FXMAC_NWCTRL_TXEN_MASK; + write_reg( + (instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *mut u32, + reg_val, + ); + + // Mark as stopped + instance_p.is_started = 0; +} + +/* + * Perform a graceful reset of the Ethernet MAC. Resets the DMA channels, the + * transmitter, and the receiver. + * + * Steps to reset + * - Stops transmit and receive channels + * - Stops DMA + * - Configure transmit and receive buffer size to default + * - Clear transmit and receive status register and counters + * - Clear all interrupt sources + * - Clear phy (if there is any previously detected) address + * - Clear MAC addresses (1-4) as well as Type IDs and hash value + * + */ + +fn FXmacReset(instance_p: &mut FXmac) { + let mut mac_addr: [u8; 6] = [0; 6]; + + /* Stop the device and reset hardware */ + FXmacStop(instance_p); + + // Module identification number + // instance_p->moudle_id = 12 + instance_p.moudle_id = (read_reg((FXMAC_IOBASE + FXMAC_REVISION_REG_OFFSET) as *const u32) + & FXMAC_IDENTIFICATION_MASK) + >> 16; + info!( + "FXmacReset, Got Moudle IDENTIFICATION: {}", + instance_p.moudle_id + ); + + instance_p.max_mtu_size = FXMAC_MTU; + instance_p.max_frame_size = FXMAC_MAX_FRAME_SIZE; + instance_p.config.max_queue_num = 16; + instance_p.config.dma_brust_length = 16; + instance_p.config.network_default_config = FXMAC_DEFAULT_OPTIONS; + + instance_p.config.pclk_hz = FXMAC0_PCLK; // 50000000 + + let netctrl = + (FXMAC_NWCTRL_STATCLR_MASK & !FXMAC_NWCTRL_LOOPBACK_LOCAL_MASK) | FXMAC_NWCTRL_MDEN_MASK; + write_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *mut u32, netctrl); + + FXmacConfigureCaps(instance_p); + + // mdio clock division + let mut w_reg: u32 = FXmacClkDivGet(instance_p); + // DMA bus width, DMA位宽为128 + w_reg |= FXmacDmaWidth(instance_p.moudle_id); + write_reg((FXMAC_IOBASE + FXMAC_NWCFG_OFFSET) as *mut u32, w_reg); + + FXmacDmaReset(instance_p); + + // This register, when read provides details of the status of the receive path. + write_reg( + (FXMAC_IOBASE + FXMAC_RXSR_OFFSET) as *mut u32, + FXMAC_SR_ALL_MASK, + ); + + // write 1 ro the relavant bit location disable that particular interrupt + write_reg( + (FXMAC_IOBASE + FXMAC_IDR_OFFSET) as *mut u32, + FXMAC_IXR_ALL_MASK, + ); + + let reg_val: u32 = read_reg((FXMAC_IOBASE + FXMAC_ISR_OFFSET) as *const u32); + write_reg((FXMAC_IOBASE + FXMAC_ISR_OFFSET) as *mut u32, reg_val); + + write_reg( + (FXMAC_IOBASE + FXMAC_TXSR_OFFSET) as *mut u32, + FXMAC_SR_ALL_MASK, + ); + + FXmacClearHash(); + + // set default mac address + for i in 0..4 { + FXmacSetMacAddress(&mac_addr, i); + FXmacGetMacAddress(&mut mac_addr, i); + FXmacSetTypeIdCheck(0, i); + } + + /* clear all counters */ + for i in 0..((FXMAC_LAST_OFFSET - FXMAC_OCTTXL_OFFSET) / 4) { + read_reg((FXMAC_IOBASE + FXMAC_OCTTXL_OFFSET + (i * 4)) as *mut u32); + } + + /* Sync default options with hardware but leave receiver and + * transmitter disabled. They get enabled with FXmacStart() if + * FXMAC_TRANSMITTER_ENABLE_OPTION and FXMAC_RECEIVER_ENABLE_OPTION are set. + */ + let options = instance_p.config.network_default_config + & !(FXMAC_TRANSMITTER_ENABLE_OPTION | FXMAC_RECEIVER_ENABLE_OPTION); + FXmacSetOptions(instance_p, options, 0); + let options = !instance_p.config.network_default_config; + FXmacClearOptions(instance_p, options, 0); +} + +fn FXmacDmaReset(instance_p: &mut FXmac) { + let max_frame_size: u32 = instance_p.max_frame_size; + + let mut dmacfg: u32 = 0; + //let max_queue_num = 16; + //let dma_brust_length = 16; + + let mut rx_buf_size: u32 = max_frame_size / FXMAC_RX_BUF_UNIT; + rx_buf_size += if (max_frame_size % FXMAC_RX_BUF_UNIT) != 0 { + 1 + } else { + 0 + }; /* roundup */ + + // moudle_id=12 + if (instance_p.moudle_id >= 2) { + for queue in 0..instance_p.config.max_queue_num { + dmacfg = 0; + + // 设置发包/收包 buffer队列的基地址 + FXmacSetQueuePtr(0, queue as u8, FXMAC_SEND); + FXmacSetQueuePtr(0, queue as u8, FXMAC_RECV); + + if queue != 0 { + write_reg( + (FXMAC_IOBASE + FXMAC_RXBUFQX_SIZE_OFFSET(queue as u64)) as *mut u32, + rx_buf_size, + ); + } else + /* queue is 0 */ + { + dmacfg |= (FXMAC_DMACR_RXBUF_MASK & (rx_buf_size << FXMAC_DMACR_RXBUF_SHIFT)); + } + } + + dmacfg |= (instance_p.config.dma_brust_length & FXMAC_DMACR_BLENGTH_MASK); + + dmacfg &= !FXMAC_DMACR_ENDIAN_MASK; + dmacfg &= !FXMAC_DMACR_SWAP_MANAGEMENT_MASK; /* 选择小端 */ + + dmacfg &= !FXMAC_DMACR_TCPCKSUM_MASK; /* close transmitter checksum generation engine */ + + dmacfg &= !FXMAC_DMACR_ADDR_WIDTH_64; + dmacfg |= FXMAC_DMACR_RXSIZE_MASK | FXMAC_DMACR_TXSIZE_MASK; + /* + set this bit can enable auto discard rx frame when lack of receive source, + which avoid endless rx buffer not available error intrrupts. + */ + dmacfg |= FXMAC_DMACR_ORCE_DISCARD_ON_ERR_MASK; /* force_discard_on_rx_err */ + + dmacfg |= FXMAC_DMACR_ADDR_WIDTH_64; // Just for aarch64 + } else { + FXmacSetQueuePtr(0, 0, FXMAC_SEND); + FXmacSetQueuePtr(0, 0, FXMAC_RECV); + dmacfg |= (FXMAC_DMACR_RXBUF_MASK & (rx_buf_size << FXMAC_DMACR_RXBUF_SHIFT)); + dmacfg |= (instance_p.config.dma_brust_length & FXMAC_DMACR_BLENGTH_MASK); + + dmacfg &= !FXMAC_DMACR_ENDIAN_MASK; + dmacfg &= !FXMAC_DMACR_SWAP_MANAGEMENT_MASK; /* 选择小端 */ + + dmacfg &= !FXMAC_DMACR_TCPCKSUM_MASK; /* close transmitter checksum generation engine */ + + dmacfg &= !FXMAC_DMACR_ADDR_WIDTH_64; + dmacfg |= FXMAC_DMACR_RXSIZE_MASK | FXMAC_DMACR_TXSIZE_MASK; + /* + set this bit can enable auto discard rx frame when lack of receive source, + which avoid endless rx buffer not available error intrrupts. + */ + dmacfg |= FXMAC_DMACR_ORCE_DISCARD_ON_ERR_MASK; /* force_discard_on_rx_err */ + dmacfg |= FXMAC_DMACR_ADDR_WIDTH_64; // Just for aarch64 + } + + write_reg((FXMAC_IOBASE + FXMAC_DMACR_OFFSET) as *mut u32, dmacfg); +} + +fn FXmacDmaWidth(moudle_id: u32) -> u32 { + if moudle_id < 2 { + return FXMAC_NWCFG_BUS_WIDTH_32_MASK; + } + + let read_regs = read_reg((FXMAC_IOBASE + FXMAC_DESIGNCFG_DEBUG1_OFFSET) as *const u32); + match ((read_regs & FXMAC_DESIGNCFG_DEBUG1_BUS_WIDTH_MASK) >> 25) { + 4 => { + info!("bus width is 128"); + FXMAC_NWCFG_BUS_WIDTH_128_MASK + } + 2 => { + info!("bus width is 64"); + FXMAC_NWCFG_BUS_WIDTH_64_MASK + } + _ => { + info!("bus width is 32"); + FXMAC_NWCFG_BUS_WIDTH_32_MASK + } + } +} + +fn FxmacFeatureSetOptions(feature: u32, xmac_p: &mut FXmac) { + let mut options: u32 = 0; + + if (feature & FXMAC_LWIP_PORT_CONFIG_JUMBO) != 0 { + info!("FXMAC_JUMBO_ENABLE_OPTION is ok"); + options |= FXMAC_JUMBO_ENABLE_OPTION; + } + + if (feature & FXMAC_LWIP_PORT_CONFIG_UNICAST_ADDRESS_FILITER) != 0 { + info!("FXMAC_UNICAST_OPTION is ok"); + options |= FXMAC_UNICAST_OPTION; + } + + if (feature & FXMAC_LWIP_PORT_CONFIG_MULTICAST_ADDRESS_FILITER) != 0 { + info!("FXMAC_MULTICAST_OPTION is ok"); + options |= FXMAC_MULTICAST_OPTION; + } + /* enable copy all frames */ + if (feature & FXMAC_LWIP_PORT_CONFIG_COPY_ALL_FRAMES) != 0 { + info!("FXMAC_PROMISC_OPTION is ok"); + options |= FXMAC_PROMISC_OPTION; + } + /* close fcs check */ + if (feature & FXMAC_LWIP_PORT_CONFIG_CLOSE_FCS_CHECK) != 0 { + info!("FXMAC_FCS_STRIP_OPTION is ok"); + options |= FXMAC_FCS_STRIP_OPTION; + } + + FXmacSetOptions(xmac_p, options, 0); +} + +/// Sets the start address of the transmit/receive buffer queue. +/// +/// # Arguments +/// +/// * `queue_p` - Physical base address of the queue ring. +/// * `queue_num` - Queue index to configure. +/// * `direction` - [`FXMAC_SEND`] or [`FXMAC_RECV`]. +/// +/// # Note +/// +/// The buffer queue address must be configured before calling [`FXmacStart`]. +pub fn FXmacSetQueuePtr(queue_p: u64, queue_num: u8, direction: u32) { + //assert!(instance_p.is_ready == FT_COMPONENT_IS_READY); + // If already started, then just return + + let flag_queue_p = if queue_p == 0 { 1 } else { 0 }; + let FXMAC_QUEUE_REGISTER_OFFSET = + |base_addr: u64, queue_id: u64| (base_addr + (queue_id - 1) * 4); + + if queue_num == 0 { + if direction == FXMAC_SEND { + /* set base start address of TX buffer queue (tx buffer descriptor list) */ + write_reg( + (FXMAC_IOBASE + FXMAC_TXQBASE_OFFSET) as *mut u32, + ((queue_p & ULONG64_LO_MASK) | flag_queue_p) as u32, + ); + } else { + /* set base start address of RX buffer queue (rx buffer descriptor list) */ + write_reg( + (FXMAC_IOBASE + FXMAC_RXQBASE_OFFSET) as *mut u32, + ((queue_p & ULONG64_LO_MASK) | flag_queue_p) as u32, + ); + } + } else if direction == FXMAC_SEND { + write_reg( + (FXMAC_IOBASE + FXMAC_QUEUE_REGISTER_OFFSET(FXMAC_TXQ1BASE_OFFSET, queue_num as u64)) + as *mut u32, + ((queue_p & ULONG64_LO_MASK) | flag_queue_p) as u32, + ); + } else { + write_reg( + (FXMAC_IOBASE + FXMAC_QUEUE_REGISTER_OFFSET(FXMAC_RXQ1BASE_OFFSET, queue_num as u64)) + as *mut u32, + ((queue_p & ULONG64_LO_MASK) | flag_queue_p) as u32, + ); + } + + if direction == FXMAC_SEND + // Only for aarch64 + { + /* Set the MSB of TX Queue start address */ + write_reg( + (FXMAC_IOBASE + FXMAC_MSBBUF_TXQBASE_OFFSET) as *mut u32, + ((queue_p & ULONG64_HI_MASK) >> 32) as u32, + ); + } else { + /* Set the MSB of RX Queue start address */ + write_reg( + (FXMAC_IOBASE + FXMAC_MSBBUF_RXQBASE_OFFSET) as *mut u32, + ((queue_p & ULONG64_HI_MASK) >> 32) as u32, + ); + } +} + +fn FXmacConfigureCaps(instance_p: &mut FXmac) { + instance_p.caps = 0; + let read_regs = read_reg((FXMAC_IOBASE + FXMAC_DESIGNCFG_DEBUG1_OFFSET) as *const u32); + if (read_regs & FXMAC_DESIGNCFG_DEBUG1_BUS_IRQCOR_MASK) == 0 { + instance_p.caps |= FXMAC_CAPS_ISR_CLEAR_ON_WRITE; + info!( + "Design ConfigReg1: {:#x} Has FXMAC_CAPS_ISR_CLEAR_ON_WRITE feature", + read_regs + ); + } +} + +fn FXmacClkDivGet(instance_p: &mut FXmac) -> u32 { + // moudle_id=12 + // let pclk_hz = 50000000; + let pclk_hz = instance_p.config.pclk_hz; // FXMAC0_PCLK; + + if (pclk_hz <= 20000000) { + FXMAC_NWCFG_CLOCK_DIV8_MASK + } else if (pclk_hz <= 40000000) { + FXMAC_NWCFG_CLOCK_DIV16_MASK + } else if (pclk_hz <= 80000000) { + FXMAC_NWCFG_CLOCK_DIV32_MASK + } else if (instance_p.moudle_id >= 2) { + if (pclk_hz <= 120000000) { + FXMAC_NWCFG_CLOCK_DIV48_MASK + } else if (pclk_hz <= 160000000) { + FXMAC_NWCFG_CLOCK_DIV64_MASK + } else if (pclk_hz <= 240000000) { + FXMAC_NWCFG_CLOCK_DIV96_MASK + } else if (pclk_hz <= 320000000) { + FXMAC_NWCFG_CLOCK_DIV128_MASK + } else { + FXMAC_NWCFG_CLOCK_DIV224_MASK + } + } else { + FXMAC_NWCFG_CLOCK_DIV64_MASK + } +} + +/** + * Set options for the driver/device. The driver should be stopped with + * FXmacStop() before changing options. + */ +fn FXmacSetOptions(instance_p: &mut FXmac, options: u32, queue_num: u32) -> u32 { + let mut reg: u32 = 0; /* Generic register contents */ + let mut reg_netcfg: u32 = 0; /* Reflects original contents of NET_CONFIG */ + let mut reg_new_netcfg: u32 = 0; /* Reflects new contents of NET_CONFIG */ + let mut status: u32 = 0; + + //let is_started = 0; + + info!( + "FXmacSetOptions, is_started={}, options={}, queue_num={}, max_queue_num={}", + instance_p.is_started, options, queue_num, instance_p.config.max_queue_num + ); + + /* Be sure device has been stopped */ + if instance_p.is_started == FT_COMPONENT_IS_STARTED { + status = 9; //FXMAC_ERR_MAC_IS_PROCESSING; + error!("FXMAC is processing when calling FXmacSetOptions function"); + } else { + /* Many of these options will change the NET_CONFIG registers. + * To reduce the amount of IO to the device, group these options here + * and change them all at once. + */ + + /* Grab current register contents */ + reg_netcfg = read_reg((FXMAC_IOBASE + FXMAC_NWCFG_OFFSET) as *const u32); + + reg_new_netcfg = reg_netcfg; + + /* + * It is configured to max 1536. + */ + if (options & FXMAC_FRAME1536_OPTION) != 0 { + reg_new_netcfg |= FXMAC_NWCFG_1536RXEN_MASK; + } + + /* Turn on VLAN packet only, only VLAN tagged will be accepted */ + if (options & FXMAC_VLAN_OPTION) != 0 { + reg_new_netcfg |= FXMAC_NWCFG_NVLANDISC_MASK; + } + + /* Turn on FCS stripping on receive packets */ + if (options & FXMAC_FCS_STRIP_OPTION) != 0 { + reg_new_netcfg |= FXMAC_NWCFG_FCS_REMOVE_MASK; + } + + /* Turn on length/type field checking on receive packets */ + if (options & FXMAC_LENTYPE_ERR_OPTION) != 0 { + reg_new_netcfg |= FXMAC_NWCFG_LENGTH_FIELD_ERROR_FRAME_DISCARD_MASK; + } + + /* Turn on flow control */ + if (options & FXMAC_FLOW_CONTROL_OPTION) != 0 { + reg_new_netcfg |= FXMAC_NWCFG_PAUSE_ENABLE_MASK; + } + + /* Turn on promiscuous frame filtering (all frames are received) */ + if (options & FXMAC_PROMISC_OPTION) != 0 { + reg_new_netcfg |= FXMAC_NWCFG_COPYALLEN_MASK; + } + + /* Allow broadcast address reception */ + if (options & FXMAC_BROADCAST_OPTION) != 0 { + reg_new_netcfg &= !FXMAC_NWCFG_BCASTDI_MASK; + } + + /* Allow multicast address filtering */ + if (options & FXMAC_MULTICAST_OPTION) != 0 { + reg_new_netcfg |= FXMAC_NWCFG_MCASTHASHEN_MASK; + } + + if (options & FXMAC_UNICAST_OPTION) != 0 { + reg_new_netcfg |= FXMAC_NWCFG_UCASTHASHEN_MASK; + } + + if (options & FXMAC_TAIL_PTR_OPTION) != 0 { + write_reg((FXMAC_IOBASE + FXMAC_TAIL_ENABLE) as *mut u32, 0x80000001); + } + + /* enable RX checksum offload */ + if (options & FXMAC_RX_CHKSUM_ENABLE_OPTION) != 0 { + reg_new_netcfg |= FXMAC_NWCFG_RXCHKSUMEN_MASK; + } + + /* Enable jumbo frames */ + if (options & FXMAC_JUMBO_ENABLE_OPTION) != 0 { + instance_p.max_mtu_size = FXMAC_MTU_JUMBO; + instance_p.max_frame_size = FXMAC_MAX_FRAME_SIZE_JUMBO; + + reg_new_netcfg |= FXMAC_NWCFG_JUMBO_MASK; + + write_reg( + (FXMAC_IOBASE + FXMAC_JUMBOMAXLEN_OFFSET) as *mut u32, + FXMAC_MAX_FRAME_SIZE_JUMBO, + ); + + write_reg( + (FXMAC_IOBASE + FXMAC_TXQSEGALLOC_QLOWER_OFFSET) as *mut u32, + FXMAC_TXQSEGALLOC_QLOWER_JUMBO_MASK, + ); + + if queue_num == 0 { + let mut rx_buf_size: u32 = 0; + reg = read_reg((FXMAC_IOBASE + FXMAC_DMACR_OFFSET) as *const u32); + + reg &= !FXMAC_DMACR_RXBUF_MASK; + + rx_buf_size = instance_p.max_frame_size / FXMAC_RX_BUF_UNIT; + rx_buf_size += if (instance_p.max_frame_size % FXMAC_RX_BUF_UNIT) != 0 { + 1 + } else { + 0 + }; + + reg |= (rx_buf_size << FXMAC_DMACR_RXBUF_SHIFT) & FXMAC_DMACR_RXBUF_MASK; + write_reg((FXMAC_IOBASE + FXMAC_DMACR_OFFSET) as *mut u32, reg); + } else if queue_num < instance_p.config.max_queue_num { + let mut rx_buf_size: u32 = 0; + rx_buf_size = instance_p.max_frame_size / FXMAC_RX_BUF_UNIT; + rx_buf_size += if (instance_p.max_frame_size % FXMAC_RX_BUF_UNIT) != 0 { + 1 + } else { + 0 + }; + + write_reg( + (FXMAC_IOBASE + FXMAC_RXBUFQX_SIZE_OFFSET(queue_num as u64)) as *mut u32, + rx_buf_size & FXMAC_RXBUFQX_SIZE_MASK, + ); + } + } + + if (options & FXMAC_SGMII_ENABLE_OPTION) != 0 { + reg_new_netcfg |= (FXMAC_NWCFG_SGMII_MODE_ENABLE_MASK | FXMAC_NWCFG_PCSSEL_MASK); + } + + if (options & FXMAC_LOOPBACK_NO_MII_OPTION) != 0 { + reg = read_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *const u32); + reg |= FXMAC_NWCTRL_LOOPBACK_LOCAL_MASK; + write_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *mut u32, reg); + } + + if (options & FXMAC_LOOPBACK_USXGMII_OPTION) != 0 { + write_reg((FXMAC_IOBASE + FXMAC_TEST_CONTROL_OFFSET) as *mut u32, 2); + } + + /* Officially change the NET_CONFIG registers if it needs to be + * modified. + */ + if (reg_netcfg != reg_new_netcfg) { + write_reg( + (FXMAC_IOBASE + FXMAC_NWCFG_OFFSET) as *mut u32, + reg_new_netcfg, + ); + } + + /* Enable TX checksum offload */ + if (options & FXMAC_TX_CHKSUM_ENABLE_OPTION) != 0 { + reg = read_reg((FXMAC_IOBASE + FXMAC_DMACR_OFFSET) as *const u32); + reg |= FXMAC_DMACR_TCPCKSUM_MASK; + write_reg((FXMAC_IOBASE + FXMAC_DMACR_OFFSET) as *mut u32, reg); + } + + /* Enable transmitter */ + if (options & FXMAC_TRANSMITTER_ENABLE_OPTION) != 0 { + reg = read_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *const u32); + reg |= FXMAC_NWCTRL_TXEN_MASK; + write_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *mut u32, reg); + } + + /* Enable receiver */ + if (options & FXMAC_RECEIVER_ENABLE_OPTION) != 0 { + reg = read_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *const u32); + reg |= FXMAC_NWCTRL_RXEN_MASK; + + write_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *mut u32, reg); + } + + /* The remaining options not handled here are managed elsewhere in the + * driver. No register modifications are needed at this time. Reflecting + * the option in instance_p->options is good enough for now. + */ + + /* Set options word to its new value */ + instance_p.options |= options; + + status = 0; // FT_SUCCESS; + } + + status +} + +/// Clear options for the driver/device +fn FXmacClearOptions(instance_p: &mut FXmac, options: u32, queue_num: u32) -> u32 { + let mut reg: u32 = 0; /* Generic */ + let mut reg_net_cfg: u32 = 0; /* Reflects original contents of NET_CONFIG */ + let mut reg_new_net_cfg: u32 = 0; /* Reflects new contents of NET_CONFIG */ + let mut status: u32 = 0; + + //let is_started = 0; + /* Be sure device has been stopped */ + if (instance_p.is_started == FT_COMPONENT_IS_STARTED) { + status = 9; //FXMAC_ERR_MAC_IS_PROCESSING + error!("FXMAC is processing when calling FXmacClearOptions function"); + } else { + /* Many of these options will change the NET_CONFIG registers. + * To reduce the amount of IO to the device, group these options here + * and change them all at once. + */ + /* Grab current register contents */ + reg_net_cfg = read_reg((FXMAC_IOBASE + FXMAC_NWCFG_OFFSET) as *const u32); + reg_new_net_cfg = reg_net_cfg; + /* There is only RX configuration!? + * It is configured in two different length, up to 1536 and 10240 bytes + */ + if (options & FXMAC_FRAME1536_OPTION) != 0 { + reg_new_net_cfg &= !FXMAC_NWCFG_1536RXEN_MASK; + } + + /* Turn off VLAN packet only */ + if (options & FXMAC_VLAN_OPTION) != 0 { + reg_new_net_cfg &= !FXMAC_NWCFG_NVLANDISC_MASK; + } + + /* Turn off FCS stripping on receive packets */ + if (options & FXMAC_FCS_STRIP_OPTION) != 0 { + reg_new_net_cfg &= !FXMAC_NWCFG_FCS_REMOVE_MASK; + } + + /* Turn off length/type field checking on receive packets */ + if (options & FXMAC_LENTYPE_ERR_OPTION) != 0 { + reg_new_net_cfg &= !FXMAC_NWCFG_LENGTH_FIELD_ERROR_FRAME_DISCARD_MASK; + } + + /* Turn off flow control */ + if (options & FXMAC_FLOW_CONTROL_OPTION) != 0 { + reg_new_net_cfg &= !FXMAC_NWCFG_PAUSE_ENABLE_MASK; + } + + /* Turn off promiscuous frame filtering (all frames are received) */ + if (options & FXMAC_PROMISC_OPTION) != 0 { + reg_new_net_cfg &= !FXMAC_NWCFG_COPYALLEN_MASK; + } + + /* Disallow broadcast address filtering => broadcast reception */ + if (options & FXMAC_BROADCAST_OPTION) != 0 { + reg_new_net_cfg |= FXMAC_NWCFG_BCASTDI_MASK; + } + + /* Disallow unicast address filtering */ + if (options & FXMAC_UNICAST_OPTION) != 0 { + reg_new_net_cfg &= !FXMAC_NWCFG_UCASTHASHEN_MASK; + } + + /* Disallow multicast address filtering */ + if (options & FXMAC_MULTICAST_OPTION) != 0 { + reg_new_net_cfg &= !FXMAC_NWCFG_MCASTHASHEN_MASK; + } + + if (options & FXMAC_TAIL_PTR_OPTION) != 0 { + write_reg((FXMAC_IOBASE + FXMAC_TAIL_ENABLE) as *mut u32, 0); + } + + /* Disable RX checksum offload */ + if (options & FXMAC_RX_CHKSUM_ENABLE_OPTION) != 0 { + reg_new_net_cfg &= !FXMAC_NWCFG_RXCHKSUMEN_MASK; + } + + /* Disable jumbo frames */ + if (options & FXMAC_JUMBO_ENABLE_OPTION) != 0 + /* 恢复之前buffer 容量 */ + { + instance_p.max_mtu_size = FXMAC_MTU; + instance_p.max_frame_size = FXMAC_MAX_FRAME_SIZE; + + reg_new_net_cfg &= !FXMAC_NWCFG_JUMBO_MASK; + + reg = read_reg((FXMAC_IOBASE + FXMAC_DMACR_OFFSET) as *const u32); + + reg &= !FXMAC_DMACR_RXBUF_MASK; + + if queue_num == 0 { + let mut rx_buf_size: u32 = 0; + + reg = read_reg((FXMAC_IOBASE + FXMAC_DMACR_OFFSET) as *const u32); + reg &= !FXMAC_DMACR_RXBUF_MASK; + + rx_buf_size = instance_p.max_frame_size / FXMAC_RX_BUF_UNIT; + rx_buf_size += if instance_p.max_frame_size % FXMAC_RX_BUF_UNIT != 0 { + 1 + } else { + 0 + }; + + reg |= (rx_buf_size << FXMAC_DMACR_RXBUF_SHIFT) & FXMAC_DMACR_RXBUF_MASK; + + write_reg((FXMAC_IOBASE + FXMAC_DMACR_OFFSET) as *mut u32, reg); + } else if (queue_num < instance_p.config.max_queue_num) { + let mut rx_buf_size: u32 = 0; + rx_buf_size = instance_p.max_frame_size / FXMAC_RX_BUF_UNIT; + rx_buf_size += if (instance_p.max_frame_size % FXMAC_RX_BUF_UNIT) != 0 { + 1 + } else { + 0 + }; + + write_reg( + (FXMAC_IOBASE + FXMAC_RXBUFQX_SIZE_OFFSET(queue_num as u64)) as *mut u32, + rx_buf_size & FXMAC_RXBUFQX_SIZE_MASK, + ); + } + } + + if (options & FXMAC_SGMII_ENABLE_OPTION) != 0 { + reg_new_net_cfg &= !(FXMAC_NWCFG_SGMII_MODE_ENABLE_MASK | FXMAC_NWCFG_PCSSEL_MASK); + } + + if (options & FXMAC_LOOPBACK_NO_MII_OPTION) != 0 { + reg = read_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *const u32); + reg &= !FXMAC_NWCTRL_LOOPBACK_LOCAL_MASK; + write_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *mut u32, reg); + } + + if (options & FXMAC_LOOPBACK_USXGMII_OPTION) != 0 { + write_reg( + (FXMAC_IOBASE + FXMAC_TEST_CONTROL_OFFSET) as *mut u32, + read_reg((FXMAC_IOBASE + FXMAC_TEST_CONTROL_OFFSET) as *const u32) & !2, + ); + } + + /* Officially change the NET_CONFIG registers if it needs to be + * modified. + */ + if reg_net_cfg != reg_new_net_cfg { + write_reg( + (FXMAC_IOBASE + FXMAC_NWCFG_OFFSET) as *mut u32, + reg_new_net_cfg, + ); + } + + /* Disable TX checksum offload */ + if (options & FXMAC_TX_CHKSUM_ENABLE_OPTION) != 0 { + reg = read_reg((FXMAC_IOBASE + FXMAC_DMACR_OFFSET) as *const u32); + reg &= !FXMAC_DMACR_TCPCKSUM_MASK; + write_reg((FXMAC_IOBASE + FXMAC_DMACR_OFFSET) as *mut u32, reg); + } + + /* Disable transmitter */ + if (options & FXMAC_TRANSMITTER_ENABLE_OPTION) != 0 { + reg = read_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *const u32); + reg &= !FXMAC_NWCTRL_TXEN_MASK; + write_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *mut u32, reg); + } + + /* Disable receiver */ + if (options & FXMAC_RECEIVER_ENABLE_OPTION) != 0 { + reg = read_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *const u32); + reg &= !FXMAC_NWCTRL_RXEN_MASK; + write_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *mut u32, reg); + } + + /* The remaining options not handled here are managed elsewhere in the + * driver. No register modifications are needed at this time. Reflecting + * option in instance_p->options is good enough for now. + */ + + /* Set options word to its new value */ + instance_p.options &= !options; + + status = 0; // FT_SUCCESS + } + status +} + +/// Clear the Hash registers for the mac address pointed by address_ptr. +fn FXmacClearHash() { + write_reg((FXMAC_IOBASE + FXMAC_HASHL_OFFSET) as *mut u32, 0); + + /* write bits [63:32] in TOP */ + write_reg((FXMAC_IOBASE + FXMAC_HASHH_OFFSET) as *mut u32, 0); +} + +/// Sets the MAC address for the specified address slot. +/// +/// The device must be stopped before calling this function. +/// +/// # Arguments +/// +/// * `address_ptr` - 6-byte MAC address. +/// * `index` - Address slot index (0..FXMAC_MAX_MAC_ADDR). +/// +/// # Panics +/// +/// Panics if `index` is out of range. +pub fn FXmacSetMacAddress(address_ptr: &[u8; 6], index: u8) -> u32 { + let mut mac_addr: u32 = 0; + let aptr = address_ptr; + let index_loc: u8 = index; + let mut status: u32 = 0; + assert!( + (index_loc < FXMAC_MAX_MAC_ADDR as u8), + "index of Mac Address exceed {}", + FXMAC_MAX_MAC_ADDR + ); + + let is_started = 0; + /* Be sure device has been stopped */ + if is_started == FT_COMPONENT_IS_STARTED { + //status = FXMAC_ERR_MAC_IS_PROCESSING; + status = 9; + error!("FXMAC is processing when calling FXmacSetMacAddress function"); + } else { + /* Set the MAC bits [31:0] in BOT */ + mac_addr = aptr[0] as u32 + | ((aptr[1] as u32) << 8) + | ((aptr[2] as u32) << 16) + | ((aptr[3] as u32) << 24); + write_reg( + (FXMAC_IOBASE + FXMAC_GEM_SA1B as u64 + (index_loc * 8) as u64) as *mut u32, + mac_addr, + ); + + /* There are reserved bits in TOP so don't affect them */ + mac_addr = + read_reg((FXMAC_IOBASE + FXMAC_GEM_SA1T as u64 + (index_loc * 8) as u64) as *const u32); + mac_addr &= !FXMAC_GEM_SAB_MASK; + + /* Set MAC bits [47:32] in TOP */ + mac_addr |= aptr[4] as u32; + mac_addr |= (aptr[5] as u32) << 8; + + write_reg( + (FXMAC_IOBASE + FXMAC_GEM_SA1T as u64 + (index_loc * 8) as u64) as *mut u32, + mac_addr, + ); + + status = 0; // FT_SUCCESS + } + + status +} +/// Reads a MAC address from the specified address slot. +/// +/// # Arguments +/// +/// * `address_ptr` - Output buffer for the MAC address. +/// * `index` - Address slot index (0..FXMAC_MAX_MAC_ADDR). +/// +/// # Panics +/// +/// Panics if `index` is out of range. +pub fn FXmacGetMacAddress(address_ptr: &mut [u8; 6], index: u8) { + assert!((index as u32) < FXMAC_MAX_MAC_ADDR); + + let mut reg_value: u32 = + read_reg((FXMAC_IOBASE + FXMAC_GEM_SA1B as u64 + (index as u64 * 8)) as *const u32); + address_ptr[0] = reg_value as u8; + address_ptr[1] = (reg_value >> 8) as u8; + address_ptr[2] = (reg_value >> 16) as u8; + address_ptr[3] = (reg_value >> 24) as u8; + + reg_value = read_reg((FXMAC_IOBASE + FXMAC_GEM_SA1T as u64 + (index as u64 * 8)) as *const u32); + address_ptr[4] = (reg_value) as u8; + address_ptr[5] = (reg_value >> 8) as u8; +} + +/// Sets a 48-bit MAC address entry in the hash table. +/// +/// The device must be stopped before calling this function. +/// +/// # Arguments +/// +/// * `intance_p` - Mutable reference to the FXMAC instance. +/// * `mac_address` - The MAC address to hash. +/// +/// The hash address register is 64 bits long and takes up two locations in +/// the memory map. The least significant bits are stored in hash register +/// bottom and the most significant bits in hash register top. +/// +/// The unicast hash enable and the multicast hash enable bits in the network +/// configuration register enable the reception of hash matched frames. The +/// destination address is reduced to a 6 bit index into the 64 bit hash +/// register using the following hash function. The hash function is an XOR +/// of every sixth bit of the destination address. +pub fn FXmac_SetHash(intance_p: &mut FXmac, mac_address: &[u8; 6]) -> u32 { + let mut HashAddr: u32 = 0; + let mut Status: u32 = 0; + debug!("Set MAC: {:x?} in hash table", mac_address); + + // Check that the Ethernet address (MAC) is not 00:00:00:00:00:00 + assert!(!((mac_address[0] == 0) && (mac_address[5] == 0))); + assert!(intance_p.is_ready == FT_COMPONENT_IS_READY); + + /* Be sure device has been stopped */ + if (intance_p.is_started == FT_COMPONENT_IS_STARTED) { + error!("FXmac_SetHash failed: FXMAC_ERR_MAC_IS_PROCESSING"); + Status = 9; // FXMAC_ERR_MAC_IS_PROCESSING + } else { + let Temp1: u8 = (mac_address[0]) & 0x3F; + let Temp2: u8 = ((mac_address[0] >> 6) & 0x03) | ((mac_address[1] & 0x0F) << 2); + let Temp3: u8 = ((mac_address[1] >> 4) & 0x0F) | ((mac_address[2] & 0x3) << 4); + let Temp4: u8 = ((mac_address[2] >> 2) & 0x3F); + let Temp5: u8 = mac_address[3] & 0x3F; + let Temp6: u8 = ((mac_address[3] >> 6) & 0x03) | ((mac_address[4] & 0x0F) << 2); + let Temp7: u8 = ((mac_address[4] >> 4) & 0x0F) | ((mac_address[5] & 0x03) << 4); + let Temp8: u8 = ((mac_address[5] >> 2) & 0x3F); + + let Result: u32 = (Temp1 as u32) + ^ (Temp2 as u32) + ^ (Temp3 as u32) + ^ (Temp4 as u32) + ^ (Temp5 as u32) + ^ (Temp6 as u32) + ^ (Temp7 as u32) + ^ (Temp8 as u32); + + if (Result >= FXMAC_MAX_HASH_BITS) { + Status = 1; // FXMAC_ERR_INVALID_PARAM + } else { + if (Result < 32) { + HashAddr = + read_reg((intance_p.config.base_address + FXMAC_HASHL_OFFSET) as *const u32); + HashAddr |= 1 << Result; + write_reg( + (intance_p.config.base_address + FXMAC_HASHL_OFFSET) as *mut u32, + HashAddr, + ); + } else { + HashAddr = + read_reg((intance_p.config.base_address + FXMAC_HASHH_OFFSET) as *const u32); + HashAddr |= 1 << (Result - 32); + write_reg( + (intance_p.config.base_address + FXMAC_HASHH_OFFSET) as *mut u32, + HashAddr, + ); + } + Status = 0; + } + } + + Status +} + +/// Delete 48-bit MAC addresses in hash table. +/// The device must be stopped before calling this function. +pub fn FXmac_DeleteHash(intance_p: &mut FXmac, mac_address: &[u8; 6]) -> u32 { + let mut HashAddr: u32 = 0; + let mut Status: u32 = 0; + + assert!(intance_p.is_ready == FT_COMPONENT_IS_READY); + + /* Be sure device has been stopped */ + if (intance_p.is_started == FT_COMPONENT_IS_STARTED) { + Status = 9; // (FXMAC_ERR_MAC_IS_PROCESSING); + } else { + let mut Temp1: u8 = (mac_address[0]) & 0x3F; + let mut Temp2: u8 = ((mac_address[0] >> 6) & 0x03) | ((mac_address[1] & 0x0F) << 2); + let mut Temp3: u8 = ((mac_address[1] >> 4) & 0x0F) | ((mac_address[2] & 0x03) << 4); + let mut Temp4: u8 = ((mac_address[2] >> 2) & 0x3F); + let mut Temp5: u8 = (mac_address[3]) & 0x3F; + let mut Temp6: u8 = ((mac_address[3] >> 6) & 0x03) | ((mac_address[4] & 0x0F) << 2); + let mut Temp7: u8 = ((mac_address[4] >> 4) & 0x0F) | ((mac_address[5] & 0x03) << 4); + let mut Temp8: u8 = ((mac_address[5] >> 2) & 0x3F); + + let Result: u32 = (Temp1 as u32) + ^ (Temp2 as u32) + ^ (Temp3 as u32) + ^ (Temp4 as u32) + ^ (Temp5 as u32) + ^ (Temp6 as u32) + ^ (Temp7 as u32) + ^ (Temp8 as u32); + + if Result >= FXMAC_MAX_HASH_BITS { + Status = 1; //(FXMAC_ERR_INVALID_PARAM); + } else { + if Result < 32 { + HashAddr = + read_reg((intance_p.config.base_address + FXMAC_HASHL_OFFSET) as *const u32); + HashAddr &= !((1 << Result) as u32); + write_reg( + (intance_p.config.base_address + FXMAC_HASHL_OFFSET) as *mut u32, + HashAddr, + ); + } else { + HashAddr = + read_reg((intance_p.config.base_address + FXMAC_HASHH_OFFSET) as *const u32); + HashAddr &= !((1 << (Result - 32)) as u32); + write_reg( + (intance_p.config.base_address + FXMAC_HASHH_OFFSET) as *mut u32, + HashAddr, + ); + } + Status = 0; + } + } + Status +} + +/// Set the Type ID match for this driver/device. The register is a 32-bit value. +/// The device must be stopped before calling this function. +fn FXmacSetTypeIdCheck(id_check: u32, index: u8) -> u32 { + let mut status: u32 = 0; + assert!( + (index < FXMAC_MAX_TYPE_ID as u8), + "index of Type ID exceed {}", + FXMAC_MAX_TYPE_ID + ); + + let is_started = 0; + /* Be sure device has been stopped */ + if is_started == FT_COMPONENT_IS_STARTED { + status = 9; //FXMAC_ERR_MAC_IS_PROCESSING + error!("FXMAC is processing when calling FXmacSetTypeIdCheck function"); + } else { + /* Set the ID bits in MATCHx register */ + write_reg( + (FXMAC_IOBASE + FXMAC_MATCH1_OFFSET + (index * 4) as u64) as *mut u32, + id_check, + ); + + status = FT_SUCCESS; + } + + status +} + +/// FXmacSelectClk +/// Determine the driver clock configuration based on the media independent interface +/// FXMAC_CLK_TYPE_0 +fn FXmacSelectClk(instance_p: &mut FXmac) { + let speed: u32 = instance_p.config.speed; + let FXMAC_WRITEREG32 = |base_address: u64, offset: u32, reg_value: u32| { + write_reg((base_address + offset as u64) as *mut u32, reg_value) + }; + + assert!( + (speed == FXMAC_SPEED_10) + || (speed == FXMAC_SPEED_100) + || (speed == FXMAC_SPEED_1000) + || (speed == FXMAC_SPEED_2500) + || (speed == FXMAC_SPEED_10000) + ); + + if (instance_p.config.interface == FXmacPhyInterface::FXMAC_PHY_INTERFACE_MODE_USXGMII) + || (instance_p.config.interface == FXmacPhyInterface::FXMAC_PHY_INTERFACE_MODE_XGMII) + { + if speed == FXMAC_SPEED_10000 { + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_SRC_SEL_LN, 0x1); /*0x1c04*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_DIV_SEL0_LN, 0x4); /*0x1c08*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_DIV_SEL1_LN, 0x1); /*0x1c0c*/ + FXMAC_WRITEREG32( + instance_p.config.base_address, + FXMAC_GEM_PMA_XCVR_POWER_STATE, + 0x1, + ); /*0x1c10*/ + } else if speed == FXMAC_SPEED_5000 { + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_SRC_SEL_LN, 0x1); /*0x1c04*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_DIV_SEL0_LN, 0x8); /*0x1c08*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_DIV_SEL1_LN, 0x2); /*0x1c0c*/ + FXMAC_WRITEREG32( + instance_p.config.base_address, + FXMAC_GEM_PMA_XCVR_POWER_STATE, + 0, + ); /*0x1c10*/ + } + } else if instance_p.config.interface == FXmacPhyInterface::FXMAC_PHY_INTERFACE_MODE_5GBASER { + if speed == FXMAC_SPEED_5000 { + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_SRC_SEL_LN, 0x1); /*0x1c04*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_DIV_SEL0_LN, 0x8); /*0x1c08*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_DIV_SEL1_LN, 0x2); /*0x1c0c*/ + FXMAC_WRITEREG32( + instance_p.config.base_address, + FXMAC_GEM_PMA_XCVR_POWER_STATE, + 0x0, + ); /*0x1c10*/ + } + } else if instance_p.config.interface == FXmacPhyInterface::FXMAC_PHY_INTERFACE_MODE_2500BASEX { + if speed == FXMAC_SPEED_25000 { + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_SRC_SEL_LN, 0x1); /*0x1c04*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_DIV_SEL0_LN, 0x1); /*0x1c08*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_DIV_SEL1_LN, 0x2); /*0x1c0c*/ + FXMAC_WRITEREG32( + instance_p.config.base_address, + FXMAC_GEM_PMA_XCVR_POWER_STATE, + 0x1, + ); /*0x1c10*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL0, 0); /*0x1c20*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL1, 0x1); /*0x1c24*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL2, 0x1); /*0x1c28*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL3, 0x1); /*0x1c2c*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL0, 0x1); /*0x1c30*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL1, 0x0); /*0x1c34*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL3_0, 0x0); /*0x1c70*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL4_0, 0x0); /*0x1c74*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL3_0, 0x0); /*0x1c78*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL4_0, 0x0); + /*0x1c7c*/ + } + } else if instance_p.config.interface == FXmacPhyInterface::FXMAC_PHY_INTERFACE_MODE_SGMII { + info!("FXMAC_PHY_INTERFACE_MODE_SGMII init"); + if speed == FXMAC_SPEED_2500 { + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_SRC_SEL_LN, 0); + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_DIV_SEL0_LN, 0x1); + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_DIV_SEL1_LN, 0x2); + FXMAC_WRITEREG32( + instance_p.config.base_address, + FXMAC_GEM_PMA_XCVR_POWER_STATE, + 0x1, + ); + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL0, 0); + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL1, 0x1); + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL2, 0x1); + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL3, 0x1); + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL0, 0x1); + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL1, 0x0); + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL3_0, 0x0); /*0x1c70*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL4_0, 0x0); /*0x1c74*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL3_0, 0x0); /*0x1c78*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL4_0, 0x0); + /*0x1c7c*/ + } else if speed == FXMAC_SPEED_1000 { + info!("sgmii FXMAC_SPEED_1000"); + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_SRC_SEL_LN, 0x1); /*0x1c04*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_DIV_SEL0_LN, 0x4); /*0x1c08*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_DIV_SEL1_LN, 0x8); /*0x1c0c*/ + FXMAC_WRITEREG32( + instance_p.config.base_address, + FXMAC_GEM_PMA_XCVR_POWER_STATE, + 0x1, + ); /*0x1c10*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL0, 0x0); /*0x1c20*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL1, 0x0); /*0x1c24*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL2, 0x0); /*0x1c28*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL3, 0x1); /*0x1c2c*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL0, 0x1); /*0x1c30*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL1, 0x0); /*0x1c34*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL3_0, 0x0); /*0x1c70*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL4_0, 0x0); /*0x1c74*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL3_0, 0x0); /*0x1c78*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL4_0, 0x0); + /*0x1c7c*/ + } else if (speed == FXMAC_SPEED_100) || (speed == FXMAC_SPEED_10) { + info!("sgmii FXMAC_SPEED_{}", speed); + + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_SRC_SEL_LN, 0x1); /*0x1c04*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_DIV_SEL0_LN, 0x4); /*0x1c08*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_DIV_SEL1_LN, 0x8); /*0x1c0c*/ + FXMAC_WRITEREG32( + instance_p.config.base_address, + FXMAC_GEM_PMA_XCVR_POWER_STATE, + 0x1, + ); /*0x1c10*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL0, 0x0); /*0x1c20*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL1, 0x0); /*0x1c24*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL2, 0x1); /*0x1c28*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL3, 0x1); /*0x1c2c*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL0, 0x1); /*0x1c30*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL1, 0x0); /*0x1c34*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL3_0, 0x1); /*0x1c70*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL4_0, 0x0); /*0x1c74*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL3_0, 0x0); /*0x1c78*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL4_0, 0x1); + /*0x1c7c*/ + } + } else if instance_p.config.interface == FXmacPhyInterface::FXMAC_PHY_INTERFACE_MODE_RGMII { + info!("FXMAC_PHY_INTERFACE_MODE_RGMII init"); + if speed == FXMAC_SPEED_1000 { + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_MII_SELECT, 0x1); /*0x1c18*/ + FXMAC_WRITEREG32( + instance_p.config.base_address, + FXMAC_GEM_SEL_MII_ON_RGMII, + 0x0, + ); /*0x1c1c*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL0, 0x0); /*0x1c20*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL1, 0x1); /*0x1c24*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL2, 0x0); /*0x1c28*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL3, 0x0); /*0x1c2c*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL0, 0x0); /*0x1c30*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL1, 0x1); /*0x1c34*/ + FXMAC_WRITEREG32( + instance_p.config.base_address, + FXMAC_GEM_CLK_250M_DIV10_DIV100_SEL, + 0x0, + ); /*0x1c38*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL5, 0x1); /*0x1c48*/ + FXMAC_WRITEREG32( + instance_p.config.base_address, + FXMAC_GEM_RGMII_TX_CLK_SEL0, + 0x1, + ); /*0x1c80*/ + FXMAC_WRITEREG32( + instance_p.config.base_address, + FXMAC_GEM_RGMII_TX_CLK_SEL1, + 0x0, + ); /*0x1c84*/ + } else if speed == FXMAC_SPEED_100 { + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_MII_SELECT, 0x1); /*0x1c18*/ + FXMAC_WRITEREG32( + instance_p.config.base_address, + FXMAC_GEM_SEL_MII_ON_RGMII, + 0x0, + ); /*0x1c1c*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL0, 0x0); /*0x1c20*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL1, 0x1); /*0x1c24*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL2, 0x0); /*0x1c28*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL3, 0x0); /*0x1c2c*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL0, 0x0); /*0x1c30*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL1, 0x1); /*0x1c34*/ + FXMAC_WRITEREG32( + instance_p.config.base_address, + FXMAC_GEM_CLK_250M_DIV10_DIV100_SEL, + 0x0, + ); /*0x1c38*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL5, 0x1); /*0x1c48*/ + FXMAC_WRITEREG32( + instance_p.config.base_address, + FXMAC_GEM_RGMII_TX_CLK_SEL0, + 0x0, + ); /*0x1c80*/ + FXMAC_WRITEREG32( + instance_p.config.base_address, + FXMAC_GEM_RGMII_TX_CLK_SEL1, + 0x0, + ); /*0x1c84*/ + } else { + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_MII_SELECT, 0x1); /*0x1c18*/ + FXMAC_WRITEREG32( + instance_p.config.base_address, + FXMAC_GEM_SEL_MII_ON_RGMII, + 0x0, + ); /*0x1c1c*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL0, 0x0); /*0x1c20*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL1, 0x1); /*0x1c24*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL2, 0x0); /*0x1c28*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_TX_CLK_SEL3, 0x0); /*0x1c2c*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL0, 0x0); /*0x1c30*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL1, 0x1); /*0x1c34*/ + FXMAC_WRITEREG32( + instance_p.config.base_address, + FXMAC_GEM_CLK_250M_DIV10_DIV100_SEL, + 0x1, + ); /*0x1c38*/ + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL5, 0x1); /*0x1c48*/ + FXMAC_WRITEREG32( + instance_p.config.base_address, + FXMAC_GEM_RGMII_TX_CLK_SEL0, + 0x0, + ); /*0x1c80*/ + FXMAC_WRITEREG32( + instance_p.config.base_address, + FXMAC_GEM_RGMII_TX_CLK_SEL1, + 0x0, + ); /*0x1c84*/ + } + } else if instance_p.config.interface == FXmacPhyInterface::FXMAC_PHY_INTERFACE_MODE_RMII { + FXMAC_WRITEREG32(instance_p.config.base_address, FXMAC_GEM_RX_CLK_SEL5, 0x1); + /*0x1c48*/ + } + + FXmacHighSpeedConfiguration(instance_p, speed); +} + +fn FXmacHighSpeedConfiguration(instance_p: &mut FXmac, speed: u32) { + let mut reg_value: u32 = 0; + let mut set_speed: i32 = 0; + match speed { + FXMAC_SPEED_25000 => { + set_speed = 2; + } + FXMAC_SPEED_10000 => { + set_speed = 4; + } + FXMAC_SPEED_5000 => { + set_speed = 3; + } + FXMAC_SPEED_2500 => { + set_speed = 2; + } + FXMAC_SPEED_1000 => { + set_speed = 1; + } + _ => { + set_speed = 0; + } + } + + /*GEM_HSMAC(0x0050) provide rate to the external*/ + reg_value = read_reg((FXMAC_IOBASE + FXMAC_GEM_HSMAC as u64) as *const u32); + reg_value &= !FXMAC_GEM_HSMACSPEED_MASK; + reg_value |= (set_speed as u32) & FXMAC_GEM_HSMACSPEED_MASK; + write_reg( + (FXMAC_IOBASE + FXMAC_GEM_HSMAC as u64) as *mut u32, + reg_value, + ); + + reg_value = read_reg((FXMAC_IOBASE + FXMAC_GEM_HSMAC as u64) as *const u32); + + info!("FXMAC_GEM_HSMAC is {:#x}", reg_value); +} + +/// FXmacInitInterface +/// Initialize the MAC controller configuration based on the PHY interface type +fn FXmacInitInterface(instance_p: &mut FXmac) { + let mut config: u32 = 0; + let mut control: u32 = 0; + + info!( + "FXmacInitInterface, PHY MODE:{:?}", + instance_p.config.interface + ); + + if instance_p.config.interface == FXmacPhyInterface::FXMAC_PHY_INTERFACE_MODE_XGMII { + config = read_reg((FXMAC_IOBASE + FXMAC_NWCFG_OFFSET) as *const u32); + config &= !FXMAC_NWCFG_PCSSEL_MASK; + write_reg((FXMAC_IOBASE + FXMAC_NWCFG_OFFSET) as *mut u32, config); + + control = read_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *const u32); + control |= FXMAC_NWCTRL_ENABLE_HS_MAC_MASK; /* Use high speed MAC */ + write_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *mut u32, control); + + instance_p.config.duplex = 1; + } else if (instance_p.config.interface == FXmacPhyInterface::FXMAC_PHY_INTERFACE_MODE_USXGMII) + || (instance_p.config.interface == FXmacPhyInterface::FXMAC_PHY_INTERFACE_MODE_5GBASER) + { + info!("usx interface is {:?}", instance_p.config.interface); + /* network_config */ + instance_p.config.duplex = 1; + config = read_reg((FXMAC_IOBASE + FXMAC_NWCFG_OFFSET) as *const u32); + config |= FXMAC_NWCFG_PCSSEL_MASK; + config &= !FXMAC_NWCFG_100_MASK; + config &= !FXMAC_NWCFG_SGMII_MODE_ENABLE_MASK; + if (instance_p.config.duplex == 1) { + info!("is duplex"); + config |= FXMAC_NWCFG_FDEN_MASK; + } + + write_reg((FXMAC_IOBASE + FXMAC_NWCFG_OFFSET) as *mut u32, config); + + /* network_control */ + control = read_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *const u32); + control |= FXMAC_NWCTRL_ENABLE_HS_MAC_MASK; /* Use high speed MAC */ + write_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *mut u32, control); + + /* High speed PCS control register */ + control = read_reg((FXMAC_IOBASE + FXMAC_GEM_USX_CONTROL_OFFSET) as *const u32); + + if (instance_p.config.speed == FXMAC_SPEED_10000) { + info!("is 10G"); + control |= FXMAC_GEM_USX_HS_MAC_SPEED_10G; + control |= FXMAC_GEM_USX_SERDES_RATE_10G; + } else if (instance_p.config.speed == FXMAC_SPEED_25000) { + control |= FXMAC_GEM_USX_HS_MAC_SPEED_2_5G; + } else if (instance_p.config.speed == FXMAC_SPEED_1000) { + control |= FXMAC_GEM_USX_HS_MAC_SPEED_1G; + } else if (instance_p.config.speed == FXMAC_SPEED_100) { + control |= FXMAC_GEM_USX_HS_MAC_SPEED_100M; + } else if (instance_p.config.speed == FXMAC_SPEED_5000) { + control |= FXMAC_GEM_USX_HS_MAC_SPEED_5G; + control |= FXMAC_GEM_USX_SERDES_RATE_5G; + } + + control &= !(FXMAC_GEM_USX_TX_SCR_BYPASS | FXMAC_GEM_USX_RX_SCR_BYPASS); + control |= FXMAC_GEM_USX_RX_SYNC_RESET; + write_reg( + (FXMAC_IOBASE + FXMAC_GEM_USX_CONTROL_OFFSET) as *mut u32, + control, + ); + + control = read_reg((FXMAC_IOBASE + FXMAC_GEM_USX_CONTROL_OFFSET) as *const u32); + control &= !FXMAC_GEM_USX_RX_SYNC_RESET; + control |= FXMAC_GEM_USX_TX_DATAPATH_EN; + control |= FXMAC_GEM_USX_SIGNAL_OK; + + write_reg( + (FXMAC_IOBASE + FXMAC_GEM_USX_CONTROL_OFFSET) as *mut u32, + control, + ); + } else if instance_p.config.interface == FXmacPhyInterface::FXMAC_PHY_INTERFACE_MODE_2500BASEX { + /* network_config */ + instance_p.config.duplex = 1; + config = read_reg((FXMAC_IOBASE + FXMAC_NWCFG_OFFSET) as *const u32); + config |= FXMAC_NWCFG_PCSSEL_MASK | FXMAC_NWCFG_SGMII_MODE_ENABLE_MASK; + config &= !FXMAC_NWCFG_100_MASK; + + if (instance_p.config.duplex == 1) { + config |= FXMAC_NWCFG_FDEN_MASK; + } + write_reg((FXMAC_IOBASE + FXMAC_NWCFG_OFFSET) as *mut u32, config); + + /* network_control */ + control = read_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *const u32); + control &= !FXMAC_NWCTRL_ENABLE_HS_MAC_MASK; + control |= FXMAC_NWCTRL_TWO_PT_FIVE_GIG_MASK; /* Use high speed MAC */ + write_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *mut u32, control); + + /* High speed PCS control register */ + control = read_reg((FXMAC_IOBASE + FXMAC_GEM_USX_CONTROL_OFFSET) as *const u32); + + if (instance_p.config.speed == FXMAC_SPEED_25000) { + control |= FXMAC_GEM_USX_HS_MAC_SPEED_2_5G; + } + + control &= !(FXMAC_GEM_USX_TX_SCR_BYPASS | FXMAC_GEM_USX_RX_SCR_BYPASS); + control |= FXMAC_GEM_USX_RX_SYNC_RESET; + write_reg( + (FXMAC_IOBASE + FXMAC_GEM_USX_CONTROL_OFFSET) as *mut u32, + control, + ); + + control = read_reg((FXMAC_IOBASE + FXMAC_GEM_USX_CONTROL_OFFSET) as *const u32); + control &= !FXMAC_GEM_USX_RX_SYNC_RESET; + control |= FXMAC_GEM_USX_TX_DATAPATH_EN; + control |= FXMAC_GEM_USX_SIGNAL_OK; + + write_reg( + (FXMAC_IOBASE + FXMAC_GEM_USX_CONTROL_OFFSET) as *mut u32, + control, + ); + } else if instance_p.config.interface == FXmacPhyInterface::FXMAC_PHY_INTERFACE_MODE_SGMII { + config = read_reg((FXMAC_IOBASE + FXMAC_NWCFG_OFFSET) as *const u32); + config |= FXMAC_NWCFG_PCSSEL_MASK | FXMAC_NWCFG_SGMII_MODE_ENABLE_MASK; + + config &= !(FXMAC_NWCFG_100_MASK + | FXMAC_NWCFG_FDEN_MASK + | FXMAC_NWCFG_LENGTH_FIELD_ERROR_FRAME_DISCARD_MASK); + + if instance_p.moudle_id >= 2 { + config &= !FXMAC_NWCFG_1000_MASK; + } + + if instance_p.config.duplex != 0 { + config |= FXMAC_NWCFG_FDEN_MASK; + } + + if instance_p.config.speed == FXMAC_SPEED_100 { + config |= FXMAC_NWCFG_100_MASK; + } else if instance_p.config.speed == FXMAC_SPEED_1000 { + config |= FXMAC_NWCFG_1000_MASK; + } + + write_reg((FXMAC_IOBASE + FXMAC_NWCFG_OFFSET) as *mut u32, config); + + if instance_p.config.speed == FXMAC_SPEED_2500 { + control = read_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *const u32); + control |= FXMAC_NWCTRL_TWO_PT_FIVE_GIG_MASK; + write_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *mut u32, control); + } else { + control = read_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *const u32); + control &= !FXMAC_NWCTRL_TWO_PT_FIVE_GIG_MASK; + write_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *mut u32, control); + } + + control = read_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *const u32); + control &= !FXMAC_NWCTRL_ENABLE_HS_MAC_MASK; + write_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *mut u32, control); + + control = read_reg((FXMAC_IOBASE + FXMAC_PCS_CONTROL_OFFSET) as *const u32); + control |= FXMAC_PCS_CONTROL_ENABLE_AUTO_NEG; + write_reg( + (FXMAC_IOBASE + FXMAC_PCS_CONTROL_OFFSET) as *mut u32, + control, + ); + } else { + config = read_reg((FXMAC_IOBASE + FXMAC_NWCFG_OFFSET) as *const u32); + + info!("select rgmii"); + + config &= !FXMAC_NWCFG_PCSSEL_MASK; + config &= !(FXMAC_NWCFG_100_MASK | FXMAC_NWCFG_FDEN_MASK); + + if instance_p.moudle_id >= 2 { + config &= !FXMAC_NWCFG_1000_MASK; + } + + if instance_p.config.duplex != 0 { + config |= FXMAC_NWCFG_FDEN_MASK; + } + + if instance_p.config.speed == FXMAC_SPEED_100 { + config |= FXMAC_NWCFG_100_MASK; + } else if instance_p.config.speed == FXMAC_SPEED_1000 { + config |= FXMAC_NWCFG_1000_MASK; + } + + write_reg((FXMAC_IOBASE + FXMAC_NWCFG_OFFSET) as *mut u32, config); + + control = read_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *const u32); + control &= !FXMAC_NWCTRL_ENABLE_HS_MAC_MASK; /* Use high speed MAC */ + write_reg((FXMAC_IOBASE + FXMAC_NWCTRL_OFFSET) as *mut u32, control); + } +} diff --git a/components/fxmac_rs/src/fxmac_const.rs b/components/fxmac_rs/src/fxmac_const.rs new file mode 100644 index 000000000..46d21451c --- /dev/null +++ b/components/fxmac_rs/src/fxmac_const.rs @@ -0,0 +1,685 @@ +//! FXMAC hardware register offsets and bit definitions. +//! +//! This module mirrors the low-level register layout from the FXMAC hardware +//! specification and is primarily intended for internal driver use. +//////////////////// +// fxmac_hw.h + +pub(crate) const FXMAC_RX_BUF_UNIT: u32 = 64; /* Number of receive buffer bytes as a unit, this is HW setup */ + +pub(crate) const FXMAC_MAX_RXBD: u32 = 128; /* Size of RX buffer descriptor queues */ +pub(crate) const FXMAC_MAX_TXBD: u32 = 128; /* Size of TX buffer descriptor queues */ + +pub(crate) const FXMAC_MAX_HASH_BITS: u32 = 64; /* Maximum value for hash bits. 2**6 */ + +/************************** Constant Definitions *****************************/ + +pub(crate) const FXMAC_MAX_MAC_ADDR: u32 = 4; /* Maxmum number of mac address supported */ +pub(crate) const FXMAC_MAX_TYPE_ID: u32 = 4; /* Maxmum number of type id supported */ + +/// for aarch64 +pub(crate) const FXMAC_BD_ALIGNMENT: u32 = 64; /* Minimum buffer descriptor alignment on the local bus */ + +pub(crate) const FXMAC_RX_BUF_ALIGNMENT: u32 = 4; /* Minimum buffer alignment when using options that impose + alignment restrictions on the buffer data on the local bus */ + +pub(crate) const FXMAC_NWCTRL_OFFSET: u64 = 0x00000000; /* Network Control reg */ +pub(crate) const FXMAC_NWCFG_OFFSET: u64 = 0x00000004; /* Network Config reg */ +pub(crate) const FXMAC_NWSR_OFFSET: u64 = 0x00000008; /* Network Status reg */ +pub(crate) const FXMAC_DMACR_OFFSET: u64 = 0x00000010; /* DMA Control reg */ +pub(crate) const FXMAC_TXSR_OFFSET: u64 = 0x00000014; /* TX Status reg */ +pub(crate) const FXMAC_RXQBASE_OFFSET: u64 = 0x00000018; /* RX Q Base address reg */ +pub(crate) const FXMAC_TXQBASE_OFFSET: u64 = 0x0000001C; /* TX Q Base address reg */ +pub(crate) const FXMAC_RXSR_OFFSET: u64 = 0x00000020; /* RX Status reg */ + +pub(crate) const FXMAC_ISR_OFFSET: u64 = 0x00000024; /* Interrupt Status reg */ +pub(crate) const FXMAC_IER_OFFSET: u64 = 0x00000028; /* Interrupt Enable reg */ +pub(crate) const FXMAC_IDR_OFFSET: u64 = 0x0000002C; /* Interrupt Disable reg */ +pub(crate) const FXMAC_IMR_OFFSET: u64 = 0x00000030; /* Interrupt Mask reg */ + +pub(crate) const FXMAC_PHYMNTNC_OFFSET: u64 = 0x00000034; /* Phy Maintaince reg */ +pub(crate) const FXMAC_RXPAUSE_OFFSET: u64 = 0x00000038; /* RX Pause Time reg */ +pub(crate) const FXMAC_TXPAUSE_OFFSET: u64 = 0x0000003C; /* TX Pause Time reg */ + +pub(crate) const FXMAC_JUMBOMAXLEN_OFFSET: u64 = 0x00000048; /* Jumbo max length reg */ +pub(crate) const FXMAC_GEM_HSMAC: u32 = 0x0050; /* Hs mac config register*/ +pub(crate) const FXMAC_RXWATERMARK_OFFSET: u64 = 0x0000007C; /* RX watermark reg */ + +pub(crate) const FXMAC_HASHL_OFFSET: u64 = 0x00000080; /* Hash Low address reg */ +pub(crate) const FXMAC_HASHH_OFFSET: u64 = 0x00000084; /* Hash High address reg */ + +pub(crate) const FXMAC_GEM_SA1B: u32 = 0x0088; /* Specific1 Bottom */ +pub(crate) const FXMAC_GEM_SA1T: u32 = 0x008C; /* Specific1 Top */ +pub(crate) const FXMAC_GEM_SA2B: u32 = 0x0090; /* Specific2 Bottom */ +pub(crate) const FXMAC_GEM_SA2T: u32 = 0x0094; /* Specific2 Top */ +pub(crate) const FXMAC_GEM_SA3B: u32 = 0x0098; /* Specific3 Bottom */ +pub(crate) const FXMAC_GEM_SA3T: u32 = 0x009C; /* Specific3 Top */ +pub(crate) const FXMAC_GEM_SA4B: u32 = 0x00A0; /* Specific4 Bottom */ +pub(crate) const FXMAC_GEM_SA4T: u32 = 0x00A4; /* Specific4 Top */ + +pub(crate) const FXMAC_MATCH1_OFFSET: u64 = 0x000000A8; /* Type ID1 Match reg */ +pub(crate) const FXMAC_MATCH2_OFFSET: u64 = 0x000000AC; /* Type ID2 Match reg */ +pub(crate) const FXMAC_MATCH3_OFFSET: u64 = 0x000000B0; /* Type ID3 Match reg */ +pub(crate) const FXMAC_MATCH4_OFFSET: u64 = 0x000000B4; /* Type ID4 Match reg */ + +pub(crate) const FXMAC_STRETCH_OFFSET: u64 = 0x000000BC; /* IPG Stretch reg */ +pub(crate) const FXMAC_REVISION_REG_OFFSET: u64 = 0x000000FC; /* identification number and module revision */ + +pub(crate) const FXMAC_OCTTXL_OFFSET: u64 = 0x00000100; /* Octects transmitted Low reg */ +pub(crate) const FXMAC_OCTTXH_OFFSET: u64 = 0x00000104; /* Octects transmitted High reg */ + +pub(crate) const FXMAC_TXCNT_OFFSET: u64 = 0x00000108; /* Error-free Frmaes transmitted counter */ +pub(crate) const FXMAC_TXBCCNT_OFFSET: u64 = 0x0000010C; /* Error-free Broadcast Frames counter*/ +pub(crate) const FXMAC_TXMCCNT_OFFSET: u64 = 0x00000110; /* Error-free Multicast Frame counter */ +pub(crate) const FXMAC_TXPAUSECNT_OFFSET: u64 = 0x00000114; /* Pause Frames Transmitted Counter */ +pub(crate) const FXMAC_TX64CNT_OFFSET: u64 = 0x00000118; /* Error-free 64 byte Frames Transmitted counter */ +pub(crate) const FXMAC_TX65CNT_OFFSET: u64 = 0x0000011C; /* Error-free 65-127 byte Frames Transmitted counter */ +pub(crate) const FXMAC_TX128CNT_OFFSET: u64 = 0x00000120; /* Error-free 128-255 byte Frames Transmitted counter*/ +pub(crate) const FXMAC_TX256CNT_OFFSET: u64 = 0x00000124; /* Error-free 256-511 byte Frames transmitted counter */ +pub(crate) const FXMAC_TX512CNT_OFFSET: u64 = 0x00000128; /* Error-free 512-1023 byte Frames transmitted counter */ +pub(crate) const FXMAC_TX1024CNT_OFFSET: u64 = 0x0000012C; /* Error-free 1024-1518 byte Frames transmitted counter */ +pub(crate) const FXMAC_TX1519CNT_OFFSET: u64 = 0x00000130; /* Error-free larger than 1519 byte Frames transmitted counter */ +pub(crate) const FXMAC_TXURUNCNT_OFFSET: u64 = 0x00000134; /* TX under run error counter */ + +pub(crate) const FXMAC_SNGLCOLLCNT_OFFSET: u64 = 0x00000138; /* Single Collision Frame Counter */ +pub(crate) const FXMAC_MULTICOLLCNT_OFFSET: u64 = 0x0000013C; /* Multiple Collision Frame Counter */ +pub(crate) const FXMAC_EXCESSCOLLCNT_OFFSET: u64 = 0x00000140; /* Excessive Collision Frame Counter */ +pub(crate) const FXMAC_LATECOLLCNT_OFFSET: u64 = 0x00000144; /* Late Collision Frame Counter */ +pub(crate) const FXMAC_TXDEFERCNT_OFFSET: u64 = 0x00000148; /* Deferred Transmission Frame Counter */ +pub(crate) const FXMAC_TXCSENSECNT_OFFSET: u64 = 0x0000014C; /* Transmit Carrier Sense Error Counter */ + +pub(crate) const FXMAC_OCTRXL_OFFSET: u64 = 0x00000150; /* Octects Received register Low */ +pub(crate) const FXMAC_OCTRXH_OFFSET: u64 = 0x00000154; /* Octects Received register High */ + +pub(crate) const FXMAC_RXCNT_OFFSET: u64 = 0x00000158; /* Error-free Frames Received Counter */ +pub(crate) const FXMAC_RXBROADCNT_OFFSET: u64 = 0x0000015C; /* Error-free Broadcast Frames Received Counter */ +pub(crate) const FXMAC_RXMULTICNT_OFFSET: u64 = 0x00000160; /* Error-free Multicast Frames Received Counter */ +pub(crate) const FXMAC_RXPAUSECNT_OFFSET: u64 = 0x00000164; /* Pause Frames Received Counter */ +pub(crate) const FXMAC_RX64CNT_OFFSET: u64 = 0x00000168; /* Error-free 64 byte Frames Received Counter */ +pub(crate) const FXMAC_RX65CNT_OFFSET: u64 = 0x0000016C; /* Error-free 65-127 byte Frames Received Counter */ +pub(crate) const FXMAC_RX128CNT_OFFSET: u64 = 0x00000170; /* Error-free 128-255 byte Frames Received Counter */ +pub(crate) const FXMAC_RX256CNT_OFFSET: u64 = 0x00000174; /* Error-free 256-512 byte Frames Received Counter */ +pub(crate) const FXMAC_RX512CNT_OFFSET: u64 = 0x00000178; /* Error-free 512-1023 byte Frames Received Counter */ +pub(crate) const FXMAC_RX1024CNT_OFFSET: u64 = 0x0000017C; /* Error-free 1024-1518 byte Frames Received Counter */ +pub(crate) const FXMAC_RX1519CNT_OFFSET: u64 = 0x00000180; /* Error-free 1519-max byte Frames Received Counter */ +pub(crate) const FXMAC_RXUNDRCNT_OFFSET: u64 = 0x00000184; /* Undersize Frames Received Counter */ +pub(crate) const FXMAC_RXOVRCNT_OFFSET: u64 = 0x00000188; /* Oversize Frames Received Counter */ +pub(crate) const FXMAC_RXJABCNT_OFFSET: u64 = 0x0000018C; /* Jabbers Received Counter */ +pub(crate) const FXMAC_RXFCSCNT_OFFSET: u64 = 0x00000190; /* Frame Check Sequence Error Counter */ +pub(crate) const FXMAC_RXLENGTHCNT_OFFSET: u64 = 0x00000194; /* Length Field Error Counter */ +pub(crate) const FXMAC_RXSYMBCNT_OFFSET: u64 = 0x00000198; /* Symbol Error Counter */ +pub(crate) const FXMAC_RXALIGNCNT_OFFSET: u64 = 0x0000019C; /* Alignment Error Counter */ +pub(crate) const FXMAC_RXRESERRCNT_OFFSET: u64 = 0x000001A0; /* Receive Resource Error Counter */ +pub(crate) const FXMAC_RXORCNT_OFFSET: u64 = 0x000001A4; /* Receive Overrun Counter */ +pub(crate) const FXMAC_RXIPCCNT_OFFSET: u64 = 0x000001A8; /* IP header Checksum Error Counter */ +pub(crate) const FXMAC_RXTCPCCNT_OFFSET: u64 = 0x000001AC; /* TCP Checksum Error Counter */ +pub(crate) const FXMAC_RXUDPCCNT_OFFSET: u64 = 0x000001B0; /* UDP Checksum Error Counter */ +pub(crate) const FXMAC_LAST_OFFSET: u64 = 0x000001B4; /* Last statistic counter offset, for clearing */ + +pub(crate) const FXMAC_1588_SEC_OFFSET: u64 = 0x000001D0; /* 1588 second counter */ +pub(crate) const FXMAC_1588_NANOSEC_OFFSET: u64 = 0x000001D4; /* 1588 nanosecond counter */ +pub(crate) const FXMAC_1588_ADJ_OFFSET: u64 = 0x000001D8; /* 1588 nanosecond adjustment counter */ +pub(crate) const FXMAC_1588_INC_OFFSET: u64 = 0x000001DC; /* 1588 nanosecond increment counter */ +pub(crate) const FXMAC_PTP_TXSEC_OFFSET: u64 = 0x000001E0; /* 1588 PTP transmit second counter */ +pub(crate) const FXMAC_PTP_TXNANOSEC_OFFSET: u64 = 0x000001E4; /* 1588 PTP transmit nanosecond counter */ +pub(crate) const FXMAC_PTP_RXSEC_OFFSET: u64 = 0x000001E8; /* 1588 PTP receive second counter */ +pub(crate) const FXMAC_PTP_RXNANOSEC_OFFSET: u64 = 0x000001EC; /* 1588 PTP receive nanosecond counter */ +pub(crate) const FXMAC_PTPP_TXSEC_OFFSET: u64 = 0x000001F0; /* 1588 PTP peer transmit second counter */ +pub(crate) const FXMAC_PTPP_TXNANOSEC_OFFSET: u64 = 0x000001F4; /* 1588 PTP peer transmit nanosecond counter */ +pub(crate) const FXMAC_PTPP_RXSEC_OFFSET: u64 = 0x000001F8; /* 1588 PTP peer receive second counter */ +pub(crate) const FXMAC_PTPP_RXNANOSEC_OFFSET: u64 = 0x000001FC; /* 1588 PTP peer receive nanosecond counter */ + +pub(crate) const FXMAC_PCS_CONTROL_OFFSET: u64 = 0x00000200; /* All PCS registers */ + +pub(crate) const FXMAC_PCS_STATUS_OFFSET: u64 = 0x00000204; /* All PCS status */ + +pub(crate) const FXMAC_PCS_AN_LP_OFFSET: u64 = 0x00000214; /* All PCS link partner's base page */ + +pub(crate) const FXMAC_DESIGNCFG_DEBUG1_OFFSET: u64 = 0x00000280; /* Design Configuration Register 1 */ + +pub(crate) const FXMAC_DESIGNCFG_DEBUG2_OFFSET: u64 = 0x00000284; /* Design Configuration Register 2 */ + +pub(crate) const FXMAC_INTQ1_STS_OFFSET: u64 = 0x00000400; /* Interrupt Q1 Status reg */ + +pub(crate) const FXMAC_TXQ1BASE_OFFSET: u64 = 0x00000440; /* TX Q1 Base address reg */ +pub(crate) const FXMAC_RXQ1BASE_OFFSET: u64 = 0x00000480; /* RX Q1 Base address reg */ + +pub(crate) const FXMAC_RXBUFQ1_SIZE_OFFSET: u64 = 0x000004a0; /* Receive Buffer Size */ +// FXMAC_RXBUFQX_SIZE_OFFSET(x) (FXMAC_RXBUFQ1_SIZE_OFFSET + (x << 2)) +pub const fn FXMAC_RXBUFQX_SIZE_OFFSET(value: u64) -> u64 { + FXMAC_RXBUFQ1_SIZE_OFFSET + (value << 2) +} +pub(crate) const FXMAC_RXBUFQX_SIZE_MASK: u32 = GENMASK(7, 0); + +pub(crate) const FXMAC_MSBBUF_TXQBASE_OFFSET: u64 = 0x000004C8; /* MSB Buffer TX Q Base reg */ +pub(crate) const FXMAC_MSBBUF_RXQBASE_OFFSET: u64 = 0x000004D4; /* MSB Buffer RX Q Base reg */ +pub(crate) const FXMAC_TXQSEGALLOC_QLOWER_OFFSET: u64 = 0x000005A0; /* Transmit SRAM segment distribution */ +pub(crate) const FXMAC_INTQ1_IER_OFFSET: u64 = 0x00000600; /* Interrupt Q1 Enable reg */ +pub const fn FXMAC_INTQX_IER_SIZE_OFFSET(x: u64) -> u64 { + FXMAC_INTQ1_IER_OFFSET + (x << 2) +} + +pub(crate) const FXMAC_INTQ1_IDR_OFFSET: u64 = 0x00000620; /* Interrupt Q1 Disable reg */ +pub const fn FXMAC_INTQX_IDR_SIZE_OFFSET(x: u64) -> u64 { + FXMAC_INTQ1_IDR_OFFSET + (x << 2) +} + +pub(crate) const FXMAC_INTQ1_IMR_OFFSET: u64 = 0x00000640; /* Interrupt Q1 Mask reg */ + +pub(crate) const FXMAC_GEM_USX_CONTROL_OFFSET: u64 = 0x0A80; /* High speed PCS control register */ +pub(crate) const FXMAC_TEST_CONTROL_OFFSET: u64 = 0x0A84; /* USXGMII Test Control Register */ +pub(crate) const FXMAC_GEM_USX_STATUS_OFFSET: u64 = 0x0A88; /* USXGMII Status Register */ + +pub(crate) const FXMAC_GEM_SRC_SEL_LN: u32 = 0x1C04; +pub(crate) const FXMAC_GEM_DIV_SEL0_LN: u32 = 0x1C08; +pub(crate) const FXMAC_GEM_DIV_SEL1_LN: u32 = 0x1C0C; +pub(crate) const FXMAC_GEM_PMA_XCVR_POWER_STATE: u32 = 0x1C10; +pub(crate) const FXMAC_GEM_SPEED_MODE: u32 = 0x1C14; +pub(crate) const FXMAC_GEM_MII_SELECT: u32 = 0x1C18; +pub(crate) const FXMAC_GEM_SEL_MII_ON_RGMII: u32 = 0x1C1C; +pub(crate) const FXMAC_GEM_TX_CLK_SEL0: u32 = 0x1C20; +pub(crate) const FXMAC_GEM_TX_CLK_SEL1: u32 = 0x1C24; +pub(crate) const FXMAC_GEM_TX_CLK_SEL2: u32 = 0x1C28; +pub(crate) const FXMAC_GEM_TX_CLK_SEL3: u32 = 0x1C2C; +pub(crate) const FXMAC_GEM_RX_CLK_SEL0: u32 = 0x1C30; +pub(crate) const FXMAC_GEM_RX_CLK_SEL1: u32 = 0x1C34; +pub(crate) const FXMAC_GEM_CLK_250M_DIV10_DIV100_SEL: u32 = 0x1C38; +pub(crate) const FXMAC_GEM_TX_CLK_SEL5: u32 = 0x1C3C; +pub(crate) const FXMAC_GEM_TX_CLK_SEL6: u32 = 0x1C40; +pub(crate) const FXMAC_GEM_RX_CLK_SEL4: u32 = 0x1C44; +pub(crate) const FXMAC_GEM_RX_CLK_SEL5: u32 = 0x1C48; +pub(crate) const FXMAC_GEM_TX_CLK_SEL3_0: u32 = 0x1C70; +pub(crate) const FXMAC_GEM_TX_CLK_SEL4_0: u32 = 0x1C74; +pub(crate) const FXMAC_GEM_RX_CLK_SEL3_0: u32 = 0x1C78; +pub(crate) const FXMAC_GEM_RX_CLK_SEL4_0: u32 = 0x1C7C; +pub(crate) const FXMAC_GEM_RGMII_TX_CLK_SEL0: u32 = 0x1C80; +pub(crate) const FXMAC_GEM_RGMII_TX_CLK_SEL1: u32 = 0x1C84; +pub(crate) const FXMAC_GEM_MODE_SEL_OFFSET: u64 = 0xDC00; +pub(crate) const FXMAC_LOOPBACK_SEL_OFFSET: u64 = 0xDC04; + +pub(crate) const FXMAC_TAIL_ENABLE: u64 = 0xe7c; /*Enable tail Register*/ +// FXMAC_TAIL_QUEUE(queue) (0x0e80 + ((queue) << 2)) + +/** + * @name interrupts bit definitions + * Bits definitions are same in FXMAC_ISR_OFFSET, + * FXMAC_IER_OFFSET, FXMAC_IDR_OFFSET, and FXMAC_IMR_OFFSET + * @{ + */ +pub(crate) const FXMAC_IXR_PTPPSTX_MASK: u32 = BIT(25); /* PTP Pdelay_resp TXed */ +pub(crate) const FXMAC_IXR_PTPPDRTX_MASK: u32 = BIT(24); /* PTP Pdelay_req TXed */ +pub(crate) const FXMAC_IXR_PTPPSRX_MASK: u32 = BIT(23); /* PTP Pdelay_resp RXed */ +pub(crate) const FXMAC_IXR_PTPPDRRX_MASK: u32 = BIT(22); /* PTP Pdelay_req RXed */ + +pub(crate) const FXMAC_IXR_PTPSTX_MASK: u32 = BIT(21); /* PTP Sync TXed */ +pub(crate) const FXMAC_IXR_PTPDRTX_MASK: u32 = BIT(20); /* PTP Delay_req TXed */ +pub(crate) const FXMAC_IXR_PTPSRX_MASK: u32 = BIT(19); /* PTP Sync RXed */ +pub(crate) const FXMAC_IXR_PTPDRRX_MASK: u32 = BIT(18); /* PTP Delay_req RXed */ + +pub(crate) const FXMAC_IXR_PAUSETX_MASK: u32 = BIT(14); /* Pause frame transmitted */ +pub(crate) const FXMAC_IXR_PAUSEZERO_MASK: u32 = BIT(13); /* Pause time has reached zero */ +pub(crate) const FXMAC_IXR_PAUSENZERO_MASK: u32 = BIT(12); /* Pause frame received */ +pub(crate) const FXMAC_IXR_HRESPNOK_MASK: u32 = BIT(11); /* hresp not ok */ +pub(crate) const FXMAC_IXR_RXOVR_MASK: u32 = BIT(10); /* Receive overrun occurred */ +pub(crate) const FXMAC_IXR_LINKCHANGE_MASK: u32 = BIT(9); /* link status change */ +pub(crate) const FXMAC_IXR_TXCOMPL_MASK: u32 = BIT(7); /* Frame transmitted ok */ +pub(crate) const FXMAC_IXR_TXEXH_MASK: u32 = BIT(6); /* Transmit err occurred or no buffers*/ +pub(crate) const FXMAC_IXR_RETRY_MASK: u32 = BIT(5); /* Retry limit exceeded */ +pub(crate) const FXMAC_IXR_URUN_MASK: u32 = BIT(4); /* Transmit underrun */ +pub(crate) const FXMAC_IXR_TXUSED_MASK: u32 = BIT(3); /* Tx buffer used bit read */ +pub(crate) const FXMAC_IXR_RXUSED_MASK: u32 = BIT(2); /* Rx buffer used bit read */ +pub(crate) const FXMAC_IXR_RXCOMPL_MASK: u32 = BIT(1); /* Frame received ok */ +pub(crate) const FXMAC_IXR_MGMNT_MASK: u32 = BIT(0); /* PHY management complete */ +pub(crate) const FXMAC_IXR_ALL_MASK: u32 = GENMASK(31, 0); /* Everything! */ + +pub(crate) const FXMAC_IXR_TX_ERR_MASK: u32 = + (FXMAC_IXR_TXEXH_MASK | FXMAC_IXR_RETRY_MASK | FXMAC_IXR_URUN_MASK); + +pub(crate) const FXMAC_IXR_RX_ERR_MASK: u32 = + (FXMAC_IXR_HRESPNOK_MASK | FXMAC_IXR_RXUSED_MASK | FXMAC_IXR_RXOVR_MASK); + +pub(crate) const FXMAC_INTR_MASK: u32 = (FXMAC_IXR_LINKCHANGE_MASK + | FXMAC_IXR_TX_ERR_MASK + | FXMAC_IXR_RX_ERR_MASK + | FXMAC_IXR_RXCOMPL_MASK + | FXMAC_IXR_TXCOMPL_MASK); + +/** @name network control register bit definitions + * @{ + */ +pub(crate) const FXMAC_NWCTRL_ENABLE_HS_MAC_MASK: u32 = BIT(31); + +pub(crate) const FXMAC_NWCTRL_TWO_PT_FIVE_GIG_MASK: u32 = BIT(29); /* 2.5G operation selected */ + +pub(crate) const FXMAC_NWCTRL_FLUSH_DPRAM_MASK: u32 = BIT(18); /* Flush a packet from Rx SRAM */ +pub(crate) const FXMAC_NWCTRL_ZEROPAUSETX_MASK: u32 = BIT(11); /* Transmit zero quantum pause frame */ +pub(crate) const FXMAC_NWCTRL_PAUSETX_MASK: u32 = BIT(11); /* Transmit pause frame */ +pub(crate) const FXMAC_NWCTRL_HALTTX_MASK: u32 = BIT(10); /* Halt transmission after current frame */ +pub(crate) const FXMAC_NWCTRL_STARTTX_MASK: u32 = BIT(9); /* Start tx (tx_go) */ + +pub(crate) const FXMAC_NWCTRL_STATWEN_MASK: u32 = BIT(7); /* Enable writing to stat counters */ +pub(crate) const FXMAC_NWCTRL_STATINC_MASK: u32 = BIT(6); /* Increment statistic registers */ +pub(crate) const FXMAC_NWCTRL_STATCLR_MASK: u32 = BIT(5); /* Clear statistic registers */ +pub(crate) const FXMAC_NWCTRL_MDEN_MASK: u32 = BIT(4); /* Enable MDIO port */ +pub(crate) const FXMAC_NWCTRL_TXEN_MASK: u32 = BIT(3); /* Enable transmit */ +pub(crate) const FXMAC_NWCTRL_RXEN_MASK: u32 = BIT(2); /* Enable receive */ +pub(crate) const FXMAC_NWCTRL_LOOPBACK_LOCAL_MASK: u32 = BIT(1); /* Loopback local */ + +/** @name network configuration register bit definitions FXMAC_NWCFG_OFFSET + * @{ + */ +pub(crate) const FXMAC_NWCFG_BADPREAMBEN_MASK: u32 = BIT(29); /* disable rejection of non-standard preamble */ +pub(crate) const FXMAC_NWCFG_IPDSTRETCH_MASK: u32 = BIT(28); /* enable transmit IPG */ +pub(crate) const FXMAC_NWCFG_SGMII_MODE_ENABLE_MASK: u32 = BIT(27); /* SGMII mode enable */ +pub(crate) const FXMAC_NWCFG_FCSIGNORE_MASK: u32 = BIT(26); /* disable rejection of FCS error */ +pub(crate) const FXMAC_NWCFG_HDRXEN_MASK: u32 = BIT(25); /* RX half duplex */ +pub(crate) const FXMAC_NWCFG_RXCHKSUMEN_MASK: u32 = BIT(24); /* enable RX checksum offload */ +pub(crate) const FXMAC_NWCFG_PAUSECOPYDI_MASK: u32 = BIT(23); /* Do not copy pause Frames to memory */ + +pub(crate) const FXMAC_NWCFG_DWIDTH_64_MASK: u32 = BIT(21); /* 64 bit Data bus width */ +pub(crate) const FXMAC_NWCFG_BUS_WIDTH_32_MASK: u32 = (0 << 21); +pub(crate) const FXMAC_NWCFG_BUS_WIDTH_64_MASK: u32 = (1 << 21); +pub(crate) const FXMAC_NWCFG_BUS_WIDTH_128_MASK: u32 = (2 << 21); + +pub(crate) const FXMAC_NWCFG_CLOCK_DIV224_MASK: u32 = (7 << 18); +pub(crate) const FXMAC_NWCFG_CLOCK_DIV128_MASK: u32 = (6 << 18); +pub(crate) const FXMAC_NWCFG_CLOCK_DIV96_MASK: u32 = (5 << 18); +pub(crate) const FXMAC_NWCFG_CLOCK_DIV64_MASK: u32 = (4 << 18); +pub(crate) const FXMAC_NWCFG_CLOCK_DIV48_MASK: u32 = (3 << 18); +pub(crate) const FXMAC_NWCFG_CLOCK_DIV32_MASK: u32 = (2 << 18); +pub(crate) const FXMAC_NWCFG_CLOCK_DIV16_MASK: u32 = (1 << 18); +pub(crate) const FXMAC_NWCFG_CLOCK_DIV8_MASK: u32 = (0 << 18); +pub(crate) const FXMAC_NWCFG_RESET_MASK: u32 = BIT(19); /* reset value of mdc_clock_division*/ +pub(crate) const FXMAC_NWCFG_MDC_SHIFT_MASK: u32 = 18; /* shift bits for MDC */ +pub(crate) const FXMAC_NWCFG_MDCCLKDIV_MASK: u32 = GENMASK(20, 18); /* MDC Mask PCLK divisor */ + +pub(crate) const FXMAC_NWCFG_FCS_REMOVE_MASK: u32 = BIT(17); /* FCS remove - setting this bit will cause received frames to be written to memory without their frame check sequence (last 4 bytes). */ +pub(crate) const FXMAC_NWCFG_LENGTH_FIELD_ERROR_FRAME_DISCARD_MASK: u32 = BIT(16); /* RX length error discard */ +// FXMAC_NWCFG_RXOFFS_MASK:u32 = GENMASK(15); /* RX buffer offset */ +pub(crate) const FXMAC_NWCFG_PAUSE_ENABLE_MASK: u32 = BIT(13); /* Pause enable - when set, transmission will pause if a non-zero 802.3 classic pause frame is received and PFC has not been negotiated. */ +pub(crate) const FXMAC_NWCFG_RETRYTESTEN_MASK: u32 = BIT(12); /* Retry test */ +pub(crate) const FXMAC_NWCFG_PCSSEL_MASK: u32 = BIT(11); /* PCS Select */ +pub(crate) const FXMAC_NWCFG_1000_MASK: u32 = BIT(10); /* Gigabit mode enable */ +pub(crate) const FXMAC_NWCFG_XTADDMACHEN_MASK: u32 = BIT(9); /* External address match enable */ +pub(crate) const FXMAC_NWCFG_1536RXEN_MASK: u32 = BIT(8); /* Enable 1536 byte frames reception */ +pub(crate) const FXMAC_NWCFG_UCASTHASHEN_MASK: u32 = BIT(7); /* Receive unicast hash frames */ +pub(crate) const FXMAC_NWCFG_MCASTHASHEN_MASK: u32 = BIT(6); /* Receive multicast hash frames */ +pub(crate) const FXMAC_NWCFG_BCASTDI_MASK: u32 = BIT(5); /* Do not receive broadcast frames */ +pub(crate) const FXMAC_NWCFG_COPYALLEN_MASK: u32 = BIT(4); /* Copy all frames */ +pub(crate) const FXMAC_NWCFG_JUMBO_MASK: u32 = BIT(3); /* Jumbo frames */ +pub(crate) const FXMAC_NWCFG_NVLANDISC_MASK: u32 = BIT(2); /* Receive only VLAN frames */ +pub(crate) const FXMAC_NWCFG_FDEN_MASK: u32 = BIT(1); /* full duplex */ +pub(crate) const FXMAC_NWCFG_100_MASK: u32 = BIT(0); /* 100 Mbps */ + +/* Receive buffer descriptor status words bit positions. + * Receive buffer descriptor consists of two 32-bit registers, + * the first - word0 contains a 32-bit word aligned address pointing to the + * address of the buffer. The lower two bits make up the wrap bit indicating + * the last descriptor and the ownership bit to indicate it has been used by + * the xmac. + * The following register - word1, contains status information regarding why + * the frame was received (the filter match condition) as well as other + * useful info. + * @{ + */ +pub(crate) const FXMAC_RXBUF_BCAST_MASK: u32 = BIT(31); /* Broadcast frame */ +pub(crate) const FXMAC_RXBUF_HASH_MASK: u32 = GENMASK(30, 29); +pub(crate) const FXMAC_RXBUF_MULTIHASH_MASK: u32 = BIT(30); /* Multicast hashed frame */ +pub(crate) const FXMAC_RXBUF_UNIHASH_MASK: u32 = BIT(29); /* Unicast hashed frame */ +pub(crate) const FXMAC_RXBUF_EXH_MASK: u32 = BIT(27); /* buffer exhausted */ +pub(crate) const FXMAC_RXBUF_AMATCH_MASK: u32 = GENMASK(26, 25); /* Specific address \ + matched */ +pub(crate) const FXMAC_RXBUF_IDFOUND_MASK: u32 = BIT(24); /* Type ID matched */ +pub(crate) const FXMAC_RXBUF_IDMATCH_MASK: u32 = GENMASK(23, 22); /* ID matched mask */ +pub(crate) const FXMAC_RXBUF_VLAN_MASK: u32 = BIT(21); /* VLAN tagged */ +pub(crate) const FXMAC_RXBUF_PRI_MASK: u32 = BIT(20); /* Priority tagged */ +pub(crate) const FXMAC_RXBUF_VPRI_MASK: u32 = GENMASK(19, 17); /* Vlan priority */ +pub(crate) const FXMAC_RXBUF_CFI_MASK: u32 = BIT(16); /* CFI frame */ +pub(crate) const FXMAC_RXBUF_EOF_MASK: u32 = BIT(15); /* End of frame. */ +pub(crate) const FXMAC_RXBUF_SOF_MASK: u32 = BIT(14); /* Start of frame. */ +pub(crate) const FXMAC_RXBUF_FCS_STATUS_MASK: u32 = BIT(13); /* Status of fcs. */ +pub(crate) const FXMAC_RXBUF_LEN_MASK: u32 = GENMASK(12, 0); /* Mask for length field */ +pub(crate) const FXMAC_RXBUF_LEN_JUMBO_MASK: u32 = GENMASK(13, 0); /* Mask for jumbo length */ + +pub(crate) const FXMAC_RXBUF_WRAP_MASK: u32 = BIT(1); /* Wrap bit, last BD */ +pub(crate) const FXMAC_RXBUF_NEW_MASK: u32 = BIT(0); /* Used bit.. */ +pub(crate) const FXMAC_RXBUF_ADD_MASK: u32 = GENMASK(31, 2); /* Mask for address */ + +/* + * @} + */ + +/* Transmit buffer descriptor status words bit positions. + * Transmit buffer descriptor consists of two 32-bit registers, + * the first - word0 contains a 32-bit address pointing to the location of + * the transmit data. + * The following register - word1, consists of various information to control + * the xmac transmit process. After transmit, this is updated with status + * information, whether the frame was transmitted OK or why it had failed. + * @{ + */ +pub(crate) const FXMAC_TXBUF_USED_MASK: u32 = BIT(31); /* Used bit. */ +pub(crate) const FXMAC_TXBUF_WRAP_MASK: u32 = BIT(30); /* Wrap bit, last descriptor */ +pub(crate) const FXMAC_TXBUF_RETRY_MASK: u32 = BIT(29); /* Retry limit exceeded */ +pub(crate) const FXMAC_TXBUF_URUN_MASK: u32 = BIT(28); /* Transmit underrun occurred */ +pub(crate) const FXMAC_TXBUF_EXH_MASK: u32 = BIT(27); /* Buffers exhausted */ +pub(crate) const FXMAC_TXBUF_TCP_MASK: u32 = BIT(26); /* Late collision. */ +pub(crate) const FXMAC_TXBUF_NOCRC_MASK: u32 = BIT(16); /* No CRC */ +pub(crate) const FXMAC_TXBUF_LAST_MASK: u32 = BIT(15); /* Last buffer */ +pub(crate) const FXMAC_TXBUF_LEN_MASK: u32 = GENMASK(13, 0); /* Mask for length field */ +/* + * @} + */ + +/** + * @name receive status register bit definitions + * @{ + */ +pub(crate) const FXMAC_RXSR_HRESPNOK_MASK: u32 = BIT(3); /* Receive hresp not OK */ +pub(crate) const FXMAC_RXSR_RXOVR_MASK: u32 = BIT(2); /* Receive overrun */ +pub(crate) const FXMAC_RXSR_FRAMERX_MASK: u32 = BIT(1); /* Frame received OK */ +pub(crate) const FXMAC_RXSR_BUFFNA_MASK: u32 = BIT(0); /* RX buffer used bit set */ + +pub(crate) const FXMAC_RXSR_ERROR_MASK: u32 = + (FXMAC_RXSR_HRESPNOK_MASK | FXMAC_RXSR_RXOVR_MASK | FXMAC_RXSR_BUFFNA_MASK); + +pub(crate) const FXMAC_SR_ALL_MASK: u32 = GENMASK(31, 0); /* Mask for full register */ + +/** @name DMA control register bit definitions + * @{ + */ +pub(crate) const FXMAC_DMACR_ADDR_WIDTH_64: u32 = BIT(30); /* 64 bit address bus */ +pub(crate) const FXMAC_DMACR_TXEXTEND_MASK: u32 = BIT(29); /* Tx Extended desc mode */ +pub(crate) const FXMAC_DMACR_RXEXTEND_MASK: u32 = BIT(28); /* Rx Extended desc mode */ +pub(crate) const FXMAC_DMACR_ORCE_DISCARD_ON_ERR_MASK: u32 = BIT(24); /* Auto Discard RX frames during lack of resource. */ +pub(crate) const FXMAC_DMACR_RXBUF_MASK: u32 = GENMASK(23, 16); /* Mask bit for RX buffer size */ +pub(crate) const FXMAC_DMACR_RXBUF_SHIFT: u32 = 16; /* Shift bit for RX buffer size */ +pub(crate) const FXMAC_DMACR_TCPCKSUM_MASK: u32 = BIT(11); /* enable/disable TX checksum offload */ +pub(crate) const FXMAC_DMACR_TXSIZE_MASK: u32 = BIT(10); /* TX buffer memory size bit[10] */ +pub(crate) const FXMAC_DMACR_RXSIZE_MASK: u32 = GENMASK(9, 8); /* RX buffer memory size bit[9:8] */ +pub(crate) const FXMAC_DMACR_ENDIAN_MASK: u32 = BIT(7); /* endian configuration */ +pub(crate) const FXMAC_DMACR_SWAP_MANAGEMENT_MASK: u32 = BIT(6); /* When clear, selects little endian mode */ +pub(crate) const FXMAC_DMACR_BLENGTH_MASK: u32 = GENMASK(4, 0); /* buffer burst length */ +pub(crate) const FXMAC_DMACR_SINGLE_AHB_AXI_BURST: u32 = BIT(0); /* single AHB_AXI bursts */ +pub(crate) const FXMAC_DMACR_INCR4_AHB_AXI_BURST: u32 = BIT(2); /* 4 bytes AHB_AXI bursts */ +pub(crate) const FXMAC_DMACR_INCR8_AHB_AXI_BURST: u32 = BIT(3); /* 8 bytes AHB_AXI bursts */ +pub(crate) const FXMAC_DMACR_INCR16_AHB_AXI_BURST: u32 = BIT(4); /* 16 bytes AHB_AXI bursts */ + +/* This register indicates module identification number and module revision. */ + +pub(crate) const FXMAC_REVISION_MODULE_MASK: u32 = GENMASK(15, 0); /* Module revision */ +pub(crate) const FXMAC_IDENTIFICATION_MASK: u32 = GENMASK(27, 16); /* Module identification number */ +pub(crate) const FXMAC_FIX_NUM_MASK: u32 = GENMASK(31, 28); /* Fix number - incremented for fix releases */ + +/** @name network status register bit definitaions + * @{ + */ +pub(crate) const FXMAC_NWSR_MDIOIDLE_MASK: u32 = BIT(2); /* PHY management idle */ +pub(crate) const FXMAC_NWSR_MDIO_MASK: u32 = BIT(1); /* Status of mdio_in */ +pub(crate) const FXMAC_NWSR_PCS_LINK_STATE_MASK: u32 = BIT(0); + +/** @name PHY Maintenance bit definitions + * @{ + */ +pub(crate) const FXMAC_PHYMNTNC_OP_MASK: u32 = (BIT(17) | BIT(30)); /* operation mask bits */ +pub(crate) const FXMAC_PHYMNTNC_OP_R_MASK: u32 = BIT(29); /* read operation */ +pub(crate) const FXMAC_PHYMNTNC_OP_W_MASK: u32 = BIT(28); /* write operation */ +pub(crate) const FXMAC_PHYMNTNC_ADDR_MASK: u32 = GENMASK(27, 23); /* Address bits */ +pub(crate) const FXMAC_PHYMNTNC_REG_MASK: u32 = GENMASK(22, 18); /* register bits */ +pub(crate) const FXMAC_PHYMNTNC_DATA_MASK: u32 = GENMASK(11, 0); /* data bits */ +pub(crate) const FXMAC_PHYMNTNC_PHAD_SHFT_MSK: u32 = 23; /* Shift bits for PHYAD */ +pub(crate) const FXMAC_PHYMNTNC_PREG_SHFT_MSK: u32 = 18; /* Shift bits for PHREG */ + +/** @name transmit status register bit definitions + * @{ + */ +pub(crate) const FXMAC_TXSR_HRESPNOK_MASK: u32 = BIT(8); /* Transmit hresp not OK */ +pub(crate) const FXMAC_TXSR_URUN_MASK: u32 = BIT(6); /* Transmit underrun */ +pub(crate) const FXMAC_TXSR_TXCOMPL_MASK: u32 = BIT(5); /* Transmit completed OK */ +pub(crate) const FXMAC_TXSR_BUFEXH_MASK: u32 = BIT(4); /* Transmit buffs exhausted mid frame */ +pub(crate) const FXMAC_TXSR_TXGO_MASK: u32 = BIT(3); /* Status of go flag */ +pub(crate) const FXMAC_TXSR_RXOVR_MASK: u32 = BIT(2); /* Retry limit exceeded */ +pub(crate) const FXMAC_TXSR_FRAMERX_MASK: u32 = BIT(1); /* Collision tx frame */ +pub(crate) const FXMAC_TXSR_USEDREAD_MASK: u32 = BIT(0); /* TX buffer used bit set */ + +pub(crate) const FXMAC_TXSR_ERROR_MASK: u32 = (FXMAC_TXSR_HRESPNOK_MASK + | FXMAC_TXSR_URUN_MASK + | FXMAC_TXSR_BUFEXH_MASK + | FXMAC_TXSR_RXOVR_MASK + | FXMAC_TXSR_FRAMERX_MASK + | FXMAC_TXSR_USEDREAD_MASK); +/** @name transmit SRAM segment allocation by queue 0 to 7 register bit definitions + * @{ + */ +pub(crate) const FXMAC_TXQSEGALLOC_QLOWER_JUMBO_MASK: u32 = BIT(2); /* 16 segments are distributed to queue 0*/ +/** + * @name Interrupt Q1 status register bit definitions + * @{ + */ +pub(crate) const FXMAC_INTQ1SR_TXCOMPL_MASK: u32 = BIT(7); /* Transmit completed OK */ +pub(crate) const FXMAC_INTQ1SR_TXERR_MASK: u32 = BIT(6); /* Transmit AMBA Error */ + +pub(crate) const FXMAC_INTQ1_IXR_ALL_MASK: u32 = + (FXMAC_INTQ1SR_TXCOMPL_MASK | FXMAC_INTQ1SR_TXERR_MASK); + +/** + * @name Interrupt QUEUE status register bit definitions + * @{ + */ +pub(crate) const FXMAC_INTQUESR_TXCOMPL_MASK: u32 = BIT(7); /* Transmit completed OK */ +pub(crate) const FXMAC_INTQUESR_TXERR_MASK: u32 = BIT(6); /* Transmit AMBA Error */ +pub(crate) const FXMAC_INTQUESR_RCOMP_MASK: u32 = BIT(1); +pub(crate) const FXMAC_INTQUESR_RXUBR_MASK: u32 = BIT(2); + +pub(crate) const FXMAC_INTQUE_IXR_ALL_MASK: u32 = + (FXMAC_INTQUESR_TXCOMPL_MASK | FXMAC_INTQUESR_TXERR_MASK); + +pub const fn FXMAC_QUEUE_REGISTER_OFFSET(base_addr: u64, queue_id: u32) -> u64 { + base_addr + (queue_id as u64 - 1) * 4 +} + +/* Design Configuration Register 1 - The GEM has many parameterisation options to configure the IP during compilation stage. */ + +pub(crate) const FXMAC_DESIGNCFG_DEBUG1_BUS_WIDTH_MASK: u32 = GENMASK(27, 25); +pub(crate) const FXMAC_DESIGNCFG_DEBUG1_BUS_IRQCOR_MASK: u32 = BIT(23); + +/*GEM hs mac config register bitfields*/ +pub(crate) const FXMAC_GEM_HSMACSPEED_OFFSET: u64 = 0; +pub(crate) const FXMAC_GEM_HSMACSPEED_SIZE: u32 = 3; +pub(crate) const FXMAC_GEM_HSMACSPEED_MASK: u32 = 0x7; + +/* Transmit buffer descriptor status words offset + * @{ + */ +pub(crate) const FXMAC_BD_ADDR_OFFSET: u64 = 0x00000000; /* word 0/addr of BDs */ +pub(crate) const FXMAC_BD_STAT_OFFSET: u64 = 4; /* word 1/status of BDs, 4 bytes */ +pub(crate) const FXMAC_BD_ADDR_HI_OFFSET: u32 = BIT(3); /* word 2/addr of BDs */ + +/** @name MAC address register word 1 mask + * @{ + */ +pub(crate) const FXMAC_GEM_SAB_MASK: u32 = GENMASK(15, 0); /* Address bits[47:32] bit[31:0] are in BOTTOM */ + +/* USXGMII control register FXMAC_GEM_USX_CONTROL_OFFSET */ +pub(crate) const FXMAC_GEM_USX_HS_MAC_SPEED_100M: u32 = (0x0 << 14); /* 100M operation */ +pub(crate) const FXMAC_GEM_USX_HS_MAC_SPEED_1G: u32 = (0x1 << 14); /* 1G operation */ +pub(crate) const FXMAC_GEM_USX_HS_MAC_SPEED_2_5G: u32 = (0x2 << 14); /* 2.5G operation */ +pub(crate) const FXMAC_GEM_USX_HS_MAC_SPEED_5G: u32 = (0x3 << 14); /* 5G operation */ +pub(crate) const FXMAC_GEM_USX_HS_MAC_SPEED_10G: u32 = (0x4 << 14); /* 10G operation */ +pub(crate) const FXMAC_GEM_USX_SERDES_RATE_5G: u32 = (0x0 << 12); +pub(crate) const FXMAC_GEM_USX_SERDES_RATE_10G: u32 = (0x1 << 12); +pub(crate) const FXMAC_GEM_USX_TX_SCR_BYPASS: u32 = BIT(8); /* RX Scrambler Bypass. Set high to bypass the receive descrambler. */ +pub(crate) const FXMAC_GEM_USX_RX_SCR_BYPASS: u32 = BIT(9); /* TX Scrambler Bypass. Set high to bypass the transmit scrambler. */ +pub(crate) const FXMAC_GEM_USX_RX_SYNC_RESET: u32 = BIT(2); /* RX Reset. Set high to reset the receive datapath. When low the receive datapath is enabled. */ +pub(crate) const FXMAC_GEM_USX_TX_DATAPATH_EN: u32 = BIT(1); /* TX Datapath Enable. */ +pub(crate) const FXMAC_GEM_USX_SIGNAL_OK: u32 = BIT(0); /* Enable the USXGMII/BASE-R receive PCS. */ + +/* All PCS registers */ +pub(crate) const FXMAC_PCS_CONTROL_ENABLE_AUTO_NEG: u32 = BIT(12); /* Enable auto-negotiation - when set active high, autonegotiation operation is enabled. */ + +/* FXMAC_PCS_STATUS_OFFSET */ +pub(crate) const FXMAC_PCS_STATUS_LINK_STATUS_OFFSET: u32 = 2; +pub(crate) const FXMAC_PCS_STATUS_LINK_STATUS: u32 = BIT(FXMAC_PCS_STATUS_LINK_STATUS_OFFSET); /* Link status - indicates the status of the physical connection to the link partner. When set to logic 1 the link is up, and when set to logic 0, the link is down. */ + +/* FXMAC_PCS_AN_LP_OFFSET */ +pub(crate) const FXMAC_PCS_AN_LP_SPEED_OFFSET: u64 = 10; +pub(crate) const FXMAC_PCS_AN_LP_SPEED: u32 = (0x3 << FXMAC_PCS_AN_LP_SPEED_OFFSET); /* SGMII 11 : Reserved 10 : 1000 Mbps 01 : 100Mbps 00 : 10 Mbps */ +pub(crate) const FXMAC_PCS_AN_LP_DUPLEX_OFFSET: u64 = 12; +pub(crate) const FXMAC_PCS_AN_LP_DUPLEX: u32 = (0x3 << FXMAC_PCS_AN_LP_DUPLEX_OFFSET); /* SGMII Bit 13: Reserved. read as 0. Bit 12 : 0 : half-duplex. 1: Full Duplex." */ +pub(crate) const FXMAC_PCS_LINK_PARTNER_NEXT_PAGE_STATUS: u32 = (1 << 15); /* In sgmii mode, 0 is link down . 1 is link up */ +pub(crate) const FXMAC_PCS_LINK_PARTNER_NEXT_PAGE_OFFSET: u64 = 15; + +/* USXGMII Status Register */ +pub(crate) const FXMAC_GEM_USX_STATUS_BLOCK_LOCK: u32 = BIT(0); /* Block Lock. A value of one indicates that the PCS has achieved block synchronization. */ + +// aarch64 +pub(crate) const BITS_PER_LONG: u32 = 64; +pub const fn BIT(n: u32) -> u32 { + 1 << n +} + +pub const fn GENMASK(h: u32, l: u32) -> u32 { + ((!0_u64 - (1 << l) + 1) & (!0_u64 >> (BITS_PER_LONG - 1 - h))) as u32 +} + +//////////////////// +// fxmac.h + +pub(crate) const FXMAC_PROMISC_OPTION: u32 = 0x00000001; +/* Accept all incoming packets. + * This option defaults to disabled (cleared) */ + +pub(crate) const FXMAC_FRAME1536_OPTION: u32 = 0x00000002; +/* Frame larger than 1516 support for Tx & Rx.x + * This option defaults to disabled (cleared) */ + +pub(crate) const FXMAC_VLAN_OPTION: u32 = 0x00000004; +/* VLAN Rx & Tx frame support. + * This option defaults to disabled (cleared) */ + +pub(crate) const FXMAC_FLOW_CONTROL_OPTION: u32 = 0x00000010; +/* Enable recognition of flow control frames on Rx + * This option defaults to enabled (set) */ + +pub(crate) const FXMAC_FCS_STRIP_OPTION: u32 = 0x00000020; +/* Strip FCS and PAD from incoming frames. Note: PAD from VLAN frames is not + * stripped. + * This option defaults to enabled (set) */ + +pub(crate) const FXMAC_FCS_INSERT_OPTION: u32 = 0x00000040; +/* Generate FCS field and add PAD automatically for outgoing frames. + * This option defaults to disabled (cleared) */ + +pub(crate) const FXMAC_LENTYPE_ERR_OPTION: u32 = 0x00000080; +/* Enable Length/Type error checking for incoming frames. When this option is + * set, the MAC will filter frames that have a mismatched type/length field + * and if FXMAC_REPORT_RXERR_OPTION is set, the user is notified when these + * types of frames are encountered. When this option is cleared, the MAC will + * allow these types of frames to be received. + * + * This option defaults to disabled (cleared) */ + +pub(crate) const FXMAC_TRANSMITTER_ENABLE_OPTION: u32 = 0x00000100; +/* Enable the transmitter. + * This option defaults to enabled (set) */ + +pub(crate) const FXMAC_RECEIVER_ENABLE_OPTION: u32 = 0x00000200; +/* Enable the receiver + * This option defaults to enabled (set) */ + +pub(crate) const FXMAC_BROADCAST_OPTION: u32 = 0x00000400; +/* Allow reception of the broadcast address + * This option defaults to enabled (set) */ + +pub(crate) const FXMAC_MULTICAST_OPTION: u32 = 0x00000800; +/* Allows reception of multicast addresses programmed into hash + * This option defaults to disabled (clear) */ + +pub(crate) const FXMAC_RX_CHKSUM_ENABLE_OPTION: u32 = 0x00001000; +/* Enable the RX checksum offload + * This option defaults to enabled (set) */ + +pub(crate) const FXMAC_TX_CHKSUM_ENABLE_OPTION: u32 = 0x00002000; +/* Enable the TX checksum offload + * This option defaults to enabled (set) */ + +pub(crate) const FXMAC_JUMBO_ENABLE_OPTION: u32 = 0x00004000; +pub(crate) const FXMAC_SGMII_ENABLE_OPTION: u32 = 0x00008000; + +pub(crate) const FXMAC_LOOPBACK_NO_MII_OPTION: u32 = 0x00010000; +pub(crate) const FXMAC_LOOPBACK_USXGMII_OPTION: u32 = 0x00020000; + +pub(crate) const FXMAC_UNICAST_OPTION: u32 = 0x00040000; + +pub(crate) const FXMAC_TAIL_PTR_OPTION: u32 = 0x00080000; + +pub(crate) const FXMAC_DEFAULT_OPTIONS: u32 = (FXMAC_FLOW_CONTROL_OPTION + | FXMAC_FCS_INSERT_OPTION + | FXMAC_FCS_STRIP_OPTION + | FXMAC_BROADCAST_OPTION + | FXMAC_LENTYPE_ERR_OPTION + | FXMAC_TRANSMITTER_ENABLE_OPTION + | FXMAC_RECEIVER_ENABLE_OPTION + | FXMAC_RX_CHKSUM_ENABLE_OPTION + | FXMAC_TX_CHKSUM_ENABLE_OPTION); + +/* The next few constants help upper layers determine the size of memory + * pools used for Ethernet buffers and descriptor lists. + */ +pub(crate) const FXMAC_MAC_ADDR_SIZE: u32 = 6; /* size of Ethernet header */ + +pub(crate) const FXMAC_MTU: u32 = 1500; /* max MTU size of Ethernet frame */ +pub(crate) const FXMAC_MTU_JUMBO: u32 = 10240; /* max MTU size of jumbo frame including Ip header + IP payload */ +pub(crate) const FXMAC_HDR_SIZE: u32 = 14; /* size of Ethernet header , DA + SA + TYPE*/ +pub(crate) const FXMAC_HDR_VLAN_SIZE: u32 = 18; /* size of Ethernet header with VLAN */ +pub(crate) const FXMAC_TRL_SIZE: u32 = 4; /* size of Ethernet trailer (FCS) */ + +pub(crate) const FXMAC_MAX_FRAME_SIZE: u32 = (FXMAC_MTU + FXMAC_HDR_SIZE + FXMAC_TRL_SIZE); +pub(crate) const FXMAC_MAX_FRAME_SIZE_JUMBO: u32 = + (FXMAC_MTU_JUMBO + FXMAC_HDR_SIZE + FXMAC_TRL_SIZE); + +pub(crate) const FXMAC_MAX_VLAN_FRAME_SIZE: u32 = + (FXMAC_MTU + FXMAC_HDR_SIZE + FXMAC_HDR_VLAN_SIZE + FXMAC_TRL_SIZE); +pub(crate) const FXMAC_MAX_VLAN_FRAME_SIZE_JUMBO: u32 = + (FXMAC_MTU_JUMBO + FXMAC_HDR_SIZE + FXMAC_HDR_VLAN_SIZE + FXMAC_TRL_SIZE); + +/** @name Callback identifiers + * + * These constants are used as parameters to FXMAC_SetHandler() + * @{ + */ +pub(crate) const FXMAC_HANDLER_DMASEND: u32 = 1; /* 发送中断 */ +pub(crate) const FXMAC_HANDLER_DMARECV: u32 = 2; /* 接收中断 */ +pub(crate) const FXMAC_HANDLER_ERROR: u32 = 3; /* 异常中断 */ +pub(crate) const FXMAC_HANDLER_LINKCHANGE: u32 = 4; /* 连接状态 */ +pub(crate) const FXMAC_HANDLER_RESTART: u32 = 5; /* 发送描述符队列发生异常 */ +/*@}*/ + +pub(crate) const FXMAC_DMA_SG_IS_STARTED: u32 = 0; +pub(crate) const FXMAC_DMA_SG_IS_STOPED: u32 = 1; + +pub(crate) const FXMAC_SPEED_10: u32 = 10; +pub(crate) const FXMAC_SPEED_100: u32 = 100; +pub(crate) const FXMAC_SPEED_1000: u32 = 1000; +pub(crate) const FXMAC_SPEED_2500: u32 = 2500; +pub(crate) const FXMAC_SPEED_5000: u32 = 5000; +pub(crate) const FXMAC_SPEED_10000: u32 = 10000; +pub(crate) const FXMAC_SPEED_25000: u32 = 25000; + +/* Capability mask bits */ +pub(crate) const FXMAC_CAPS_ISR_CLEAR_ON_WRITE: u32 = 0x00000001; /* irq status parameters need to be written to clear after they have been read */ +pub(crate) const FXMAC_CAPS_TAILPTR: u32 = 0x00000002; /* use tail ptr */ + +/* +Direction identifiers +These are used by several functions and callbacks that need +to specify whether an operation specifies a send or receive channel. + +pub(crate) const FXMAC_PHY_INTERFACE_MODE_2500BASEX: u32 = 6; +pub(crate) const FXMAC_PHY_INTERFACE_MODE_5GBASER: u32 = 5; +pub(crate) const FXMAC_PHY_INTERFACE_MODE_USXGMII: u32 = 4; +pub(crate) const FXMAC_PHY_INTERFACE_MODE_XGMII: u32 = 3; +pub(crate) const FXMAC_PHY_INTERFACE_MODE_RGMII: u32 = 2; +pub(crate) const FXMAC_PHY_INTERFACE_MODE_RMII: u32 = 1; +pub(crate) const FXMAC_PHY_INTERFACE_MODE_SGMII: u32 = 0; +*/ diff --git a/components/fxmac_rs/src/fxmac_dma.rs b/components/fxmac_rs/src/fxmac_dma.rs new file mode 100644 index 000000000..0c26a7f57 --- /dev/null +++ b/components/fxmac_rs/src/fxmac_dma.rs @@ -0,0 +1,1513 @@ +//! DMA buffer descriptor management for FXMAC Ethernet. +//! +//! This module handles DMA-based packet transmission and reception, +//! including buffer descriptor ring management. + +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::cmp::min; +use core::mem::size_of; +use core::ptr::{null, null_mut}; +use core::slice::from_raw_parts_mut; + +use crate::fxmac::*; +use crate::fxmac_const::*; +use crate::fxmac_phy::*; +use crate::utils::*; + +// fxmac_lwip_port.h +pub const FXMAX_RX_BDSPACE_LENGTH: usize = FXMAX_RX_PBUFS_LENGTH * 64; // 0x20000, default set 128KB +pub const FXMAX_TX_BDSPACE_LENGTH: usize = FXMAX_RX_PBUFS_LENGTH * 64; // 0x20000, default set 128KB + +pub const FXMAX_RX_PBUFS_LENGTH: usize = 128; // 128 +pub const FXMAX_TX_PBUFS_LENGTH: usize = 128; + +pub const FXMAX_MAX_HARDWARE_ADDRESS_LENGTH: usize = 6; + +/* configuration */ +pub const FXMAC_LWIP_PORT_CONFIG_JUMBO: u32 = BIT(0); +pub const FXMAC_LWIP_PORT_CONFIG_MULTICAST_ADDRESS_FILITER: u32 = BIT(1); /* Allow multicast address filtering */ +pub const FXMAC_LWIP_PORT_CONFIG_COPY_ALL_FRAMES: u32 = BIT(2); /* enable copy all frames */ +pub const FXMAC_LWIP_PORT_CONFIG_CLOSE_FCS_CHECK: u32 = BIT(3); /* close fcs check */ +pub const FXMAC_LWIP_PORT_CONFIG_UNICAST_ADDRESS_FILITER: u32 = BIT(5); /* Allow unicast address filtering */ + +/* Phy */ +pub const FXMAC_PHY_SPEED_10M: u32 = 10; +pub const FXMAC_PHY_SPEED_100M: u32 = 100; +pub const FXMAC_PHY_SPEED_1000M: u32 = 1000; +pub const FXMAC_PHY_SPEED_10G: u32 = 10000; + +pub const FXMAC_PHY_HALF_DUPLEX: u32 = 0; +pub const FXMAC_PHY_FULL_DUPLEX: u32 = 1; + +pub const FXMAC_RECV_MAX_COUNT: u32 = 10; + +/* frame queue */ +pub const PQ_QUEUE_SIZE: u32 = 4096; + +/// Transmit buffer descriptor status words offset +/// word 0/addr of BDs +pub const FXMAC_BD_ADDR_OFFSET: u32 = 0; +/// word 1/status of BDs, 4 bytes +pub const FXMAC_BD_STAT_OFFSET: u32 = 4; +/// word 2/addr of BDs +pub const FXMAC_BD_ADDR_HI_OFFSET: u32 = 1 << 3; + +/// RX Used bit +pub const FXMAC_RXBUF_NEW_MASK: u32 = 1 << 0; +/// RX Wrap bit, last BD +pub const FXMAC_RXBUF_WRAP_MASK: u32 = 1 << 1; +/// Mask for address +pub const FXMAC_RXBUF_ADD_MASK: u32 = GENMASK(31, 2); +// FXMAC_RXBUF_ADD_MASK=0xff_ff_ff_fc + +/// TX Used bit +pub const FXMAC_TXBUF_USED_MASK: u32 = 1 << 31; +/// TX Wrap bit, last descriptor +pub const FXMAC_TXBUF_WRAP_MASK: u32 = 1 << 30; + +// Note: ULONG64_HI_MASK and ULONG64_LO_MASK are defined in fxmac.rs +use crate::fxmac::{ULONG64_HI_MASK, ULONG64_LO_MASK}; + +/// Byte alignment of BDs +pub const BD_ALIGNMENT: u64 = FXMAC_DMABD_MINIMUM_ALIGNMENT * 2; // 128 +pub const FXMAC_DMABD_MINIMUM_ALIGNMENT: u64 = 64; +pub const FXMAC_BD_NUM_WORDS: usize = 4; + +/// DMA address width 64 bits: +/// word 0: 32 bit address of Data Buffer +/// word 1: control / status, 32-bit +/// word 2: upper 32 bit address of Data Buffer +/// word 3: unused +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct macb_dma_desc { + pub addr: u32, + pub ctrl: u32, + pub addrh: u32, + pub resvd: u32, +} + +pub type FXmacBd = [u32; FXMAC_BD_NUM_WORDS]; + +// sizeof(uintptr)=8 +pub type uintptr = u64; + +// Enable tail Register +//pub const FXMAC_TAIL_ENABLE: u32 = 0xe7c; + +/// send direction +pub const FXMAC_SEND: u32 = 1; +/// receive direction +pub const FXMAC_RECV: u32 = 2; + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct FXmacBdRing { + pub phys_base_addr: uintptr, + pub base_bd_addr: uintptr, + pub high_bd_addr: uintptr, + pub length: u32, + pub run_state: u32, + pub separation: u32, + pub free_head: *mut FXmacBd, + pub pre_head: *mut FXmacBd, + pub hw_head: *mut FXmacBd, + pub hw_tail: *mut FXmacBd, + pub post_head: *mut FXmacBd, + pub bda_restart: *mut FXmacBd, + pub hw_cnt: u32, + pub pre_cnt: u32, + pub free_cnt: u32, + pub post_cnt: u32, + pub all_cnt: u32, +} + +impl Default for FXmacBdRing { + fn default() -> Self { + Self { + phys_base_addr: 0, + base_bd_addr: 0, + high_bd_addr: 0, + length: 0, + run_state: 0, + separation: 0, + free_head: null_mut(), + pre_head: null_mut(), + hw_head: null_mut(), + hw_tail: null_mut(), + post_head: null_mut(), + bda_restart: null_mut(), + hw_cnt: 0, + pre_cnt: 0, + free_cnt: 0, + post_cnt: 0, + all_cnt: 0, + } + } +} + +pub struct FXmacNetifBuffer { + // 作为FXmacBdRing的基地址,并设置成一串多个BD + pub rx_bdspace: usize, // [u8; FXMAX_RX_BDSPACE_LENGTH], aligned(256); 接收bd 缓冲区 + pub tx_bdspace: usize, // FXMAX_TX_BDSPACE_LENGTH, aligned(256); 发送bd 缓冲区 + + // 保存收发数据包的内存基地址,收发数据包内存需要申请alloc + pub rx_pbufs_storage: [uintptr; FXMAX_RX_PBUFS_LENGTH], + pub tx_pbufs_storage: [uintptr; FXMAX_TX_PBUFS_LENGTH], +} + +impl Default for FXmacNetifBuffer { + fn default() -> Self { + let alloc_pages = FXMAX_RX_BDSPACE_LENGTH.div_ceil(PAGE_SIZE); + let (mut rx_vaddr, mut rx_dma) = + crate_interface::call_interface!(crate::KernelFunc::dma_alloc_coherent(alloc_pages)); + + let alloc_pages = FXMAX_TX_BDSPACE_LENGTH.div_ceil(PAGE_SIZE); + let (mut tx_vaddr, mut tx_dma) = + crate_interface::call_interface!(crate::KernelFunc::dma_alloc_coherent(alloc_pages)); + + //let rx_buf = unsafe { from_raw_parts_mut(vaddr as *mut u8, FXMAX_RX_BDSPACE_LENGTH) }; + + Self { + rx_bdspace: rx_vaddr, + tx_bdspace: tx_vaddr, + rx_pbufs_storage: [0; FXMAX_RX_PBUFS_LENGTH], + tx_pbufs_storage: [0; FXMAX_TX_PBUFS_LENGTH], + } + } +} + +pub struct FXmacLwipPort { + pub buffer: FXmacNetifBuffer, + // configuration + pub feature: u32, + pub hwaddr: [u8; FXMAX_MAX_HARDWARE_ADDRESS_LENGTH], + // Indicating how many receive interrupts have been triggered + pub recv_flg: u32, +} + +pub fn fxmac_bd_read(bd_ptr: u64, offset: u32) -> u32 { + trace!("fxmac_bd_read at {:#x}", bd_ptr + offset as u64); + read_reg( + (crate_interface::call_interface!(crate::KernelFunc::virt_to_phys(bd_ptr as usize)) + + offset as usize) as *const u32, + ) +} +pub fn fxmac_bd_write(bd_ptr: u64, offset: u32, data: u32) { + debug!( + "fxmac_bd_write {:#x} to {:#x}", + data, + bd_ptr + offset as u64 + ); + // uintptr: u64 + write_reg( + (crate_interface::call_interface!(crate::KernelFunc::virt_to_phys(bd_ptr as usize)) + + offset as usize) as *mut u32, + data, + ); +} + +/// FXmacBdSetRxWrap +/// Set this bit to mark the last descriptor in the receive buffer descriptor list. +fn FXmacBdSetRxWrap(mut bdptr: u64) { + bdptr += FXMAC_BD_ADDR_OFFSET as u64; + let temp_ptr = bdptr as *mut u32; + if !temp_ptr.is_null() { + let mut data_value_rx: u32 = unsafe { *temp_ptr }; + info!( + "RX WRAP of BD @ {:#x} set {:#x} | FXMAC_RXBUF_WRAP_MASK", + bdptr, data_value_rx + ); + data_value_rx |= FXMAC_RXBUF_WRAP_MASK; + unsafe { + temp_ptr.write_volatile(data_value_rx); + } + } +} + +/// FXmacBdSetTxWrap +/// Sets this bit to mark the last descriptor in the transmit buffer descriptor list. +fn FXmacBdSetTxWrap(mut bdptr: u64) { + bdptr += FXMAC_BD_STAT_OFFSET as u64; + let temp_ptr = bdptr as *mut u32; + if !temp_ptr.is_null() { + let mut data_value_tx: u32 = unsafe { *temp_ptr }; + info!( + "TX WRAP of BD @ {:#x} set {:#x} | TXBUF_WRAP", + bdptr, data_value_tx + ); + data_value_tx |= FXMAC_TXBUF_WRAP_MASK; + unsafe { + temp_ptr.write_volatile(data_value_tx); + } + } +} + +/// Reset BD ring head and tail pointers. +fn FXmacBdringPtrReset(ring_ptr: &mut FXmacBdRing, virtaddrloc: *mut FXmacBd) { + ring_ptr.free_head = virtaddrloc; + ring_ptr.pre_head = virtaddrloc; + ring_ptr.hw_head = virtaddrloc; + ring_ptr.hw_tail = virtaddrloc; + ring_ptr.post_head = virtaddrloc; +} + +/// Set the BD's address field (word 0). +fn fxmac_bd_set_address_rx(bd_ptr: u64, addr: u64) { + fxmac_bd_write( + (bd_ptr), + FXMAC_BD_ADDR_OFFSET, + ((fxmac_bd_read(bd_ptr, FXMAC_BD_ADDR_OFFSET) & !FXMAC_RXBUF_ADD_MASK) + | (addr & ULONG64_LO_MASK) as u32), + ); + + fxmac_bd_write( + bd_ptr, + FXMAC_BD_ADDR_HI_OFFSET, + ((addr & ULONG64_HI_MASK) >> 32) as u32, + ); + + // For aarch64 +} + +/// Set the BD's address field (word 0). +fn fxmac_bd_set_address_tx(bd_ptr: u64, addr: u64) { + fxmac_bd_write( + bd_ptr, + FXMAC_BD_ADDR_OFFSET, + (addr & ULONG64_LO_MASK) as u32, + ); + + fxmac_bd_write( + bd_ptr, + FXMAC_BD_ADDR_HI_OFFSET, + ((addr & ULONG64_HI_MASK) >> 32) as u32, + ); + + // For aarch64 +} + +/// 将bdptr参数向前移动任意数量的bd,绕到环的开头。 +fn FXMAC_RING_SEEKAHEAD(ring_ptr: &mut FXmacBdRing, bdptr: &mut (*mut FXmacBd), num_bd: u32) { + trace!("FXMAC_RING_SEEKAHEAD, bdptr={:#x}", *bdptr as u64); + + // 第一个free BD + // bdptr = free_head + let mut addr: u64 = *bdptr as u64; + addr += (ring_ptr.separation * num_bd) as u64; + + if (addr > ring_ptr.high_bd_addr) || (*bdptr as u64 > addr) { + addr -= ring_ptr.length as u64; + } + *bdptr = addr as *mut FXmacBd; + + trace!( + "FXMAC_RING_SEEKAHEAD, bdptr: {:#x}, addr: {:#x}", + *bdptr as u64, + addr + ); +} + +pub fn FXmacAllocDmaPbufs(instance_p: &mut FXmac) -> u32 { + // 为DMA构建环形缓冲区内存 + + let mut status: u32 = 0; + let rxringptr: &mut FXmacBdRing = &mut instance_p.rx_bd_queue.bdring; + let txringptr: &mut FXmacBdRing = &mut instance_p.tx_bd_queue.bdring; + + // Allocate RX descriptors, 1 RxBD at a time. + info!("Allocate RX descriptors, 1 RxBD at a time."); + for i in 0..FXMAX_RX_PBUFS_LENGTH { + let max_frame_size = if (instance_p.lwipport.feature & FXMAC_LWIP_PORT_CONFIG_JUMBO) != 0 { + info!("FXMAC_LWIP_PORT_CONFIG_JUMBO"); + FXMAC_MAX_FRAME_SIZE_JUMBO + } else { + info!("NO CONFIG_JUMBO"); + FXMAC_MAX_FRAME_SIZE + }; + + let alloc_rx_buffer_pages = (max_frame_size as usize).div_ceil(PAGE_SIZE); + let (mut rx_mbufs_vaddr, mut rx_mbufs_dma) = crate_interface::call_interface!( + crate::KernelFunc::dma_alloc_coherent(alloc_rx_buffer_pages) + ); + + let rxringptr: &mut FXmacBdRing = &mut instance_p.rx_bd_queue.bdring; + let mut rxbd: *mut FXmacBd = null_mut(); + //let my_speed: Box = Box::new(88); + //rxbd = Box::into_raw(my_speed); + // OR + //let mut my_speed: i32 = 88; + //rxbd = &mut my_speed; + + // 在BD list中预留待设置的BD + status = FXmacBdRingAlloc(rxringptr, 1, &mut rxbd); + assert!(!rxbd.is_null()); + if (status != 0) { + error!("FXmacInitDma: Error allocating RxBD"); + return status; + } + + // 将一组BD排队到之前由FXmacBdRingAlloc分配了的硬件上 + status = FXmacBdRingToHw(rxringptr, 1, rxbd); + + let bdindex = FXMAC_BD_TO_INDEX(rxringptr, rxbd as u64); + + let mut temp = rxbd as *mut u32; + + let mut v = 0; + if bdindex == (FXMAX_RX_PBUFS_LENGTH - 1) as u32 { + // Marks last descriptor in receive buffer descriptor list + v |= FXMAC_RXBUF_WRAP_MASK; + } + unsafe { + temp.write_volatile(v); + // Clear word 1 in descriptor + temp.add(1).write_volatile(0); + } + crate::utils::DSB(); + + // dc civac, virt_addr 通过虚拟地址清除和无效化cache + crate::utils::FCacheDCacheInvalidateRange(rx_mbufs_vaddr as u64, max_frame_size as u64); + + // Set the BD's address field (word 0) + // void *payload; 指向数据区域的指针,指向该pbuf管理的数据区域起始地址,可以是ROM或者RAM中的某个地址 + fxmac_bd_set_address_rx(rxbd as u64, rx_mbufs_dma as u64); + + instance_p.lwipport.buffer.rx_pbufs_storage[bdindex as usize] = rx_mbufs_vaddr as u64; + } + + for index in 0..FXMAX_TX_PBUFS_LENGTH { + let max_fr_size = if (instance_p.lwipport.feature & FXMAC_LWIP_PORT_CONFIG_JUMBO) != 0 { + FXMAC_MAX_FRAME_SIZE_JUMBO + } else { + FXMAC_MAX_FRAME_SIZE + }; + let alloc_pages = (max_fr_size as usize).div_ceil(PAGE_SIZE); + let (mut tx_mbufs_vaddr, mut tx_mbufs_dma) = + crate_interface::call_interface!(crate::KernelFunc::dma_alloc_coherent(alloc_pages)); + + instance_p.lwipport.buffer.tx_pbufs_storage[index] = tx_mbufs_vaddr as u64; + + /* + let txbd: *mut FXmacBd = null_mut(); + FXmacBdRingAlloc(txringptr, 1, txbd); + FXmacBdRingToHw(txringptr, 1, txbd); + + let bdindex = FXMAC_BD_TO_INDEX(txringptr, txbd as u64); + assert!(index == bdindex as usize); + */ + + // From index to BD + let txbd = + (txringptr.base_bd_addr + (index as u64 * txringptr.separation as u64)) as *mut FXmacBd; + + fxmac_bd_set_address_tx(txbd as u64, tx_mbufs_dma as u64); + //debug!("TX DMA DESC {}: {:#010x?}", index, unsafe{*(txbd as *const macb_dma_desc)}); + + //curbdpntr = FXMAC_BD_RING_NEXT(txring, curbdpntr); + crate::utils::DSB(); + } + 0 +} + +pub fn FXmacInitDma(instance_p: &mut FXmac) -> u32 { + //let mut rxbd: FXmacBd = [0; FXMAC_BD_NUM_WORDS]; + + let rxringptr: &mut FXmacBdRing = &mut instance_p.rx_bd_queue.bdring; + let txringptr: &mut FXmacBdRing = &mut instance_p.tx_bd_queue.bdring; + info!("FXmacInitDma, rxringptr: {:p}", rxringptr); + info!("FXmacInitDma, txringptr: {:p}", txringptr); + info!( + "FXmacInitDma, rx_bdspace: {:#x}", + &instance_p.lwipport.buffer.rx_bdspace + ); + info!( + "FXmacInitDma, tx_bdspace: {:#x}", + &instance_p.lwipport.buffer.tx_bdspace + ); + + // Setup RxBD space. + // 对BD域清零 + //let mut bdtemplate: FXmacBd = unsafe { core::mem::zeroed() }; + + // FXmacBd: The FXMAC_Bd is the type for buffer descriptors (BDs). + let mut bdtemplate: FXmacBd = [0; FXMAC_BD_NUM_WORDS]; + + // Create the RxBD ring, bdspace地址必须对齐128 + // 创建收包的环形缓冲区 + let mut status: u32 = FXmacBdRingCreate( + rxringptr, + instance_p.lwipport.buffer.rx_bdspace as u64, + instance_p.lwipport.buffer.rx_bdspace as u64, + BD_ALIGNMENT, + FXMAX_RX_PBUFS_LENGTH as u32, + ); + + // 将给定的BD, 克隆到list中的每个BD上 + status = FXmacBdRingClone(rxringptr, &bdtemplate, FXMAC_RECV); + + /*let bdtem = macb_dma_desc { + addr: 0, + ctrl: FXMAC_TXBUF_USED_MASK, + addrh: 0, + resvd: 0, + };*/ + bdtemplate.fill(0); + // FXMAC_BD_SET_STATUS(), 注:这里先设置TXBUF_USED位,对于发包很重要! + // Set the BD's Status field (word 1). + fxmac_bd_write( + (&bdtemplate as *const _ as u64), + FXMAC_BD_STAT_OFFSET, + fxmac_bd_read((&mut bdtemplate as *mut _ as u64), FXMAC_BD_STAT_OFFSET) + | (FXMAC_TXBUF_USED_MASK), + ); + + /* Create the TxBD ring */ + status = FXmacBdRingCreate( + txringptr, + instance_p.lwipport.buffer.tx_bdspace as u64, + instance_p.lwipport.buffer.tx_bdspace as u64, + BD_ALIGNMENT, + FXMAX_TX_PBUFS_LENGTH as u32, + ); + + /* We reuse the bd template, as the same one will work for both rx and tx. */ + status = FXmacBdRingClone(txringptr, &bdtemplate, FXMAC_SEND); + + // 创建收发网络包的DMA内存 + FXmacAllocDmaPbufs(instance_p); + + FXmacSetQueuePtr(instance_p.rx_bd_queue.bdring.phys_base_addr, 0, FXMAC_RECV); + FXmacSetQueuePtr(instance_p.tx_bd_queue.bdring.phys_base_addr, 0, FXMAC_SEND); + + let FXMAC_TAIL_QUEUE = |queue: u64| 0x0e80 + (queue << 2); + if (instance_p.config.caps & FXMAC_CAPS_TAILPTR) != 0 { + write_reg( + (instance_p.config.base_address + FXMAC_TAIL_QUEUE(0)) as *mut u32, + 1 << 31, + ); + } + + 0 +} + +fn FXMAC_BD_TO_INDEX(ringptr: &mut FXmacBdRing, bdptr: u64) -> u32 { + ((bdptr - ringptr.base_bd_addr) / ringptr.separation as u64) as u32 +} + +/// 从bptr的列表中,获取下一个BD +fn FXMAC_BD_RING_NEXT(ring_ptr: &mut FXmacBdRing, bd_ptr: *mut FXmacBd) -> *mut FXmacBd { + if bd_ptr as u64 >= ring_ptr.high_bd_addr { + ring_ptr.base_bd_addr as *mut FXmacBd + } else { + (bd_ptr as u64 + ring_ptr.separation as u64) as *mut FXmacBd + } +} + +/// Create the RxBD ring +/// 创建收包的环形缓冲区 +pub fn FXmacBdRingCreate( + ring_ptr: &mut FXmacBdRing, + phys_addr: u64, + virt_addr: u64, + alignment: u64, + bd_count: u32, +) -> u32 { + // uintptr: u64 + + // alignment=128, bd_count=128 + //let alignment = BD_ALIGNMENT; + //let bd_count = FXMAX_RX_PBUFS_LENGTH; + + //let virt_addr_loc = instance_p.buffer.rx_bdspace; + let virt_addr_loc: u64 = virt_addr; + + ring_ptr.all_cnt = 0; + ring_ptr.free_cnt = 0; + ring_ptr.hw_cnt = 0; + ring_ptr.pre_cnt = 0; + ring_ptr.post_cnt = 0; + + // 该地址必须对齐alignment=128 + assert!((virt_addr_loc % alignment) == 0); + assert!(bd_count > 0); + + // 相邻BD之间隔多少bytes + ring_ptr.separation = size_of::() as u32; + + // Initial ring setup: + // - Clear the entire space + // - Setup each BD's BDA field with the physical address of the next BD + let rxringptr = unsafe { from_raw_parts_mut(virt_addr_loc as *mut FXmacBd, bd_count as usize) }; + rxringptr.fill([0; FXMAC_BD_NUM_WORDS]); + + let mut bd_virt_addr = virt_addr_loc; + for i in 1..bd_count { + bd_virt_addr += ring_ptr.separation as u64; + } + + info!( + "FXmacBdRingCreate BDs count={}, separation={}, {:#x}~{:#x}", + bd_count, ring_ptr.separation, virt_addr, bd_virt_addr + ); + // Setup and initialize pointers and counters + + // BD list中第一个的虚拟地址 + ring_ptr.base_bd_addr = virt_addr_loc; + // BD list中最后一个的虚拟地址 + ring_ptr.high_bd_addr = bd_virt_addr; + // ring的总大小bytes + ring_ptr.length = (ring_ptr.high_bd_addr - ring_ptr.base_bd_addr) as u32 + ring_ptr.separation; + // 第一个free BD + ring_ptr.free_head = virt_addr_loc as *mut FXmacBd; + // 第一个pre-work BD + ring_ptr.pre_head = virt_addr_loc as *mut FXmacBd; + + // 可分配的free BD数量 + ring_ptr.free_cnt = bd_count; + // 总共的BD数 + ring_ptr.all_cnt = bd_count; + + ring_ptr.run_state = FXMAC_DMA_SG_IS_STOPED; + ring_ptr.phys_base_addr = phys_addr; + ring_ptr.hw_head = virt_addr_loc as *mut FXmacBd; + ring_ptr.hw_tail = virt_addr_loc as *mut FXmacBd; + ring_ptr.post_head = virt_addr_loc as *mut FXmacBd; + ring_ptr.bda_restart = phys_addr as *mut FXmacBd; + + 0 +} + +/// 将给定的BD, 克隆到list中的每个BD上 +pub fn FXmacBdRingClone(ring_ptr: &mut FXmacBdRing, src_bd_ptr: &FXmacBd, direction: u32) -> u32 { + // Can't do this function with some of the BDs in use + assert!(ring_ptr.free_cnt == ring_ptr.all_cnt); + + let mut cur_bd = ring_ptr.base_bd_addr; + for i in 0..ring_ptr.all_cnt { + trace!("FXmacBdRingClone, copy current bd @ {:#x}", cur_bd); + // 将所有BD逐个复制成bdtemplate + let cur_bd_slice = unsafe { from_raw_parts_mut(cur_bd as *mut FXmacBd, 1) }; + cur_bd_slice[0].copy_from_slice(src_bd_ptr); + crate::utils::DSB(); + + cur_bd += ring_ptr.separation as u64; + } + cur_bd -= ring_ptr.separation as u64; + + // 对最后一个BD描述符进行位标记 + if direction == FXMAC_RECV { + FXmacBdSetRxWrap(cur_bd); + } else { + FXmacBdSetTxWrap(cur_bd); + } + + 0 +} + +/// 在BD list中预留待设置的BD +pub fn FXmacBdRingAlloc( + ring_ptr: &mut FXmacBdRing, + num_bd: u32, + bd_set_ptr: &mut (*mut FXmacBd), +) -> u32 { + /* + let num_bd = 1; + let bd_set_ptr = &rxbd; + */ + + if ring_ptr.free_cnt < num_bd { + error!("No Enough free BDs available for the request: {}", num_bd); + 4 + } else { + // 获取待设置的BD,并向前移动free BD + *bd_set_ptr = ring_ptr.free_head as *mut FXmacBd; + + let b = ring_ptr.free_head; + let mut free_head_t = ring_ptr.free_head; + FXMAC_RING_SEEKAHEAD(ring_ptr, &mut free_head_t, num_bd); + ring_ptr.free_head = free_head_t; + + debug!( + "free_head {:#x} seekahead to {:#x}", + b as usize, ring_ptr.free_head as usize + ); + assert!(b as usize != ring_ptr.free_head as usize); + + ring_ptr.free_cnt -= num_bd; + ring_ptr.pre_cnt += num_bd; + + 0 + } +} + +/// 将一组BD排队到之前由FXmacBdRingAlloc分配了的硬件上 +pub fn FXmacBdRingToHw(ring_ptr: &mut FXmacBdRing, num_bd: u32, bd_set_ptr: *mut FXmacBd) -> u32 { + // uintptr: u64 + + let mut cur_bd_ptr: *mut FXmacBd = bd_set_ptr; + for i in 0..num_bd { + cur_bd_ptr = FXMAC_BD_RING_NEXT(ring_ptr, cur_bd_ptr); + } + + let mut pre_head_t = ring_ptr.pre_head; + FXMAC_RING_SEEKAHEAD(ring_ptr, &mut pre_head_t, num_bd); + ring_ptr.pre_head = pre_head_t; + + ring_ptr.pre_cnt -= num_bd; + ring_ptr.hw_tail = cur_bd_ptr; + ring_ptr.hw_cnt += num_bd; + + 0 +} + +pub fn FXmacBdRingFromHwRx( + ring_ptr: &mut FXmacBdRing, + bd_limit: usize, + bd_set_ptr: &mut (*mut FXmacBd), +) -> u32 { + let mut cur_bd_ptr: *mut FXmacBd = ring_ptr.hw_head; + let mut status: u32 = 0; + let mut bd_str: u32 = 0; + let mut bd_count: u32 = 0; + let mut bd_partial_count: u32 = 0; + + if ring_ptr.hw_cnt == 0 { + warn!("No BDs in RX work group, there's nothing to search"); + + *bd_set_ptr = null_mut(); + + status = 0; + } else { + /* Starting at hw_head, keep moving forward in the list until: + * - A BD is encountered with its new/used bit set which means hardware has completed processing of that BD. + * - ring_ptr->hw_tail is reached and ring_ptr->hw_cnt is reached. + * - The number of requested BDs has been processed + */ + while (bd_count as usize) < bd_limit { + // Read the status + + bd_str = fxmac_bd_read(cur_bd_ptr as u64, FXMAC_BD_STAT_OFFSET); + + // FXMAC_BD_IS_RX_NEW, Determine the new bit of the receive BD + let bd_rx_new = + fxmac_bd_read(cur_bd_ptr as u64, FXMAC_BD_ADDR_OFFSET) & FXMAC_RXBUF_NEW_MASK; + if bd_rx_new == 0 { + break; + } + //debug!("FXMAC_RXBUF_NEW_MASK got {:#x}", bd_rx_new); + + bd_count += 1; + + /* hardware has processed this BD so check the "last" bit. If + * it is clear, then there are more BDs for the current packet. + * Keep a count of these partial packet BDs. + */ + if (bd_str & FXMAC_RXBUF_EOF_MASK) != 0 { + bd_partial_count = 0; + } else { + bd_partial_count += 1; + } + + // Move on to next BD in work group + cur_bd_ptr = FXMAC_BD_RING_NEXT(ring_ptr, cur_bd_ptr); + } + + // 减去找到的任何partial packet BDs + bd_count -= bd_partial_count; + + /* If bd_count is non-zero then BDs were found to return. Set return + * parameters, update pointers and counters, return success + */ + if bd_count > 0 { + *bd_set_ptr = ring_ptr.hw_head; + + ring_ptr.hw_cnt -= bd_count; + ring_ptr.post_cnt += bd_count; + + let mut hw_head_t = ring_ptr.hw_head; + FXMAC_RING_SEEKAHEAD(ring_ptr, &mut hw_head_t, bd_count); + ring_ptr.hw_head = hw_head_t; + + info!("FXmacBdRingFromHwRx, Found BD={}", bd_count); + status = bd_count; + } else { + //warn!("FXmacBdRingFromHwRx, no found BD={}", bd_count); + + *bd_set_ptr = null_mut(); + status = 0; + } + } + status +} + +/// @name: FXmacBdRingFromHwTx +/// @msg: Returns a set of BD(s) that have been processed by hardware. The returned +/// BDs may be examined to determine the outcome of the DMA transaction(s). +/// Once the BDs have been examined, the user must call FXmacBdRingFree() +/// in the same order which they were retrieved here. +pub fn FXmacBdRingFromHwTx( + ring_ptr: &mut FXmacBdRing, + bd_limit: usize, + bd_set_ptr: &mut (*mut FXmacBd), +) -> u32 { + let mut bd_str: u32 = 0; + let mut bd_count: u32 = 0; + let mut bd_partial_count: u32 = 0; + let mut status: u32 = 0; + + let mut bd_limitLoc: u32 = bd_limit as u32; + let mut cur_bd_ptr: *mut FXmacBd = ring_ptr.hw_head; + + /* If no BDs in work group, then there's nothing to search */ + if ring_ptr.hw_cnt == 0 { + debug!("No BDs in TX work group, then there's nothing to search"); + *bd_set_ptr = null_mut(); + status = 0; + } else { + if bd_limitLoc > ring_ptr.hw_cnt { + bd_limitLoc = ring_ptr.hw_cnt; + } + /* Starting at hw_head, keep moving forward in the list until: + * - A BD is encountered with its new/used bit set which means + * hardware has not completed processing of that BD. + * - ring_ptr->hw_tail is reached and ring_ptr->hw_cnt is reached. + * - The number of requested BDs has been processed + */ + while bd_count < bd_limitLoc { + // Read the status + bd_str = fxmac_bd_read(cur_bd_ptr as u64, FXMAC_BD_STAT_OFFSET); + + // 当DMA硬件对该BD已经成功处理完成时,TXBUF_USED位则处于被硬件置位; + // 当软件去主动清除TX_USED位时,使能了为DMA硬件准备好的buffer + if (bd_str & FXMAC_TXBUF_USED_MASK) != 0 { + info!("FXmacBdRingFromHwTx, found a hardware USED TXBUF"); + bd_count += 1; + bd_partial_count += 1; + } + + /* hardware has processed this BD so check the "last" bit. + * If it is clear, then there are more BDs for the current + * packet. Keep a count of these partial packet BDs. + */ + if (bd_str & FXMAC_TXBUF_LAST_MASK) != 0 { + bd_partial_count = 0; + } + + /* Move on to next BD in work group */ + cur_bd_ptr = FXMAC_BD_RING_NEXT(ring_ptr, cur_bd_ptr); + } + + info!("FXmacBdRingFromHwTx, Subtract off any partial packet BDs found"); + bd_count -= bd_partial_count; + + /* If bd_count is non-zero then BDs were found to return. Set return + * parameters, update pointers and counters, return success + */ + if bd_count > 0 { + *bd_set_ptr = ring_ptr.hw_head; + + ring_ptr.hw_cnt -= bd_count; + ring_ptr.post_cnt += bd_count; + + let mut hw_head_t = ring_ptr.hw_head; + FXMAC_RING_SEEKAHEAD(ring_ptr, &mut hw_head_t, bd_count); + ring_ptr.hw_head = hw_head_t; + + info!("FXmacBdRingFromHwTx, Found BD={}", bd_count); + status = bd_count; + } else { + *bd_set_ptr = null_mut(); + status = 0; + } + } + + status +} + +/// Transmits packets using scatter-gather DMA. +/// +/// This function sends one or more packets through the FXMAC controller using +/// DMA buffer descriptors. Each packet in the vector is transmitted as a +/// separate frame. +/// +/// # Arguments +/// +/// * `instance_p` - Mutable reference to the FXMAC instance. +/// * `p` - Vector of packets to transmit, where each packet is a `Vec`. +/// +/// # Returns +/// +/// The total number of bytes queued for transmission. +/// +/// # Example +/// +/// ```ignore +/// let mut packets = Vec::new(); +/// packets.push(ethernet_frame.to_vec()); +/// let bytes_sent = FXmacSgsend(fxmac, packets); +/// ``` +pub fn FXmacSgsend(instance_p: &mut FXmac, p: Vec>) -> u32 { + let mut status: u32 = 0; + let mut bdindex: u32 = 0; + let mut max_fr_size: u32 = 0; + let mut send_len: u32 = 0; + + let mut last_txbd: *mut FXmacBd = null_mut(); + let mut txbdset: *mut FXmacBd = null_mut(); + let txring: &mut FXmacBdRing = &mut instance_p.tx_bd_queue.bdring; + + // Count the number of pbufs: p + let n_pbufs: u32 = p.len() as u32; + + debug!("Sending packets: {}", n_pbufs); + /* obtain as many BD's */ + status = FXmacBdRingAlloc(txring, n_pbufs, &mut txbdset); + assert!(!txbdset.is_null()); + + let mut txbd: *mut FXmacBd = txbdset; + for q in &p { + bdindex = FXMAC_BD_TO_INDEX(txring, txbd as u64); + + /* if (instance_p.lwipport.buffer.tx_pbufs_storage[bdindex as usize] != 0) + { + panic!("PBUFS not available"); + }*/ + + if (instance_p.lwipport.feature & FXMAC_LWIP_PORT_CONFIG_JUMBO) != 0 { + max_fr_size = FXMAC_MAX_FRAME_SIZE_JUMBO; + } else { + max_fr_size = FXMAC_MAX_FRAME_SIZE; + } + let pbufs_len = min(q.len(), max_fr_size as usize); + + let pbufs_virt = instance_p.lwipport.buffer.tx_pbufs_storage[bdindex as usize]; + let pbuf = unsafe { from_raw_parts_mut(pbufs_virt as *mut u8, pbufs_len) }; + pbuf.copy_from_slice(q); + crate::utils::FCacheDCacheFlushRange(pbufs_virt, pbufs_len as u64); + warn!( + ">>>>>>>>> TX PKT {} @{:#x} - {}", + pbufs_len, pbufs_virt, bdindex + ); + + debug!(">>>>>>>>> {:x?}", pbuf); + + //fxmac_bd_set_address_tx(txbd as u64, (pbufs_virt & 0xffff_ffff) as u64); + send_len += pbufs_len as u32; + + if q.len() > max_fr_size as usize { + warn!("The packet: {} to be send is TOO LARGE", q.len()); + // FXMAC_BD_SET_LENGTH: 设置BD的发送长度(以bytes为单位)。每次BD交给硬件时,都必须设置该长度 + // FXMAC_BD_SET_LENGTH(txbd, max_fr_size & 0x3FFF); + fxmac_bd_write( + txbd as u64, + FXMAC_BD_STAT_OFFSET, + ((fxmac_bd_read(txbd as u64, FXMAC_BD_STAT_OFFSET) & !FXMAC_TXBUF_LEN_MASK) + | (max_fr_size & 0x3FFF)), + ); + } else { + fxmac_bd_write( + txbd as u64, + FXMAC_BD_STAT_OFFSET, + ((fxmac_bd_read(txbd as u64, FXMAC_BD_STAT_OFFSET) & !FXMAC_TXBUF_LEN_MASK) + | (q.len() as u32 & 0x3FFF)), + ); + } + + //instance_p.lwipport.buffer.tx_pbufs_storage[bdindex as usize] = q.as_ptr() as u64; + + // 增加该pbuf的引用计数。 + //pbuf_ref(q); + last_txbd = txbd; + + // 告诉DMA当前包不是以该BD结束 + //FXMAC_BD_CLEAR_LAST(txbd); + let t_txbd = txbd as u64; + fxmac_bd_write( + t_txbd, + FXMAC_BD_STAT_OFFSET, + fxmac_bd_read(t_txbd, FXMAC_BD_STAT_OFFSET) & !FXMAC_TXBUF_LAST_MASK, + ); + + txbd = FXMAC_BD_RING_NEXT(txring, txbd); + } + // 告诉DMA 该BD标志着当前数据包的结束 + //FXMAC_BD_SET_LAST(last_txbd); + let t_txbd = last_txbd as u64; + fxmac_bd_write( + t_txbd, + FXMAC_BD_STAT_OFFSET, + fxmac_bd_read(t_txbd, FXMAC_BD_STAT_OFFSET) | FXMAC_TXBUF_LAST_MASK, + ); + + ///////// + + /* The bdindex always points to the first free_head in tx_bdrings */ + if (instance_p.config.caps & FXMAC_CAPS_TAILPTR) != 0 { + bdindex = FXMAC_BD_TO_INDEX(txring, txbd as u64); + } + + // The used bit for the 1st BD should be cleared at the end after clearing out used bits for other fragments. + let mut txbd = txbdset; + let FXMAC_BD_CLEAR_TX_USED = |bd_ptr: u64| { + fxmac_bd_write( + bd_ptr, + FXMAC_BD_STAT_OFFSET, + fxmac_bd_read(bd_ptr, FXMAC_BD_STAT_OFFSET) & (!FXMAC_TXBUF_USED_MASK), + ) + }; + + for q in 1..p.len() { + txbd = FXMAC_BD_RING_NEXT(txring, txbd); + FXMAC_BD_CLEAR_TX_USED(txbd as u64); + crate::utils::DSB(); + } + FXMAC_BD_CLEAR_TX_USED(txbdset as u64); // 最后清第一个BD + crate::utils::DSB(); + + status = FXmacBdRingToHw(txring, n_pbufs, txbdset); + + let FXMAC_TAIL_QUEUE = |queue: u64| 0x0e80 + (queue << 2); + if (instance_p.config.caps & FXMAC_CAPS_TAILPTR) != 0 { + write_reg( + (instance_p.config.base_address + FXMAC_TAIL_QUEUE(0)) as *mut u32, + (1 << 31) | bdindex, + ); + } + + debug!("TX DMA DESC: {:#010x?}", unsafe { + *(txbdset as *const macb_dma_desc) + }); + + // Start transmit + let value = read_reg((instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *const u32) + | FXMAC_NWCTRL_STARTTX_MASK; + write_reg( + (instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *mut u32, + value, + ); + + send_len +} + +/// Handles received packets from the DMA queue. +/// +/// This function processes all received packets currently available in the +/// RX buffer descriptor ring. It extracts packet data and returns them as +/// a vector of byte vectors. +/// +/// # Arguments +/// +/// * `instance_p` - Mutable reference to the FXMAC instance. +/// +/// # Returns +/// +/// * `Some(Vec>)` - A vector of received packets if any are available. +/// * `None` - If no packets are available. +/// +/// # Example +/// +/// ```ignore +/// if let Some(packets) = FXmacRecvHandler(fxmac) { +/// for packet in packets { +/// // Process each received Ethernet frame +/// process_packet(&packet); +/// } +/// } +/// ``` +pub fn FXmacRecvHandler(instance_p: &mut FXmac) -> Option>> { + trace!("RX receive packets"); + let mut recv_packets = Vec::new(); + + let mut rxbdset: *mut FXmacBd = null_mut(); + //let rxring: &mut FXmacBdRing = &mut (instance_p.rx_bd_queue.bdring); + + /* If Reception done interrupt is asserted, call RX call back function + to handle the processed BDs and then raise the according flag.*/ + let regval: u32 = read_reg((instance_p.config.base_address + FXMAC_RXSR_OFFSET) as *const u32); + write_reg( + (instance_p.config.base_address + FXMAC_RXSR_OFFSET) as *mut u32, + regval, + ); + + loop { + // Returns a set of BD(s) that have been processed by hardware. + let bd_processed: u32 = FXmacBdRingFromHwRx( + &mut instance_p.rx_bd_queue.bdring, + FXMAX_RX_PBUFS_LENGTH, + &mut rxbdset, + ); + if bd_processed == 0 { + // 没有待收的网络包了 + break; + } + assert!(!rxbdset.is_null()); + + let mut curbdptr: *mut FXmacBd = rxbdset; + for k in 0..bd_processed { + let rxring: &mut FXmacBdRing = &mut instance_p.rx_bd_queue.bdring; + + // Adjust the buffer size to the actual number of bytes received. + let rx_bytes: u32 = if (instance_p.lwipport.feature & FXMAC_LWIP_PORT_CONFIG_JUMBO) != 0 + { + //FXMAC_GET_RX_FRAME_SIZE(curbdptr) + fxmac_bd_read(curbdptr as u64, FXMAC_BD_STAT_OFFSET) & 0x00003FFF + } else { + debug!("FXMAC_RXBUF_LEN_MASK={:#x}", FXMAC_RXBUF_LEN_MASK); + //FXMAC_BD_GET_LENGTH(curbdptr) + fxmac_bd_read(curbdptr as u64, FXMAC_BD_STAT_OFFSET) & FXMAC_RXBUF_LEN_MASK + }; + + let bdindex: u32 = FXMAC_BD_TO_INDEX(rxring, curbdptr as u64); + let pbufs_virt = instance_p.lwipport.buffer.rx_pbufs_storage[bdindex as usize]; + debug!( + "RX PKT {} @{:#x} <<<<<<<<< - {}", + rx_bytes, pbufs_virt, bdindex + ); + let mbuf = unsafe { from_raw_parts_mut(pbufs_virt as *mut u8, rx_bytes as usize) }; + + debug!("pbuf: {:x?}", mbuf); + + // Copy mbuf into a new Vec + recv_packets.push(mbuf.to_vec()); + + /* + The value of hash_match indicates the hash result of the received packet + 0: No hash match + 1: Unicast hash match + 2: Multicast hash match + 3: Reserved, the value is not legal + */ + // FXMAC_BD_GET_HASH_MATCH(bd_ptr) + let mut hash_match: u32 = (fxmac_bd_read(curbdptr as u64, FXMAC_BD_STAT_OFFSET) + & FXMAC_RXBUF_HASH_MASK) + >> 29; + debug!("hash_match is {:#x}", hash_match); + + // Invalidate RX frame before queuing to handle + // L1 cache prefetch conditions on any architecture. + crate::utils::FCacheDCacheInvalidateRange( + instance_p.lwipport.buffer.rx_pbufs_storage[bdindex as usize], + rx_bytes as u64, + ); + + /* store it in the receive queue, + * where it'll be processed by a different handler + */ + //if (FXmacPqEnqueue(&instance_p->recv_q, (void *)p) < 0) + + //instance_p.lwipport.buffer.rx_pbufs_storage[bdindex as usize] = 0; + + // Just clear 64 bits header + mbuf[..min(64, rx_bytes as usize)].fill(0); + + curbdptr = FXMAC_BD_RING_NEXT(rxring, curbdptr); + } + + // free up the BD's + FXmacBdRingFree(&mut instance_p.rx_bd_queue.bdring, bd_processed); + + SetupRxBds(instance_p); + } + + if !recv_packets.is_empty() { + Some(recv_packets) + } else { + None + } +} + +pub fn SetupRxBds(instance_p: &mut FXmac) { + let rxring: &mut FXmacBdRing = &mut instance_p.rx_bd_queue.bdring; + + let mut status: u32 = 0; + let mut rxbd: *mut FXmacBd = null_mut(); + + // let alloc_tx_buffer_pages: usize = ((TX_RING_SIZE * MBUF_SIZE) + (PAGE_SIZE - 1)) / PAGE_SIZE; + // let alloc_rx_buffer_pages: usize = ((RX_RING_SIZE * MBUF_SIZE) + (PAGE_SIZE - 1)) / PAGE_SIZE; + + let mut freebds: u32 = rxring.free_cnt; + + while freebds > 0 { + freebds -= 1; + + let max_frame_size = if (instance_p.lwipport.feature & FXMAC_LWIP_PORT_CONFIG_JUMBO) != 0 { + FXMAC_MAX_FRAME_SIZE_JUMBO + } else { + FXMAC_MAX_FRAME_SIZE + }; + let alloc_rx_buffer_pages: usize = (max_frame_size as usize).div_ceil(PAGE_SIZE); + + status = FXmacBdRingAlloc(rxring, 1, &mut rxbd); + assert!(!rxbd.is_null()); + status = FXmacBdRingToHw(rxring, 1, rxbd); + + // 继续使用前面申请过的dma pbufs, 不再清理并重新申请 + //let (mut rx_mbufs_vaddr, mut rx_mbufs_dma) = crate::utils::dma_alloc_coherent(alloc_rx_buffer_pages); + //crate::utils::FCacheDCacheInvalidateRange(rx_mbufs_vaddr as u64, max_frame_size as u64); + + let bdindex: u32 = FXMAC_BD_TO_INDEX(rxring, rxbd as u64); + + let rx_macb_dma_desc = unsafe { (rxbd as *const macb_dma_desc).read_volatile() }; + trace!("SetupRxBds - {}: {:#010x?}", bdindex, rx_macb_dma_desc); + + let mut v = rx_macb_dma_desc.addr & (!0x7f); // 128位对齐? + if bdindex == (FXMAX_RX_PBUFS_LENGTH - 1) as u32 { + // Mask last descriptor in receive buffer list + v |= FXMAC_RXBUF_WRAP_MASK; + } + + let mut temp = rxbd as *mut u32; + unsafe { + temp.add(1).write_volatile(0); // clear rx ctl + temp.write_volatile(v); // set rx addr + } + crate::utils::DSB(); + + // 设置BD的地址字段(word 0) + //fxmac_bd_set_address_rx(rxbd as u64, rx_mbufs_dma as u64); + + assert!(instance_p.lwipport.buffer.rx_pbufs_storage[bdindex as usize] != 0); + //instance_p.lwipport.buffer.rx_pbufs_storage[bdindex as usize] = rx_mbufs_vaddr as u64; + } +} + +/// FXmacBdRingFree, Frees a set of BDs that had been previously retrieved with +pub fn FXmacBdRingFree(ring_ptr: &mut FXmacBdRing, num_bd: u32) -> u32 { + // if no bds to process, simply return. + if 0 == num_bd { + 0 + } else { + /* Update pointers and counters */ + ring_ptr.free_cnt += num_bd; + ring_ptr.post_cnt -= num_bd; + + let mut post_head_t = ring_ptr.post_head; + FXMAC_RING_SEEKAHEAD(ring_ptr, &mut post_head_t, num_bd); + ring_ptr.post_head = post_head_t; + + 0 + } +} + +/// Reset Tx and Rx DMA pointers after FXmacStop +pub fn ResetDma(instance_p: &mut FXmac) { + info!("Resetting DMA"); + + let txringptr: &mut FXmacBdRing = &mut instance_p.tx_bd_queue.bdring; + let rxringptr: &mut FXmacBdRing = &mut instance_p.rx_bd_queue.bdring; + + FXmacBdringPtrReset( + txringptr, + instance_p.lwipport.buffer.tx_bdspace as *mut FXmacBd, + ); + FXmacBdringPtrReset( + rxringptr, + instance_p.lwipport.buffer.rx_bdspace as *mut FXmacBd, + ); + + FXmacSetQueuePtr(instance_p.tx_bd_queue.bdring.phys_base_addr, 0, FXMAC_SEND); + FXmacSetQueuePtr(instance_p.rx_bd_queue.bdring.phys_base_addr, 0, FXMAC_RECV); +} + +/// Handle DMA interrupt error +pub fn FXmacHandleDmaTxError(instance_p: &mut FXmac) { + panic!("Failed to handle DMA interrupt error"); + /* + FreeTxRxPbufs(instance_p); + FXmacCfgInitialize(&instance_p->instance, &instance_p->instance.config); // -> FXmacReset() + + /* initialize the mac */ + FXmacInitOnError(instance_p); /* need to set mac filter address */ + let mut dmacrreg: u32 = read_reg((instance_p.config.base_address + FXMAC_DMACR_OFFSET) as *const u32); + dmacrreg = dmacrreg | FXMAC_DMACR_ORCE_DISCARD_ON_ERR_MASK; /* force_discard_on_err */ + write_reg((instance_p.config.base_address + FXMAC_DMACR_OFFSET) as *mut u32, dmacrreg); + FXmacSetupIsr(instance_p); + FXmacInitDma(instance_p); + + FXmacStart(&instance_p->instance); + */ +} + +pub fn FXmacHandleTxErrors(instance_p: &mut FXmac) { + let mut netctrlreg: u32 = + read_reg((instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *const u32); + netctrlreg &= !FXMAC_NWCTRL_TXEN_MASK; + write_reg( + (instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *mut u32, + netctrlreg, + ); + FreeOnlyTxPbufs(instance_p); + + CleanDmaTxdescs(instance_p); + netctrlreg = read_reg((instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *const u32); + netctrlreg |= FXMAC_NWCTRL_TXEN_MASK; + write_reg( + (instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *mut u32, + netctrlreg, + ); +} + +fn CleanDmaTxdescs(instance_p: &mut FXmac) { + warn!("Clean DMA TX DESCs"); + let txringptr: &mut FXmacBdRing = &mut instance_p.tx_bd_queue.bdring; + + let mut bdtemplate: FXmacBd = [0; FXMAC_BD_NUM_WORDS]; + + //FXMAC_BD_SET_STATUS(&bdtemplate, FXMAC_TXBUF_USED_MASK); + fxmac_bd_write( + (&mut bdtemplate as *mut _ as u64), + FXMAC_BD_STAT_OFFSET, + fxmac_bd_read((&mut bdtemplate as *mut _ as u64), FXMAC_BD_STAT_OFFSET) + | (FXMAC_TXBUF_USED_MASK), + ); + + let tx_bdspace_ptr = instance_p.lwipport.buffer.tx_bdspace as u64; + FXmacBdRingCreate( + txringptr, + tx_bdspace_ptr, + tx_bdspace_ptr, + BD_ALIGNMENT, + FXMAX_TX_BDSPACE_LENGTH as u32, + ); + FXmacBdRingClone(txringptr, &bdtemplate, FXMAC_SEND); +} + +fn FreeOnlyTxPbufs(instance_p: &mut FXmac) { + warn!("Free all TX DMA pbuf"); + for index in 0..FXMAX_TX_PBUFS_LENGTH { + if (instance_p.lwipport.buffer.tx_pbufs_storage[index] != 0) { + let pbuf = instance_p.lwipport.buffer.tx_pbufs_storage[index]; + let pages = (FXMAC_MAX_FRAME_SIZE as usize).div_ceil(PAGE_SIZE); + crate_interface::call_interface!(crate::KernelFunc::dma_free_coherent( + pbuf as usize, + pages + )); + + instance_p.lwipport.buffer.tx_pbufs_storage[index] = 0; + } + } +} + +/// FXmacProcessSentBds, 释放发送队列q参数 +pub fn FXmacProcessSentBds(instance_p: &mut FXmac) { + let txring: &mut FXmacBdRing = &mut (instance_p.tx_bd_queue.bdring); + let mut txbdset: *mut FXmacBd = null_mut(); + loop { + /* obtain processed BD's */ + let n_bds: u32 = FXmacBdRingFromHwTx(txring, FXMAX_TX_PBUFS_LENGTH, &mut txbdset); + if n_bds == 0 { + info!("FXmacProcessSentBds have not found BD"); + return; + } + /* free the processed BD's */ + let mut n_pbufs_freed: u32 = n_bds; + let mut curbdpntr: *mut FXmacBd = txbdset; + while n_pbufs_freed > 0 { + let bdindex = FXMAC_BD_TO_INDEX(txring, curbdpntr as u64) as usize; + + trace!("FXmacProcessSentBds - {}: {:#010x?}", bdindex, unsafe { + *(curbdpntr as *const macb_dma_desc) + }); + let mut v = 0; + if bdindex == (FXMAX_TX_PBUFS_LENGTH - 1) { + v = 0xC0000000; /* Word 1 ,used/Wrap – marks last descriptor in transmit buffer descriptor list.*/ + } else { + v = 0x80000000; /* Word 1 , Used – must be zero for GEM to read data to the transmit buffer.*/ + } + + let mut temp = curbdpntr as *mut u32; + // Word 0 + unsafe { + //temp.write_volatile(0); // 这里不再对dma buffer地址清0 + temp.add(1).write_volatile(v); // 设置dma desc的ctrl + } + crate::utils::DSB(); + + //let pbuf = instance_p.lwipport.buffer.tx_pbufs_storage[bdindex]; + /* if pbuf != 0 { + // pbuf_free(p); + let pages = (FXMAC_MAX_FRAME_SIZE as usize + (PAGE_SIZE - 1)) / PAGE_SIZE; + // Deallocate DMA memory by virtual address + crate::utils::dma_free_coherent(pbuf as usize, pages); + } */ + + //instance_p.lwipport.buffer.tx_pbufs_storage[bdindex] = 0; + + let b = curbdpntr; + curbdpntr = FXMAC_BD_RING_NEXT(txring, curbdpntr); + assert!(curbdpntr as usize != b as usize); + + n_pbufs_freed -= 1; + crate::utils::DSB(); + } + + FXmacBdRingFree(txring, n_bds); + } +} + +pub fn FXmacSendHandler(instance: &mut FXmac) { + debug!("-> FXmacSendHandler"); + //let txringptr: FXmacBdRing = instance.tx_bd_queue.bdring; + let regval: u32 = read_reg((instance.config.base_address + FXMAC_TXSR_OFFSET) as *const u32); + + // 清除中断状态位来停止中断 + write_reg( + (instance.config.base_address + FXMAC_TXSR_OFFSET) as *mut u32, + regval, + ); + + // If Transmit done interrupt is asserted, process completed BD's + // 释放发送队列q参数 + FXmacProcessSentBds(instance); +} + +pub fn FXmacLinkChange(instance: &mut FXmac) { + debug!("-> FXmacLinkChange"); + + if instance.config.interface == FXmacPhyInterface::FXMAC_PHY_INTERFACE_MODE_SGMII { + let mut link: u32 = 0; + let mut link_status: u32 = 0; + + let ctrl: u32 = + read_reg((instance.config.base_address + FXMAC_PCS_AN_LP_OFFSET) as *const u32); + let link: u32 = (ctrl & FXMAC_PCS_LINK_PARTNER_NEXT_PAGE_STATUS) + >> FXMAC_PCS_LINK_PARTNER_NEXT_PAGE_OFFSET; + + match link { + 0 => { + info!("link status is down"); + link_status = FXMAC_LINKDOWN; + } + 1 => { + info!("link status is up"); + link_status = FXMAC_LINKUP; + } + _ => { + error!("link status is error {:#x}", link); + } + } + + if link_status == FXMAC_LINKUP { + if link_status != instance.link_status { + instance.link_status = FXMAC_NEGOTIATING; + info!("need NEGOTIATING"); + } + } else { + instance.link_status = FXMAC_LINKDOWN; + } + } +} + +/* phy */ + +/** + * @name: phy_link_detect + * @msg: 获取当前link status + * @note: + * @param {FXmac} *fxmac_p + * @param {u32} phy_addr + * @return {*} 1 is link up , 0 is link down + */ +pub fn phy_link_detect(xmac_p: &mut FXmac, phy_addr: u32) -> u32 { + let mut status: u16 = 0; + + // Read Phy Status register twice to get the confirmation of the current link status. + let mut ret: u32 = FXmacPhyRead(xmac_p, phy_addr, PHY_STATUS_REG_OFFSET, &mut status); + + if status & PHY_STAT_LINK_STATUS != 0 { + return 1; + } + + 0 +} + +pub fn phy_autoneg_status(xmac_p: &mut FXmac, phy_addr: u32) -> u32 { + let mut status: u16 = 0; + + // Read Phy Status register twice to get the confirmation of the current link status. + FXmacPhyRead(xmac_p, phy_addr, PHY_STATUS_REG_OFFSET, &mut status); + + if status & PHY_STATUS_AUTONEGOTIATE_COMPLETE != 0 { + return 1; + } + + 0 +} + +/// Transmits packets through the lwIP-compatible interface. +/// +/// This is the high-level transmission function that should be called by +/// network stack implementations. It handles buffer availability checking +/// and automatically processes completed transmissions. +/// +/// # Arguments +/// +/// * `instance` - Mutable reference to the FXMAC instance. +/// * `pbuf` - Vector of packets to transmit. +/// +/// # Returns +/// +/// * Positive value: Number of bytes successfully queued for transmission. +/// * `-3`: No buffer space available; packet was dropped. +/// +/// # Example +/// +/// ```ignore +/// let mut packets = Vec::new(); +/// packets.push(frame_data.to_vec()); +/// +/// match FXmacLwipPortTx(fxmac, packets) { +/// n if n > 0 => println!("Sent {} bytes", n), +/// -3 => println!("TX buffer full, packet dropped"), +/// _ => println!("Unknown error"), +/// } +/// ``` +pub fn FXmacLwipPortTx(instance: &mut FXmac, pbuf: Vec>) -> i32 { + info!("TX transmit packets"); + // 发送网络包时注意屏蔽下中断 + + // check if space is available to send + let freecnt = (instance.tx_bd_queue.bdring).free_cnt; + if freecnt <= 4 { + //5 + info!("TX freecnt={}, let's process sent BDs", freecnt); + FXmacProcessSentBds(instance); + } + + if (instance.tx_bd_queue.bdring).free_cnt != 0 { + FXmacSgsend(instance, pbuf) as i32 + } else { + error!(" TX packets dropped, no space"); + -3 // FREERTOS_XMAC_NO_VALID_SPACE + } +} + +pub fn ethernetif_input_to_recv_packets(instance_p: &mut FXmac) { + if (instance_p.lwipport.recv_flg > 0) { + info!( + "ethernetif_input_to_recv_packets, fxmac_port->recv_flg={}", + instance_p.lwipport.recv_flg + ); + + // 也许需要屏蔽中断的临界区来保护 + instance_p.lwipport.recv_flg -= 1; + + // 开中断 + write_reg( + (instance_p.config.base_address + FXMAC_IER_OFFSET) as *mut u32, + instance_p.mask, + ); + + // 若需要中断处理函数中来接收包,可以这里解注释 + //FXmacRecvHandler(instance_p); + } + + { + // move received packet into a new pbuf + //p = low_level_input(netif); + // IP or ARP packet + // full packet send to tcpip thread to process + } +} diff --git a/components/fxmac_rs/src/fxmac_intr.rs b/components/fxmac_rs/src/fxmac_intr.rs new file mode 100644 index 000000000..91ef23853 --- /dev/null +++ b/components/fxmac_rs/src/fxmac_intr.rs @@ -0,0 +1,542 @@ +//! Interrupt handling for FXMAC Ethernet controller. +//! +//! This module provides interrupt handlers and ISR setup functions for +//! handling TX/RX completion, errors, and link status changes. + +use core::sync::atomic::AtomicPtr; +use core::sync::atomic::Ordering; + +use alloc::boxed::Box; + +use crate::fxmac::*; +use crate::fxmac_const::*; +use crate::fxmac_dma::*; + +/* XMAC */ +pub const FXMAC_NUM: u32 = 4; +pub const FXMAC0_ID: u32 = 0; +pub const FXMAC1_ID: u32 = 1; +pub const FXMAC2_ID: u32 = 2; +pub const FXMAC3_ID: u32 = 3; +pub const FXMAC0_BASE_ADDR: u32 = 0x3200C000; +pub const FXMAC1_BASE_ADDR: u32 = 0x3200E000; +pub const FXMAC2_BASE_ADDR: u32 = 0x32010000; +pub const FXMAC3_BASE_ADDR: u32 = 0x32012000; +pub const FXMAC0_MODE_SEL_BASE_ADDR: u32 = 0x3200DC00; +pub const FXMAC0_LOOPBACK_SEL_BASE_ADDR: u32 = 0x3200DC04; +pub const FXMAC1_MODE_SEL_BASE_ADDR: u32 = 0x3200FC00; +pub const FXMAC1_LOOPBACK_SEL_BASE_ADDR: u32 = 0x3200FC04; +pub const FXMAC2_MODE_SEL_BASE_ADDR: u32 = 0x32011C00; +pub const FXMAC2_LOOPBACK_SEL_BASE_ADDR: u32 = 0x32011C04; +pub const FXMAC3_MODE_SEL_BASE_ADDR: u32 = 0x32013C00; +pub const FXMAC3_LOOPBACK_SEL_BASE_ADDR: u32 = 0x32013C04; +pub const FXMAC0_PCLK: u32 = 50000000; +pub const FXMAC1_PCLK: u32 = 50000000; +pub const FXMAC2_PCLK: u32 = 50000000; +pub const FXMAC3_PCLK: u32 = 50000000; +pub const FXMAC0_HOTPLUG_IRQ_NUM: u32 = (53 + 30); +pub const FXMAC1_HOTPLUG_IRQ_NUM: u32 = (54 + 30); +pub const FXMAC2_HOTPLUG_IRQ_NUM: u32 = (55 + 30); +pub const FXMAC3_HOTPLUG_IRQ_NUM: u32 = (56 + 30); +pub const FXMAC_QUEUE_MAX_NUM: u32 = 16; // #define FXMAC_QUEUE_MAX_NUM 16U +pub const FXMAC0_QUEUE0_IRQ_NUM: u32 = (57 + 30); +pub const FXMAC0_QUEUE1_IRQ_NUM: u32 = (58 + 30); +pub const FXMAC0_QUEUE2_IRQ_NUM: u32 = (59 + 30); +pub const FXMAC0_QUEUE3_IRQ_NUM: u32 = (60 + 30); +pub const FXMAC0_QUEUE4_IRQ_NUM: u32 = (30 + 30); +pub const FXMAC0_QUEUE5_IRQ_NUM: u32 = (31 + 30); +pub const FXMAC0_QUEUE6_IRQ_NUM: u32 = (32 + 30); +pub const FXMAC0_QUEUE7_IRQ_NUM: u32 = (33 + 30); +pub const FXMAC1_QUEUE0_IRQ_NUM: u32 = (61 + 30); +pub const FXMAC1_QUEUE1_IRQ_NUM: u32 = (62 + 30); +pub const FXMAC1_QUEUE2_IRQ_NUM: u32 = (63 + 30); +pub const FXMAC1_QUEUE3_IRQ_NUM: u32 = (64 + 30); +pub const FXMAC2_QUEUE0_IRQ_NUM: u32 = (66 + 30); +pub const FXMAC2_QUEUE1_IRQ_NUM: u32 = (67 + 30); +pub const FXMAC2_QUEUE2_IRQ_NUM: u32 = (68 + 30); +pub const FXMAC2_QUEUE3_IRQ_NUM: u32 = (69 + 30); +pub const FXMAC3_QUEUE0_IRQ_NUM: u32 = (70 + 30); +pub const FXMAC3_QUEUE1_IRQ_NUM: u32 = (71 + 30); +pub const FXMAC3_QUEUE2_IRQ_NUM: u32 = (72 + 30); +pub const FXMAC3_QUEUE3_IRQ_NUM: u32 = (73 + 30); +//pub const FXMAC_PHY_MAX_NUM:u32 = 32; +// #define FXMAC_CLK_TYPE_0 + +/// Global pointer to the active FXMAC instance. +/// +/// This atomic pointer is set during initialization and used by the interrupt +/// handler to access the controller instance. +pub static XMAC: AtomicPtr = AtomicPtr::new(core::ptr::null_mut()); + +/// Top-level interrupt handler for FXMAC. +/// +/// This function should be registered as the interrupt handler for the FXMAC +/// controller. It retrieves the global FXMAC instance and dispatches to the +/// appropriate sub-handlers. +/// +/// # Safety +/// +/// This function accesses the global `XMAC` pointer. It assumes that the +/// pointer has been properly initialized by [`xmac_init`]. +pub fn xmac_intr_handler() { + debug!("Handling xmac intr ..."); + + let xmac = XMAC.load(Ordering::Relaxed); + if !xmac.is_null() { + let xmac_ptr = unsafe { &mut (*xmac) }; + + // maybe irq num + let vector = xmac_ptr.config.queue_irq_num[0]; + FXmacIntrHandler(vector as i32, xmac_ptr); + + info!("xmac intr is already handled"); + } else { + error!("static FXmac has not been initialized"); + } +} + +/// Main interrupt handler for FXMAC controller. +/// +/// Processes all pending interrupts for the specified FXMAC instance. This +/// handler supports the following interrupt types: +/// +/// - **FXMAC_HANDLER_DMARECV**: RX completion - calls `FXmacRecvIsrHandler` +/// - **FXMAC_HANDLER_DMASEND**: TX completion - calls `FXmacSendHandler` +/// - **FXMAC_HANDLER_ERROR**: Error conditions - calls `FXmacErrorHandler` +/// - **FXMAC_HANDLER_LINKCHANGE**: Link status change - calls `FXmacLinkChange` +/// +/// # Arguments +/// +/// * `vector` - The IRQ vector number that triggered the interrupt. +/// * `instance_p` - Mutable reference to the FXMAC instance. +/// +/// # Note +/// +/// Currently only single-queue operation is fully supported. +pub fn FXmacIntrHandler(vector: i32, instance_p: &mut FXmac) { + assert!(instance_p.is_ready == FT_COMPONENT_IS_READY); + + // 0 ~ FXMAC_QUEUE_MAX_NUM ,Index queue number + let tx_queue_id = instance_p.tx_bd_queue.queue_id; + // 0 ~ FXMAC_QUEUE_MAX_NUM ,Index queue number + let rx_queue_id = instance_p.rx_bd_queue.queue_id; + + assert!((rx_queue_id < FXMAC_QUEUE_MAX_NUM) && (tx_queue_id < FXMAC_QUEUE_MAX_NUM)); + + /* This ISR will try to handle as many interrupts as it can in a single + * call. However, in most of the places where the user's error handler + * is called, this ISR exits because it is expected that the user will + * reset the device in nearly all instances. + */ + let mut reg_isr: u32 = + read_reg((instance_p.config.base_address + FXMAC_ISR_OFFSET) as *const u32); + + info!( + "+++++++++ IRQ num vector={}, Interrupt Status ISR={:#x}, tx_queue_id={}, rx_queue_id={}", + vector, reg_isr, tx_queue_id, rx_queue_id + ); + + if vector as u32 == instance_p.config.queue_irq_num[tx_queue_id as usize] { + if tx_queue_id == 0 { + if (reg_isr & FXMAC_IXR_TXCOMPL_MASK) != 0 { + // Clear TX status register TX complete indication but preserve error bits if there is any + write_reg( + (instance_p.config.base_address + FXMAC_TXSR_OFFSET) as *mut u32, + FXMAC_TXSR_TXCOMPL_MASK | FXMAC_TXSR_USEDREAD_MASK, + ); + + FXmacSendHandler(instance_p); + + /* add */ + if (instance_p.caps & FXMAC_CAPS_ISR_CLEAR_ON_WRITE) != 0 { + write_reg( + (instance_p.config.base_address + FXMAC_ISR_OFFSET) as *mut u32, + FXMAC_IXR_TXCOMPL_MASK, + ); + } + } + + /* Transmit error conditions interrupt */ + if ((reg_isr & FXMAC_IXR_TX_ERR_MASK) != 0) && ((reg_isr & FXMAC_IXR_TXCOMPL_MASK) == 0) + { + /* Clear TX status register */ + let reg_txsr: u32 = + read_reg((instance_p.config.base_address + FXMAC_TXSR_OFFSET) as *const u32); + + write_reg( + (instance_p.config.base_address + FXMAC_TXSR_OFFSET) as *mut u32, + reg_txsr, + ); + + FXmacErrorHandler(instance_p, FXMAC_SEND as u8, reg_txsr); + + /* add */ + if (instance_p.caps & FXMAC_CAPS_ISR_CLEAR_ON_WRITE) != 0 { + write_reg( + (instance_p.config.base_address + FXMAC_ISR_OFFSET) as *mut u32, + FXMAC_IXR_TX_ERR_MASK, + ); + } + } + + /* add restart */ + if (reg_isr & FXMAC_IXR_TXUSED_MASK) != 0 { + /* add */ + if (instance_p.caps & FXMAC_CAPS_ISR_CLEAR_ON_WRITE) != 0 { + write_reg( + (instance_p.config.base_address + FXMAC_ISR_OFFSET) as *mut u32, + FXMAC_IXR_TXUSED_MASK, + ); + } + + /* + if (instance_p->restart_handler) + { + instance_p->restart_handler(instance_p->restart_args); + } + */ + } + + /* link changed */ + if (reg_isr & FXMAC_IXR_LINKCHANGE_MASK) != 0 { + FXmacLinkChange(instance_p); + + if (instance_p.caps & FXMAC_CAPS_ISR_CLEAR_ON_WRITE) != 0 { + write_reg( + (instance_p.config.base_address + FXMAC_ISR_OFFSET) as *mut u32, + FXMAC_IXR_LINKCHANGE_MASK, + ); + } + } + } else + /* use queue number more than 0 */ + { + reg_isr = read_reg( + (instance_p.config.base_address + + FXMAC_QUEUE_REGISTER_OFFSET(FXMAC_INTQ1_STS_OFFSET, tx_queue_id)) + as *const u32, + ); + + /* Transmit Q1 complete interrupt */ + if ((reg_isr & FXMAC_INTQUESR_TXCOMPL_MASK) != 0) { + /* Clear TX status register TX complete indication but preserve + * error bits if there is any */ + write_reg( + (instance_p.config.base_address + + FXMAC_QUEUE_REGISTER_OFFSET(FXMAC_INTQ1_STS_OFFSET, tx_queue_id)) + as *mut u32, + FXMAC_INTQUESR_TXCOMPL_MASK, + ); + write_reg( + (instance_p.config.base_address + FXMAC_TXSR_OFFSET) as *mut u32, + FXMAC_TXSR_TXCOMPL_MASK | FXMAC_TXSR_USEDREAD_MASK, + ); + + FXmacSendHandler(instance_p); + } + + /* Transmit Q1 error conditions interrupt */ + if (((reg_isr & FXMAC_INTQ1SR_TXERR_MASK) != 0) + && ((reg_isr & FXMAC_INTQ1SR_TXCOMPL_MASK) != 0)) + { + /* Clear Interrupt Q1 status register */ + write_reg( + (instance_p.config.base_address + + FXMAC_QUEUE_REGISTER_OFFSET(FXMAC_INTQ1_STS_OFFSET, tx_queue_id)) + as *mut u32, + reg_isr, + ); + + FXmacErrorHandler(instance_p, FXMAC_SEND as u8, reg_isr); + } + } + } + + if vector as u32 == instance_p.config.queue_irq_num[rx_queue_id as usize] { + if rx_queue_id == 0 { + /* Receive complete interrupt */ + if (reg_isr & FXMAC_IXR_RXCOMPL_MASK) != 0 { + /* Clear RX status register RX complete indication but preserve + * error bits if there is any */ + write_reg( + (instance_p.config.base_address + FXMAC_RXSR_OFFSET) as *mut u32, + FXMAC_RXSR_FRAMERX_MASK | FXMAC_RXSR_BUFFNA_MASK, + ); + FXmacRecvIsrHandler(instance_p); + + /* add */ + if (instance_p.caps & FXMAC_CAPS_ISR_CLEAR_ON_WRITE) != 0 { + write_reg( + (instance_p.config.base_address + FXMAC_ISR_OFFSET) as *mut u32, + FXMAC_IXR_RXCOMPL_MASK, + ); + } + } + + /* Receive error conditions interrupt */ + if (reg_isr & FXMAC_IXR_RX_ERR_MASK) != 0 { + /* Clear RX status register */ + let mut reg_rxsr: u32 = + read_reg((instance_p.config.base_address + FXMAC_RXSR_OFFSET) as *const u32); + write_reg( + (instance_p.config.base_address + FXMAC_RXSR_OFFSET) as *mut u32, + reg_rxsr, + ); + + if (reg_isr & FXMAC_IXR_RXUSED_MASK) != 0 { + let reg_ctrl: u32 = read_reg( + (instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *const u32, + ); + + let mut reg_temp: u32 = reg_ctrl | FXMAC_NWCTRL_FLUSH_DPRAM_MASK; + reg_temp &= !FXMAC_NWCTRL_RXEN_MASK; + write_reg( + (instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *mut u32, + reg_temp, + ); + + /* add */ + reg_temp = reg_ctrl | FXMAC_NWCTRL_RXEN_MASK; + write_reg( + (instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *mut u32, + reg_temp, + ); + + if (instance_p.caps & FXMAC_CAPS_ISR_CLEAR_ON_WRITE) != 0 { + write_reg( + (instance_p.config.base_address + FXMAC_ISR_OFFSET) as *mut u32, + FXMAC_IXR_RXUSED_MASK, + ); + } + } + + /* add */ + if ((reg_isr & FXMAC_IXR_RXOVR_MASK) != 0) + && ((instance_p.caps & FXMAC_CAPS_ISR_CLEAR_ON_WRITE) != 0) + { + write_reg( + (instance_p.config.base_address + FXMAC_ISR_OFFSET) as *mut u32, + FXMAC_IXR_RXOVR_MASK, + ); + } + + /* add */ + if ((reg_isr & FXMAC_IXR_HRESPNOK_MASK) != 0) + && ((instance_p.caps & FXMAC_CAPS_ISR_CLEAR_ON_WRITE) != 0) + { + write_reg( + (instance_p.config.base_address + FXMAC_ISR_OFFSET) as *mut u32, + FXMAC_IXR_HRESPNOK_MASK, + ); + } + + if reg_rxsr != 0 { + FXmacErrorHandler(instance_p, FXMAC_RECV as u8, reg_rxsr); + } + } + } else { + /* use queue number more than 0 */ + reg_isr = read_reg( + (instance_p.config.base_address + + FXMAC_QUEUE_REGISTER_OFFSET(FXMAC_INTQ1_STS_OFFSET, rx_queue_id)) + as *const u32, + ); + + /* Receive complete interrupt */ + if ((reg_isr & FXMAC_INTQUESR_RCOMP_MASK) != 0) { + /* Clear RX status register RX complete indication but preserve + * error bits if there is any */ + write_reg( + (instance_p.config.base_address + + FXMAC_QUEUE_REGISTER_OFFSET(FXMAC_INTQ1_STS_OFFSET, rx_queue_id)) + as *mut u32, + FXMAC_INTQUESR_RCOMP_MASK, + ); + FXmacRecvIsrHandler(instance_p); + } + + /* Receive error conditions interrupt */ + if (reg_isr & FXMAC_IXR_RX_ERR_MASK) != 0 { + let mut reg_ctrl: u32 = + read_reg((instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *const u32); + reg_ctrl &= !FXMAC_NWCTRL_RXEN_MASK; + + write_reg( + (instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *mut u32, + reg_ctrl, + ); + + /* Clear RX status register */ + let mut reg_rxsr = + read_reg((instance_p.config.base_address + FXMAC_RXSR_OFFSET) as *const u32); + write_reg( + (instance_p.config.base_address + FXMAC_RXSR_OFFSET) as *mut u32, + reg_rxsr, + ); + + if ((reg_isr & FXMAC_IXR_RXUSED_MASK) != 0) { + reg_ctrl = read_reg( + (instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *const u32, + ); + reg_ctrl |= FXMAC_NWCTRL_FLUSH_DPRAM_MASK; + + write_reg( + (instance_p.config.base_address + FXMAC_NWCTRL_OFFSET) as *mut u32, + reg_ctrl, + ); + } + + /* Clear RX status register RX complete indication but preserve + * error bits if there is any */ + write_reg( + (instance_p.config.base_address + + FXMAC_QUEUE_REGISTER_OFFSET(FXMAC_INTQ1_STS_OFFSET, rx_queue_id)) + as *mut u32, + FXMAC_INTQUESR_RXUBR_MASK, + ); + FXmacRecvIsrHandler(instance_p); + + if reg_rxsr != 0 { + FXmacErrorHandler(instance_p, FXMAC_RECV as u8, reg_rxsr); + } + } + } + } +} + +/** + * @name: FXmacQueueIrqDisable + * @msg: Disable queue irq + * @param {FXmac} *instance_p a pointer to the instance to be worked on. + * @param {u32} queue_num queue number + * @param {u32} mask is interrupt disable value mask + */ +pub fn FXmacQueueIrqDisable(instance_p: &mut FXmac, queue_num: u32, mask: u32) { + assert!(instance_p.is_ready == FT_COMPONENT_IS_READY); + assert!(instance_p.config.max_queue_num > queue_num); + + if queue_num == 0 { + write_reg( + (instance_p.config.base_address + FXMAC_IDR_OFFSET) as *mut u32, + mask & FXMAC_IXR_ALL_MASK, + ); + } else { + write_reg( + (instance_p.config.base_address + FXMAC_INTQX_IDR_SIZE_OFFSET(queue_num as u64)) + as *mut u32, + mask & FXMAC_IXR_ALL_MASK, + ); + } +} + +/// FXmacQueueIrqEnable, Enable queue irq +pub fn FXmacQueueIrqEnable(instance_p: &mut FXmac, queue_num: u32, mask: u32) { + assert!(instance_p.is_ready == FT_COMPONENT_IS_READY); + assert!(instance_p.config.max_queue_num > queue_num); + + if queue_num == 0 { + write_reg( + (instance_p.config.base_address + FXMAC_IER_OFFSET) as *mut u32, + mask & FXMAC_IXR_ALL_MASK, + ); + } else { + write_reg( + (instance_p.config.base_address + FXMAC_INTQX_IER_SIZE_OFFSET(queue_num as u64)) + as *mut u32, + mask & FXMAC_IXR_ALL_MASK, + ); + } +} + +pub fn FXmacErrorHandler(instance_p: &mut FXmac, direction: u8, error_word: u32) { + debug!( + "-> FXmacErrorHandler, direction={}, error_word={}", + direction, error_word + ); + if error_word != 0 { + match direction as u32 { + FXMAC_RECV => { + if (error_word & FXMAC_RXSR_HRESPNOK_MASK) != 0 { + error!("Receive DMA error"); + FXmacHandleDmaTxError(instance_p); + } + if (error_word & FXMAC_RXSR_RXOVR_MASK) != 0 { + error!("Receive over run"); + //FXmacRecvHandler(instance_p); + } + if (error_word & FXMAC_RXSR_BUFFNA_MASK) != 0 { + error!("Receive buffer not available"); + //FXmacRecvHandler(instance_p); + } + } + FXMAC_SEND => { + if (error_word & FXMAC_TXSR_HRESPNOK_MASK) != 0 { + error!("Transmit DMA error"); + FXmacHandleDmaTxError(instance_p); + } + if (error_word & FXMAC_TXSR_URUN_MASK) != 0 { + error!("Transmit under run"); + FXmacHandleTxErrors(instance_p); + } + if (error_word & FXMAC_TXSR_BUFEXH_MASK) != 0 { + error!("Transmit buffer exhausted"); + FXmacHandleTxErrors(instance_p); + } + if (error_word & FXMAC_TXSR_RXOVR_MASK) != 0 { + error!("Transmit retry excessed limits"); + FXmacHandleTxErrors(instance_p); + } + if (error_word & FXMAC_TXSR_FRAMERX_MASK) != 0 { + error!("Transmit collision"); + FXmacProcessSentBds(instance_p); + } + } + _ => { + error!("FXmacErrorHandler failed, unknown direction={}", direction); + } + } + } +} + +pub fn FXmacRecvIsrHandler(instance: &mut FXmac) { + debug!("-> FXmacRecvIsrHandler"); + // 关中断 + write_reg( + (instance.config.base_address + FXMAC_IDR_OFFSET) as *mut u32, + FXMAC_IXR_RXCOMPL_MASK, + ); + instance.lwipport.recv_flg += 1; + + ethernetif_input_to_recv_packets(instance); + // 处理后会开中断 +} + +/// 网卡中断设置 +pub fn FXmacSetupIsr(instance: &mut FXmac) { + // 获取当前CPU ID: 0, 1, 2, 3, 4, 5, 6, 7 + //let cpu_id: u32 = get_cpu_id(); + // 路由中断到指定的cpu,或所有的cpu + + // Setup callbacks, 为指定类型设置回调函数 + /* + FXmacSetHandler(&instance_p->instance, FXMAC_HANDLER_DMASEND, FXmacSendHandler, instance_p); + FXmacSetHandler(&instance_p->instance, FXMAC_HANDLER_DMARECV, FXmacRecvIsrHandler, instance_p); + FXmacSetHandler(&instance_p->instance, FXMAC_HANDLER_ERROR, FXmacErrorHandler, instance_p); + FXmacSetHandler(&instance_p->instance, FXMAC_HANDLER_LINKCHANGE, FXmacLinkChange, instance_p); + */ + + //let IRQ_PRIORITY_VALUE_0 = 0x0; + //let IRQ_PRIORITY_VALUE_12 = 0xc; + // 设置中断优先级为IRQ_PRIORITY_VALUE_12 + + // setup interrupt handler, 该函数将自定义中断回调函数注册到对应的中断ID + // 使能对应中断 + let irq_num = instance.config.queue_irq_num[0] as usize; // 32 + 55 + + // SPI(Shared Peripheral Interrupt) rang: 32..1020 + info!("register callback function for irq: {}", irq_num); + crate_interface::call_interface!(crate::KernelFunc::dma_request_irq( + irq_num, + xmac_intr_handler + )); +} diff --git a/components/fxmac_rs/src/fxmac_phy.rs b/components/fxmac_rs/src/fxmac_phy.rs new file mode 100644 index 000000000..c95bfc0db --- /dev/null +++ b/components/fxmac_rs/src/fxmac_phy.rs @@ -0,0 +1,535 @@ +//! PHY management for FXMAC Ethernet controller. +//! +//! This module provides functions for PHY initialization, configuration, +//! and management through the MDIO interface. + +use crate::fxmac::*; +use crate::fxmac_const::*; + +/// PHY Control Register offset (MII register 0). +pub const PHY_CONTROL_REG_OFFSET: u32 = 0; +pub const PHY_STATUS_REG_OFFSET: u32 = 1; +pub const PHY_IDENTIFIER_1_REG: u32 = 2; +pub const PHY_IDENTIFIER_2_REG: u32 = 3; +pub const PHY_AUTONEGO_ADVERTISE_REG: u32 = 4; +pub const PHY_PARTNER_ABILITIES_1_REG_OFFSET: u32 = 5; +pub const PHY_PARTNER_ABILITIES_2_REG_OFFSET: u32 = 8; +pub const PHY_PARTNER_ABILITIES_3_REG_OFFSET: u32 = 10; +pub const PHY_1000_ADVERTISE_REG_OFFSET: u32 = 9; +pub const PHY_MMD_ACCESS_CONTROL_REG: u32 = 13; +pub const PHY_MMD_ACCESS_ADDRESS_DATA_REG: u32 = 14; +pub const PHY_SPECIFIC_STATUS_REG: u32 = 17; +pub const PHY_CONTROL_FULL_DUPLEX_MASK: u16 = 0x0100; +pub const PHY_CONTROL_LINKSPEED_MASK: u16 = 0x0040; +pub const PHY_CONTROL_LINKSPEED_1000M: u16 = 0x0040; +pub const PHY_CONTROL_LINKSPEED_100M: u16 = 0x2000; +pub const PHY_CONTROL_LINKSPEED_10M: u16 = 0x0000; +pub const PHY_CONTROL_RESET_MASK: u16 = 0x8000; +pub const PHY_CONTROL_LOOPBACK_MASK: u16 = 0x4000; +pub const PHY_CONTROL_AUTONEGOTIATE_ENABLE: u16 = 0x1000; +pub const PHY_CONTROL_AUTONEGOTIATE_RESTART: u16 = 0x0200; +pub const PHY_STATUS_AUTONEGOTIATE_COMPLETE: u16 = 0x0020; +pub const PHY_STAT_LINK_STATUS: u16 = 0x0004; +pub const PHY_AUTOADVERTISE_ASYMMETRIC_PAUSE_MASK: u16 = 0x0800; +pub const PHY_AUTOADVERTISE_PAUSE_MASK: u16 = 0x0400; +pub const PHY_AUTOADVERTISE_AUTONEG_ERROR_MASK: u16 = 0x8000; + +/* Advertisement control register. */ +pub const PHY_AUTOADVERTISE_10HALF: u16 = 0x0020; /* Try for 10mbps half-duplex */ +pub const PHY_AUTOADVERTISE_1000XFULL: u16 = 0x0020; /* Try for 1000BASE-X full-duplex */ +pub const PHY_AUTOADVERTISE_10FULL: u16 = 0x0040; /* Try for 10mbps full-duplex */ +pub const PHY_AUTOADVERTISE_1000XHALF: u16 = 0x0040; /* Try for 1000BASE-X half-duplex */ +pub const PHY_AUTOADVERTISE_100HALF: u16 = 0x0080; /* Try for 100mbps half-duplex */ +pub const PHY_AUTOADVERTISE_1000XPAUSE: u16 = 0x0080; /* Try for 1000BASE-X pause */ +pub const PHY_AUTOADVERTISE_100FULL: u16 = 0x0100; /* Try for 100mbps full-duplex */ +pub const PHY_AUTOADVERTISE_1000XPSE_ASYM: u16 = 0x0100; /* Try for 1000BASE-X asym pause */ +pub const PHY_AUTOADVERTISE_100BASE4: u16 = 0x0200; /* Try for 100mbps 4k packets */ +pub const PHY_AUTOADVERTISE_100_AND_10: u16 = (PHY_AUTOADVERTISE_10FULL + | PHY_AUTOADVERTISE_100FULL + | PHY_AUTOADVERTISE_10HALF + | PHY_AUTOADVERTISE_100HALF); +pub const PHY_AUTOADVERTISE_100: u16 = (PHY_AUTOADVERTISE_100FULL | PHY_AUTOADVERTISE_100HALF); +pub const PHY_AUTOADVERTISE_10: u16 = (PHY_AUTOADVERTISE_10FULL | PHY_AUTOADVERTISE_10HALF); +pub const PHY_AUTOADVERTISE_1000: u16 = 0x0300; +pub const PHY_SPECIFIC_STATUS_SPEED_1000M: u16 = (2 << 14); +pub const PHY_SPECIFIC_STATUS_SPEED_100M: u16 = (1 << 14); +pub const PHY_SPECIFIC_STATUS_SPEED_0M: u16 = (0 << 14); + +pub const FT_SUCCESS: u32 = 0; +pub const XMAC_PHY_RESET_ENABLE: u32 = 1; +pub const XMAC_PHY_RESET_DISABLE: u32 = 0; +pub const FXMAC_PHY_AUTONEGOTIATION_DISABLE: u32 = 0; +pub const FXMAC_PHY_AUTONEGOTIATION_ENABLE: u32 = 1; +pub const FXMAC_PHY_MODE_HALFDUPLEX: u32 = 0; +pub const FXMAC_PHY_MODE_FULLDUPLEX: u32 = 1; +pub const FXMAC_PHY_MAX_NUM: u32 = 32; + +/// Writes data to a PHY register via MDIO. +/// +/// Writes a 16-bit value to the specified register of the PHY at the given +/// address. The MAC provides MDIO access to PHYs adhering to the IEEE 802.3 +/// Media Independent Interface (MII) standard. +/// +/// # Arguments +/// +/// * `instance_p` - Mutable reference to the FXMAC instance. +/// * `phy_address` - PHY address on the MDIO bus (0-31). +/// * `register_num` - PHY register number to write. +/// * `phy_data` - 16-bit data to write to the register. +/// +/// # Returns +/// +/// * `0` (FT_SUCCESS) on successful write. +/// * `6` (FXMAC_ERR_PHY_BUSY) if the MDIO bus is busy. +/// +/// # Note +/// +/// The device does not need to be stopped before PHY access, but the MDIO +/// clock should be properly configured. +pub fn FXmacPhyWrite( + instance_p: &mut FXmac, + phy_address: u32, + register_num: u32, + phy_data: u16, +) -> u32 { + let mut mgtcr: u32 = 0; + let mut ipisr: u32 = 0; + let mut ip_write_temp: u32 = 0; + let mut status: u32 = 0; + + debug!( + "FXmacPhyWrite, phy_address={:#x}, register_num={}, phy_data={:#x}", + phy_address, register_num, phy_data + ); + + /* Make sure no other PHY operation is currently in progress */ + if (read_reg((instance_p.config.base_address + FXMAC_NWSR_OFFSET) as *const u32) + & FXMAC_NWSR_MDIOIDLE_MASK) + == 0 + { + status = 6; // FXMAC_ERR_PHY_BUSY; + error!("FXmacPhyRead error: PHY busy!"); + } else { + /* Construct mgtcr mask for the operation */ + mgtcr = FXMAC_PHYMNTNC_OP_MASK + | FXMAC_PHYMNTNC_OP_W_MASK + | (phy_address << FXMAC_PHYMNTNC_PHAD_SHFT_MSK) + | (register_num << FXMAC_PHYMNTNC_PREG_SHFT_MSK) + | phy_data as u32; + + /* Write mgtcr and wait for completion */ + write_reg( + (instance_p.config.base_address + FXMAC_PHYMNTNC_OFFSET) as *mut u32, + mgtcr, + ); + + loop { + ipisr = read_reg((instance_p.config.base_address + FXMAC_NWSR_OFFSET) as *const u32); + ip_write_temp = ipisr; + + if (ip_write_temp & FXMAC_NWSR_MDIOIDLE_MASK) != 0 { + break; + } + } + + status = 0; // FT_SUCCESS; + } + + status +} + +/// Reads data from a PHY register via MDIO. +/// +/// Reads a 16-bit value from the specified register of the PHY at the given +/// address. +/// +/// # Arguments +/// +/// * `instance_p` - Mutable reference to the FXMAC instance. +/// * `phy_address` - PHY address on the MDIO bus (0-31). +/// * `register_num` - PHY register number to read. +/// * `phydat_aptr` - Mutable reference to store the read data. +/// +/// # Returns +/// +/// * `0` (FT_SUCCESS) on successful read. +/// * `6` (FXMAC_ERR_PHY_BUSY) if the MDIO bus is busy. +pub fn FXmacPhyRead( + instance_p: &mut FXmac, + phy_address: u32, + register_num: u32, + phydat_aptr: &mut u16, +) -> u32 { + let mut mgtcr: u32 = 0; + let mut ipisr: u32 = 0; + let mut IpReadTemp: u32 = 0; + let mut status: u32 = 0; + + /* Make sure no other PHY operation is currently in progress */ + if (read_reg((instance_p.config.base_address + FXMAC_NWSR_OFFSET) as *const u32) + & FXMAC_NWSR_MDIOIDLE_MASK) + == 0 + { + status = 6; + error!("FXmacPhyRead error: PHY busy!"); + } else { + /* Construct mgtcr mask for the operation */ + mgtcr = FXMAC_PHYMNTNC_OP_MASK + | FXMAC_PHYMNTNC_OP_R_MASK + | (phy_address << FXMAC_PHYMNTNC_PHAD_SHFT_MSK) + | (register_num << FXMAC_PHYMNTNC_PREG_SHFT_MSK); + + /* Write mgtcr and wait for completion */ + write_reg( + (instance_p.config.base_address + FXMAC_PHYMNTNC_OFFSET) as *mut u32, + mgtcr, + ); + + loop { + ipisr = read_reg((instance_p.config.base_address + FXMAC_NWSR_OFFSET) as *const u32); + IpReadTemp = ipisr; + + if (IpReadTemp & FXMAC_NWSR_MDIOIDLE_MASK) != 0 { + break; + } + } + + // Read data + *phydat_aptr = + read_reg((instance_p.config.base_address + FXMAC_PHYMNTNC_OFFSET) as *const u32) as u16; + + debug!( + "FXmacPhyRead, phy_address={:#x}, register_num={}, phydat_aptr={:#x}", + phy_address, register_num, phydat_aptr + ); + + status = 0; + } + + status +} + +/// Initializes the PHY for the FXMAC controller. +/// +/// This function performs PHY detection, optional reset, and speed/duplex +/// configuration. It supports both auto-negotiation and manual speed setting. +/// +/// # Arguments +/// +/// * `instance_p` - Mutable reference to the FXMAC instance. +/// * `reset_flag` - Set to `XMAC_PHY_RESET_ENABLE` to perform a PHY reset +/// before configuration, or `XMAC_PHY_RESET_DISABLE` to skip. +/// +/// # Returns +/// +/// * `0` (FT_SUCCESS) on successful initialization. +/// * `7` (FXMAC_PHY_IS_NOT_FOUND) if no PHY is detected. +/// * `8` (FXMAC_PHY_AUTO_AUTONEGOTIATION_FAILED) if auto-negotiation fails. +/// * Other error codes for PHY communication failures. +/// +/// # Example +/// +/// ```ignore +/// // Initialize PHY with reset +/// let result = FXmacPhyInit(fxmac, XMAC_PHY_RESET_ENABLE); +/// if result == 0 { +/// println!("PHY initialized, speed: {} Mbps", fxmac.config.speed); +/// } +/// ``` +pub fn FXmacPhyInit(instance_p: &mut FXmac, reset_flag: u32) -> u32 { + let speed = instance_p.config.speed; + let duplex_mode = instance_p.config.duplex; + let autonegotiation_en = instance_p.config.auto_neg; + info!( + "FXmacPhyInit, speed={}, duplex_mode={}, autonegotiation_en={}, reset_flag={}", + speed, duplex_mode, autonegotiation_en, reset_flag + ); + let mut ret: u32 = 0; + let mut phy_addr: u32 = 0; + if FXmacDetect(instance_p, &mut phy_addr) != 0 { + error!("Phy is not found."); + return 7; //FXMAC_PHY_IS_NOT_FOUND; + } + info!("Setting phy addr is {}", phy_addr); + instance_p.phy_address = phy_addr; + if reset_flag != 0 { + FXmacPhyReset(instance_p, phy_addr); + } + if autonegotiation_en != 0 { + ret = FXmacGetIeeePhySpeed(instance_p, phy_addr); + if ret != 0 { + return ret; + } + } else { + info!("Set the communication speed manually."); + assert!(speed != FXMAC_SPEED_1000, "The speed must be 100M or 10M!"); + + ret = FXmacConfigureIeeePhySpeed(instance_p, phy_addr, speed, duplex_mode); + if ret != 0 { + error!("Failed to manually set the phy."); + return ret; + } + } + instance_p.link_status = FXMAC_LINKUP; + + 0 //FT_SUCCESS +} + +pub fn FXmacDetect(instance_p: &mut FXmac, phy_addr_p: &mut u32) -> u32 { + let mut phy_addr: u32 = 0; + let mut phy_reg: u16 = 0; + let mut phy_id1_reg: u16 = 0; + let mut phy_id2_reg: u16 = 0; + + for phy_addr in 0..FXMAC_PHY_MAX_NUM { + let mut ret: u32 = FXmacPhyRead(instance_p, phy_addr, PHY_STATUS_REG_OFFSET, &mut phy_reg); + if (ret != FT_SUCCESS) { + error!("Phy operation is busy."); + return ret; + } + info!("Phy status reg is {:#x}", phy_reg); + + if (phy_reg != 0xffff) { + ret = FXmacPhyRead(instance_p, phy_addr, PHY_IDENTIFIER_1_REG, &mut phy_id1_reg); + ret |= FXmacPhyRead(instance_p, phy_addr, PHY_IDENTIFIER_2_REG, &mut phy_id2_reg); + info!( + "Phy id1 reg is {:#x}, Phy id2 reg is {:#x}", + phy_id1_reg, phy_id2_reg + ); + + if ((ret == FT_SUCCESS) + && (phy_id2_reg != 0) + && (phy_id2_reg != 0xffff) + && (phy_id1_reg != 0xffff)) + { + *phy_addr_p = phy_addr; + //phy_addr_b = phy_addr; + info!("Phy addr is {:#x}", phy_addr); + return FT_SUCCESS; + } + } + } + + 7 //FXMAC_PHY_IS_NOT_FOUND +} + +/// FXmacPhyReset: Perform phy software reset +pub fn FXmacPhyReset(instance_p: &mut FXmac, phy_addr: u32) -> u32 { + let mut control: u16 = 0; + + let mut ret: u32 = FXmacPhyRead(instance_p, phy_addr, PHY_CONTROL_REG_OFFSET, &mut control); + if (ret != FT_SUCCESS) { + error!("FXmacPhyReset, read PHY_CONTROL_REG_OFFSET is error"); + return ret; + } + + control |= PHY_CONTROL_RESET_MASK; + + ret = FXmacPhyWrite(instance_p, phy_addr, PHY_CONTROL_REG_OFFSET, control); + if (ret != FT_SUCCESS) { + error!("FXmacPhyReset, write PHY_CONTROL_REG_OFFSET is error"); + return ret; + } + + loop { + ret = FXmacPhyRead(instance_p, phy_addr, PHY_CONTROL_REG_OFFSET, &mut control); + if (ret != FT_SUCCESS) { + error!("FXmacPhyReset, read PHY_CONTROL_REG_OFFSET is error"); + return ret; + } + if (control & PHY_CONTROL_RESET_MASK) == 0 { + break; + } + } + + info!("Phy reset end."); + ret +} + +pub fn FXmacGetIeeePhySpeed(instance_p: &mut FXmac, phy_addr: u32) -> u32 { + let mut temp: u16 = 0; + let mut temp2: u16 = 0; + let mut control: u16 = 0; + let mut status: u16 = 0; + let mut negotitation_timeout_cnt: u32 = 0; + + info!("Start phy auto negotiation."); + + let mut ret: u32 = FXmacPhyRead(instance_p, phy_addr, PHY_CONTROL_REG_OFFSET, &mut control); + if (ret != FT_SUCCESS) { + error!("FXmacGetIeeePhySpeed,read PHY_CONTROL_REG_OFFSET is error"); + return ret; + } + + control |= PHY_CONTROL_AUTONEGOTIATE_ENABLE; + control |= PHY_CONTROL_AUTONEGOTIATE_RESTART; + ret = FXmacPhyWrite(instance_p, phy_addr, PHY_CONTROL_REG_OFFSET, control); + if (ret != FT_SUCCESS) { + error!("FXmacGetIeeePhySpeed,write PHY_CONTROL_REG_OFFSET is error"); + return ret; + } + + info!("Waiting for phy to complete auto negotiation."); + loop { + // 睡眠50毫秒 + crate::utils::msdelay(50); + + ret = FXmacPhyRead(instance_p, phy_addr, PHY_STATUS_REG_OFFSET, &mut status); + if (ret != FT_SUCCESS) { + error!("FXmacGetIeeePhySpeed,read PHY_STATUS_REG_OFFSET is error"); + return ret; + } + + negotitation_timeout_cnt += 1; + if (negotitation_timeout_cnt >= 0xff) { + error!("Auto negotiation is error."); + return 8; //FXMAC_PHY_AUTO_AUTONEGOTIATION_FAILED; + } + + if (status & PHY_STATUS_AUTONEGOTIATE_COMPLETE) != 0 { + break; + } + } + + info!("Auto negotiation complete."); + + ret = FXmacPhyRead(instance_p, phy_addr, PHY_SPECIFIC_STATUS_REG, &mut temp); + if (ret != FT_SUCCESS) { + error!("FXmacGetIeeePhySpeed,read PHY_SPECIFIC_STATUS_REG is error"); + return ret; + } + + info!("Temp is {:#x}", temp); + ret = FXmacPhyRead(instance_p, phy_addr, PHY_STATUS_REG_OFFSET, &mut temp2); + if (ret != FT_SUCCESS) { + error!("FXmacGetIeeePhySpeed,read PHY_STATUS_REG_OFFSET is error"); + return ret; + } + + info!("Temp2 is {:#x}", temp2); + + if (temp & (1 << 13)) != 0 { + info!("Duplex is full."); + instance_p.config.duplex = 1; + } else { + info!("Duplex is half."); + instance_p.config.duplex = 0; + } + + if (temp & 0xC000) == PHY_SPECIFIC_STATUS_SPEED_1000M { + info!("Speed is 1000M."); + instance_p.config.speed = 1000; + } else if (temp & 0xC000) == PHY_SPECIFIC_STATUS_SPEED_100M { + info!("Speed is 100M."); + instance_p.config.speed = 100; + } else { + info!("Speed is 10M."); + instance_p.config.speed = 10; + } + + FT_SUCCESS +} + +pub fn FXmacConfigureIeeePhySpeed( + instance_p: &mut FXmac, + phy_addr: u32, + speed: u32, + duplex_mode: u32, +) -> u32 { + let mut control: u16 = 0; + let mut autonereg: u16 = 0; + let mut specific_reg: u16 = 0; + + info!( + "Manual setting, phy_addr is {:#x},speed {}, duplex_mode is {}.", + phy_addr, speed, duplex_mode + ); + + let mut ret: u32 = FXmacPhyRead( + instance_p, + phy_addr, + PHY_AUTONEGO_ADVERTISE_REG, + &mut autonereg, + ); + if (ret != FT_SUCCESS) { + error!("FXmacConfigureIeeePhySpeed, read PHY_AUTONEGO_ADVERTISE_REG is error."); + return ret; + } + + autonereg |= PHY_AUTOADVERTISE_ASYMMETRIC_PAUSE_MASK; + autonereg |= PHY_AUTOADVERTISE_PAUSE_MASK; + ret = FXmacPhyWrite(instance_p, phy_addr, PHY_AUTONEGO_ADVERTISE_REG, autonereg); + if (ret != FT_SUCCESS) { + error!("FXmacConfigureIeeePhySpeed, write PHY_AUTONEGO_ADVERTISE_REG is error."); + return ret; + } + + ret = FXmacPhyRead(instance_p, phy_addr, PHY_CONTROL_REG_OFFSET, &mut control); + if (ret != FT_SUCCESS) { + error!("FXmacConfigureIeeePhySpeed, read PHY_AUTONEGO_ADVERTISE_REG is error."); + return ret; + } + info!("PHY_CONTROL_REG_OFFSET is {:#x}.", control); + + control &= !PHY_CONTROL_LINKSPEED_1000M; + control &= !PHY_CONTROL_LINKSPEED_100M; + control &= !PHY_CONTROL_LINKSPEED_10M; + + if speed == 100 { + control |= PHY_CONTROL_LINKSPEED_100M; + } else if speed == 10 { + control |= PHY_CONTROL_LINKSPEED_10M; + } + + if duplex_mode == 1 { + control |= PHY_CONTROL_FULL_DUPLEX_MASK; + } else { + control &= !PHY_CONTROL_FULL_DUPLEX_MASK; + } + + /* disable auto-negotiation */ + control &= !PHY_CONTROL_AUTONEGOTIATE_ENABLE; + control &= !PHY_CONTROL_AUTONEGOTIATE_RESTART; + + ret = FXmacPhyWrite(instance_p, phy_addr, PHY_CONTROL_REG_OFFSET, control); /* Technology Ability Field */ + if (ret != FT_SUCCESS) { + error!("FXmacConfigureIeeePhySpeed, write PHY_AUTONEGO_ADVERTISE_REG is error."); + return ret; + } + + //FDriverMdelay(1500); + crate::utils::msdelay(1500); + + info!("Manual selection completed."); + + ret = FXmacPhyRead( + instance_p, + phy_addr, + PHY_SPECIFIC_STATUS_REG, + &mut specific_reg, + ); + if (ret != FT_SUCCESS) { + error!("FXmacConfigureIeeePhySpeed, read PHY_SPECIFIC_STATUS_REG is error."); + return ret; + } + + info!("Specific reg is {:#x}", specific_reg); + + if (specific_reg & (1 << 13)) != 0 { + info!("Duplex is full."); + instance_p.config.duplex = 1; + } else { + info!("Duplex is half."); + instance_p.config.duplex = 0; + } + + if (specific_reg & 0xC000) == PHY_SPECIFIC_STATUS_SPEED_100M { + info!("Speed is 100M."); + instance_p.config.speed = 100; + } else { + info!("Speed is 10M."); + instance_p.config.speed = 10; + } + + FT_SUCCESS +} diff --git a/components/fxmac_rs/src/lib.rs b/components/fxmac_rs/src/lib.rs new file mode 100644 index 000000000..7bfad147c --- /dev/null +++ b/components/fxmac_rs/src/lib.rs @@ -0,0 +1,260 @@ +//! # FXMAC Ethernet Driver +//! +//! A `no_std` Rust driver for the FXMAC Ethernet controller found on the PhytiumPi (Phytium Pi) board. +//! This driver supports DMA-based packet transmission and reception, providing a foundation for +//! network communication in embedded and bare-metal environments. +//! +//! ## Features +//! +//! - **DMA Support**: Efficient packet transmission and reception using DMA buffer descriptors. +//! - **PHY Management**: Support for PHY initialization, auto-negotiation, and manual speed configuration. +//! - **Interrupt Handling**: Built-in interrupt handlers for TX/RX completion and error conditions. +//! - **Multiple PHY Interfaces**: Support for SGMII, RGMII, RMII, XGMII, and other interface modes. +//! - **Configurable**: Supports jumbo frames, multicast filtering, and various MAC options. +//! +//! ## Target Platform +//! +//! This driver is designed for the aarch64 architecture, specifically targeting the PhytiumPi board +//! with the Motorcomm YT8521 PHY. +//! +//! ## Quick Start +//! +//! To use this driver, you need to implement the [`KernelFunc`] trait to provide the necessary +//! kernel functions for address translation and DMA memory allocation. +//! +//! ```ignore +//! use fxmac_rs::{KernelFunc, xmac_init, FXmacLwipPortTx, FXmacRecvHandler}; +//! +//! // Implement the KernelFunc trait for your platform +//! pub struct FXmacDriver; +//! +//! #[crate_interface::impl_interface] +//! impl KernelFunc for FXmacDriver { +//! fn virt_to_phys(addr: usize) -> usize { +//! // Your implementation +//! addr +//! } +//! +//! fn phys_to_virt(addr: usize) -> usize { +//! // Your implementation +//! addr +//! } +//! +//! fn dma_alloc_coherent(pages: usize) -> (usize, usize) { +//! // Your implementation: returns (virtual_addr, physical_addr) +//! unimplemented!() +//! } +//! +//! fn dma_free_coherent(vaddr: usize, pages: usize) { +//! // Your implementation +//! } +//! +//! fn dma_request_irq(irq: usize, handler: fn()) { +//! // Your implementation +//! } +//! } +//! +//! // Initialize the driver +//! let hwaddr: [u8; 6] = [0x55, 0x44, 0x33, 0x22, 0x11, 0x00]; +//! let fxmac = xmac_init(&hwaddr); +//! +//! // Send packets +//! let mut tx_vec = Vec::new(); +//! tx_vec.push(packet_data.to_vec()); +//! FXmacLwipPortTx(fxmac, tx_vec); +//! +//! // Receive packets +//! if let Some(recv_packets) = FXmacRecvHandler(fxmac) { +//! for packet in recv_packets { +//! // Process received packet +//! } +//! } +//! ``` +//! +//! ## Module Structure +//! +//! - [`fxmac`]: Core MAC controller functionality and configuration. +//! - [`fxmac_dma`]: DMA buffer descriptor management and packet handling. +//! - [`fxmac_intr`]: Interrupt handling and callback management. +//! - [`fxmac_phy`]: PHY initialization and management functions. +//! +//! ## Safety and Environment +//! +//! - This crate targets `no_std` and assumes the platform provides DMA-coherent +//! memory and interrupt routing. +//! - Most APIs interact with memory-mapped registers and should be used with +//! care in the correct execution context. +//! +//! ## Feature Flags +//! +//! - `debug`: Enable logging via the `log` crate. Without this feature, logging +//! macros become no-ops. + +#![no_std] +#![feature(linkage)] +#![allow(unused)] +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +extern crate alloc; + +#[cfg(feature = "debug")] +#[macro_use] +extern crate log; + +#[cfg(not(feature = "debug"))] +#[macro_use] +mod log { + macro_rules! trace { + ($($arg:tt)*) => {}; + } + macro_rules! debug { + ($($arg:tt)*) => {}; + } + macro_rules! info { + ($($arg:tt)*) => {}; + } + macro_rules! warn { + ($($arg:tt)*) => {}; + } + macro_rules! error { + ($($arg:tt)*) => {}; + } +} + +//mod mii_const; +mod fxmac_const; + +mod fxmac; +mod fxmac_dma; +mod fxmac_intr; +mod fxmac_phy; +mod utils; + +// Re-exports for core MAC functionality +pub use fxmac::*; +// Re-exports for DMA operations +pub use fxmac_dma::*; +// Re-exports for interrupt handling +pub use fxmac_intr::{xmac_intr_handler, FXmacIntrHandler}; +// Re-exports for PHY interface +pub use fxmac_phy::{FXmacPhyInit, FXmacPhyRead, FXmacPhyWrite}; + +/// Kernel function interface required by the FXMAC Ethernet driver. +/// +/// This trait defines the platform-specific functions that must be implemented +/// by the host system to support the FXMAC driver. These functions handle +/// address translation, DMA memory management, and interrupt registration. +/// +/// # Implementation Requirements +/// +/// All implementations must be `#[crate_interface::impl_interface]` compatible +/// and provide thread-safe operations where applicable. +/// +/// # Example +/// +/// ```ignore +/// pub struct MyPlatform; +/// +/// #[crate_interface::impl_interface] +/// impl fxmac_rs::KernelFunc for MyPlatform { +/// fn virt_to_phys(addr: usize) -> usize { +/// // Platform-specific virtual to physical address translation +/// addr - KERNEL_OFFSET +/// } +/// +/// fn phys_to_virt(addr: usize) -> usize { +/// // Platform-specific physical to virtual address translation +/// addr + KERNEL_OFFSET +/// } +/// +/// fn dma_alloc_coherent(pages: usize) -> (usize, usize) { +/// // Allocate DMA-capable coherent memory +/// // Returns (virtual_address, physical_address) +/// allocator.alloc_dma(pages) +/// } +/// +/// fn dma_free_coherent(vaddr: usize, pages: usize) { +/// // Free previously allocated DMA memory +/// allocator.free_dma(vaddr, pages) +/// } +/// +/// fn dma_request_irq(irq: usize, handler: fn()) { +/// // Register interrupt handler for the specified IRQ +/// interrupt_controller.register(irq, handler) +/// } +/// } +/// ``` +#[crate_interface::def_interface] +pub trait KernelFunc { + /// Converts a virtual address to its corresponding physical address. + /// + /// This function is used by the driver to obtain physical addresses for + /// DMA operations, as the hardware requires physical addresses for + /// buffer descriptors. + /// + /// # Arguments + /// + /// * `addr` - The virtual address to convert. + /// + /// # Returns + /// + /// The corresponding physical address. + fn virt_to_phys(addr: usize) -> usize; + + /// Converts a physical address to its corresponding virtual address. + /// + /// This function is used by the driver to access hardware registers + /// and DMA buffers through virtual addresses. + /// + /// # Arguments + /// + /// * `addr` - The physical address to convert. + /// + /// # Returns + /// + /// The corresponding virtual address. + fn phys_to_virt(addr: usize) -> usize; + + /// Allocates DMA-coherent memory pages. + /// + /// Allocates physically contiguous memory that is suitable for DMA + /// operations. The memory should be cache-coherent or properly managed + /// for DMA access. + /// + /// # Arguments + /// + /// * `pages` - The number of pages (typically 4KB each) to allocate. + /// + /// # Returns + /// + /// A tuple containing `(virtual_address, physical_address)` of the + /// allocated memory region. + fn dma_alloc_coherent(pages: usize) -> (usize, usize); + + /// Frees previously allocated DMA-coherent memory. + /// + /// # Arguments + /// + /// * `vaddr` - The virtual address of the memory region to free. + /// * `pages` - The number of pages to free. + fn dma_free_coherent(vaddr: usize, pages: usize); + + /// Registers an interrupt handler for DMA/network interrupts. + /// + /// This function should configure the interrupt controller to route + /// the specified IRQ to the provided handler function. + /// + /// # Arguments + /// + /// * `irq` - The IRQ number to register. + /// * `handler` - The interrupt handler function to call when the IRQ fires. + fn dma_request_irq(irq: usize, handler: fn()); +} + +#[cfg(test)] +mod tests { + #[test] + fn it_works() {} +} diff --git a/components/fxmac_rs/src/mii_const.rs b/components/fxmac_rs/src/mii_const.rs new file mode 100644 index 000000000..dc99dd1c9 --- /dev/null +++ b/components/fxmac_rs/src/mii_const.rs @@ -0,0 +1,274 @@ +// linux/mii.h: definitions for MII-compatible transceivers + +/* Generic MII registers. */ +pub(crate) const MII_BMCR: u32 = 0x00; /* Basic mode control register */ +pub(crate) const MII_BMSR: u32 = 0x01; /* Basic mode status register */ +pub(crate) const MII_PHYSID1: u32 = 0x02; /* PHYS ID 1 */ +pub(crate) const MII_PHYSID2: u32 = 0x03; /* PHYS ID 2 */ +pub(crate) const MII_ADVERTISE: u32 = 0x04; /* Advertisement control reg */ +pub(crate) const MII_LPA: u32 = 0x05; /* Link partner ability reg */ +pub(crate) const MII_EXPANSION: u32 = 0x06; /* Expansion register */ +pub(crate) const MII_CTRL1000: u32 = 0x09; /* 1000BASE-T control */ +pub(crate) const MII_STAT1000: u32 = 0x0a; /* 1000BASE-T status */ +pub(crate) const MII_MMD_CTRL: u32 = 0x0d; /* MMD Access Control Register */ +pub(crate) const MII_MMD_DATA: u32 = 0x0e; /* MMD Access Data Register */ +pub(crate) const MII_ESTATUS: u32 = 0x0f; /* Extended Status */ +pub(crate) const MII_DCOUNTER: u32 = 0x12; /* Disconnect counter */ +pub(crate) const MII_FCSCOUNTER: u32 = 0x13; /* False carrier counter */ +pub(crate) const MII_NWAYTEST: u32 = 0x14; /* N-way auto-neg test reg */ +pub(crate) const MII_RERRCOUNTER: u32 = 0x15; /* Receive error counter */ +pub(crate) const MII_SREVISION: u32 = 0x16; /* Silicon revision */ +pub(crate) const MII_RESV1: u32 = 0x17; /* Reserved... */ +pub(crate) const MII_LBRERROR: u32 = 0x18; /* Lpback, rx, bypass error */ +pub(crate) const MII_PHYADDR: u32 = 0x19; /* PHY address */ +pub(crate) const MII_RESV2: u32 = 0x1a; /* Reserved... */ +pub(crate) const MII_TPISTATUS: u32 = 0x1b; /* TPI status for 10mbps */ +pub(crate) const MII_NCONFIG: u32 = 0x1c; /* Network interface config */ + +/* Basic mode control register. */ +pub(crate) const BMCR_RESV: u32 = 0x003f; /* Unused... */ +pub(crate) const BMCR_SPEED1000: u32 = 0x0040; /* MSB of Speed (1000) */ +pub(crate) const BMCR_CTST: u32 = 0x0080; /* Collision test */ +pub(crate) const BMCR_FULLDPLX: u32 = 0x0100; /* Full duplex */ +pub(crate) const BMCR_ANRESTART: u32 = 0x0200; /* Auto negotiation restart */ +pub(crate) const BMCR_ISOLATE: u32 = 0x0400; /* Isolate data paths from MII */ +pub(crate) const BMCR_PDOWN: u32 = 0x0800; /* Enable low power state */ +pub(crate) const BMCR_ANENABLE: u32 = 0x1000; /* Enable auto negotiation */ +pub(crate) const BMCR_SPEED100: u32 = 0x2000; /* Select 100Mbps */ +pub(crate) const BMCR_LOOPBACK: u32 = 0x4000; /* TXD loopback bits */ +pub(crate) const BMCR_RESET: u32 = 0x8000; /* Reset to default state */ +pub(crate) const BMCR_SPEED10: u32 = 0x0000; /* Select 10Mbps */ + +/* Basic mode status register. */ +pub(crate) const BMSR_ERCAP: u32 = 0x0001; /* Ext-reg capability */ +pub(crate) const BMSR_JCD: u32 = 0x0002; /* Jabber detected */ +pub(crate) const BMSR_LSTATUS: u32 = 0x0004; /* Link status */ +pub(crate) const BMSR_ANEGCAPABLE: u32 = 0x0008; /* Able to do auto-negotiation */ +pub(crate) const BMSR_RFAULT: u32 = 0x0010; /* Remote fault detected */ +pub(crate) const BMSR_ANEGCOMPLETE: u32 = 0x0020; /* Auto-negotiation complete */ +pub(crate) const BMSR_RESV: u32 = 0x00c0; /* Unused... */ +pub(crate) const BMSR_ESTATEN: u32 = 0x0100; /* Extended Status in R15 */ +pub(crate) const BMSR_100HALF2: u32 = 0x0200; /* Can do 100BASE-T2 HDX */ +pub(crate) const BMSR_100FULL2: u32 = 0x0400; /* Can do 100BASE-T2 FDX */ +pub(crate) const BMSR_10HALF: u32 = 0x0800; /* Can do 10mbps, half-duplex */ +pub(crate) const BMSR_10FULL: u32 = 0x1000; /* Can do 10mbps, full-duplex */ +pub(crate) const BMSR_100HALF: u32 = 0x2000; /* Can do 100mbps, half-duplex */ +pub(crate) const BMSR_100FULL: u32 = 0x4000; /* Can do 100mbps, full-duplex */ +pub(crate) const BMSR_100BASE4: u32 = 0x8000; /* Can do 100mbps, 4k packets */ + +/* Advertisement control register. */ +pub(crate) const ADVERTISE_SLCT: u32 = 0x001f; /* Selector bits */ +pub(crate) const ADVERTISE_CSMA: u32 = 0x0001; /* Only selector supported */ +pub(crate) const ADVERTISE_10HALF: u32 = 0x0020; /* Try for 10mbps half-duplex */ +pub(crate) const ADVERTISE_1000XFULL: u32 = 0x0020; /* Try for 1000BASE-X full-duplex */ +pub(crate) const ADVERTISE_10FULL: u32 = 0x0040; /* Try for 10mbps full-duplex */ +pub(crate) const ADVERTISE_1000XHALF: u32 = 0x0040; /* Try for 1000BASE-X half-duplex */ +pub(crate) const ADVERTISE_100HALF: u32 = 0x0080; /* Try for 100mbps half-duplex */ +pub(crate) const ADVERTISE_1000XPAUSE: u32 = 0x0080; /* Try for 1000BASE-X pause */ +pub(crate) const ADVERTISE_100FULL: u32 = 0x0100; /* Try for 100mbps full-duplex */ +pub(crate) const ADVERTISE_1000XPSE_ASYM: u32 = 0x0100; /* Try for 1000BASE-X asym pause */ +pub(crate) const ADVERTISE_100BASE4: u32 = 0x0200; /* Try for 100mbps 4k packets */ +pub(crate) const ADVERTISE_PAUSE_CAP: u32 = 0x0400; /* Try for pause */ +pub(crate) const ADVERTISE_PAUSE_ASYM: u32 = 0x0800; /* Try for asymetric pause */ +pub(crate) const ADVERTISE_RESV: u32 = 0x1000; /* Unused... */ +pub(crate) const ADVERTISE_RFAULT: u32 = 0x2000; /* Say we can detect faults */ +pub(crate) const ADVERTISE_LPACK: u32 = 0x4000; /* Ack link partners response */ +pub(crate) const ADVERTISE_NPAGE: u32 = 0x8000; /* Next page bit */ + +pub(crate) const ADVERTISE_FULL: u32 = ADVERTISE_100FULL | ADVERTISE_10FULL | ADVERTISE_CSMA; +pub(crate) const ADVERTISE_ALL: u32 = + ADVERTISE_10HALF | ADVERTISE_10FULL | ADVERTISE_100HALF | ADVERTISE_100FULL; + +/* Link partner ability register. */ +pub(crate) const LPA_SLCT: u32 = 0x001f; /* Same as advertise selector */ +pub(crate) const LPA_10HALF: u32 = 0x0020; /* Can do 10mbps half-duplex */ +pub(crate) const LPA_1000XFULL: u32 = 0x0020; /* Can do 1000BASE-X full-duplex */ +pub(crate) const LPA_10FULL: u32 = 0x0040; /* Can do 10mbps full-duplex */ +pub(crate) const LPA_1000XHALF: u32 = 0x0040; /* Can do 1000BASE-X half-duplex */ +pub(crate) const LPA_100HALF: u32 = 0x0080; /* Can do 100mbps half-duplex */ +pub(crate) const LPA_1000XPAUSE: u32 = 0x0080; /* Can do 1000BASE-X pause */ +pub(crate) const LPA_100FULL: u32 = 0x0100; /* Can do 100mbps full-duplex */ +pub(crate) const LPA_1000XPAUSE_ASYM: u32 = 0x0100; /* Can do 1000BASE-X pause asym*/ +pub(crate) const LPA_100BASE4: u32 = 0x0200; /* Can do 100mbps 4k packets */ +pub(crate) const LPA_PAUSE_CAP: u32 = 0x0400; /* Can pause */ +pub(crate) const LPA_PAUSE_ASYM: u32 = 0x0800; /* Can pause asymetrically */ +pub(crate) const LPA_RESV: u32 = 0x1000; /* Unused... */ +pub(crate) const LPA_RFAULT: u32 = 0x2000; /* Link partner faulted */ +pub(crate) const LPA_LPACK: u32 = 0x4000; /* Link partner acked us */ +pub(crate) const LPA_NPAGE: u32 = 0x8000; /* Next page bit */ + +pub(crate) const LPA_DUPLEX: u32 = LPA_10FULL | LPA_100FULL; +pub(crate) const LPA_100: u32 = LPA_100FULL | LPA_100HALF | LPA_100BASE4; + +/* Expansion register for auto-negotiation. */ +pub(crate) const EXPANSION_NWAY: u32 = 0x0001; /* Can do N-way auto-nego */ +pub(crate) const EXPANSION_LCWP: u32 = 0x0002; /* Got new RX page code word */ +pub(crate) const EXPANSION_ENABLENPAGE: u32 = 0x0004; /* This enables npage words */ +pub(crate) const EXPANSION_NPCAPABLE: u32 = 0x0008; /* Link partner supports npage */ +pub(crate) const EXPANSION_MFAULTS: u32 = 0x0010; /* Multiple faults detected */ +pub(crate) const EXPANSION_RESV: u32 = 0xffe0; /* Unused... */ + +pub(crate) const ESTATUS_1000_XFULL: u32 = 0x8000; /* Can do 1000BaseX Full */ +pub(crate) const ESTATUS_1000_XHALF: u32 = 0x4000; /* Can do 1000BaseX Half */ +pub(crate) const ESTATUS_1000_TFULL: u32 = 0x2000; /* Can do 1000BT Full */ +pub(crate) const ESTATUS_1000_THALF: u32 = 0x1000; /* Can do 1000BT Half */ + +/* N-way test register. */ +pub(crate) const NWAYTEST_RESV1: u32 = 0x00ff; /* Unused... */ +pub(crate) const NWAYTEST_LOOPBACK: u32 = 0x0100; /* Enable loopback for N-way */ +pub(crate) const NWAYTEST_RESV2: u32 = 0xfe00; /* Unused... */ + +/* MAC and PHY tx_config_Reg[15:0] for SGMII in-band auto-negotiation.*/ +pub(crate) const ADVERTISE_SGMII: u32 = 0x0001; /* MAC can do SGMII */ +pub(crate) const LPA_SGMII: u32 = 0x0001; /* PHY can do SGMII */ +pub(crate) const LPA_SGMII_SPD_MASK: u32 = 0x0c00; /* SGMII speed mask */ +pub(crate) const LPA_SGMII_FULL_DUPLEX: u32 = 0x1000; /* SGMII full duplex */ +pub(crate) const LPA_SGMII_DPX_SPD_MASK: u32 = 0x1C00; /* SGMII duplex and speed bits */ +pub(crate) const LPA_SGMII_10: u32 = 0x0000; /* 10Mbps */ +pub(crate) const LPA_SGMII_10HALF: u32 = 0x0000; /* Can do 10mbps half-duplex */ +pub(crate) const LPA_SGMII_10FULL: u32 = 0x1000; /* Can do 10mbps full-duplex */ +pub(crate) const LPA_SGMII_100: u32 = 0x0400; /* 100Mbps */ +pub(crate) const LPA_SGMII_100HALF: u32 = 0x0400; /* Can do 100mbps half-duplex */ +pub(crate) const LPA_SGMII_100FULL: u32 = 0x1400; /* Can do 100mbps full-duplex */ +pub(crate) const LPA_SGMII_1000: u32 = 0x0800; /* 1000Mbps */ +pub(crate) const LPA_SGMII_1000HALF: u32 = 0x0800; /* Can do 1000mbps half-duplex */ +pub(crate) const LPA_SGMII_1000FULL: u32 = 0x1800; /* Can do 1000mbps full-duplex */ +pub(crate) const LPA_SGMII_LINK: u32 = 0x8000; /* PHY link with copper-side partner */ + +/* 1000BASE-T Control register */ +pub(crate) const ADVERTISE_1000FULL: u32 = 0x0200; /* Advertise 1000BASE-T full duplex */ +pub(crate) const ADVERTISE_1000HALF: u32 = 0x0100; /* Advertise 1000BASE-T half duplex */ +pub(crate) const CTL1000_PREFER_MASTER: u32 = 0x0400; /* prefer to operate as master */ +pub(crate) const CTL1000_AS_MASTER: u32 = 0x0800; +pub(crate) const CTL1000_ENABLE_MASTER: u32 = 0x1000; + +/* 1000BASE-T Status register */ +pub(crate) const LPA_1000MSFAIL: u32 = 0x8000; /* Master/Slave resolution failure */ +pub(crate) const LPA_1000MSRES: u32 = 0x4000; /* Master/Slave resolution status */ +pub(crate) const LPA_1000LOCALRXOK: u32 = 0x2000; /* Link partner local receiver status */ +pub(crate) const LPA_1000REMRXOK: u32 = 0x1000; /* Link partner remote receiver status */ +pub(crate) const LPA_1000FULL: u32 = 0x0800; /* Link partner 1000BASE-T full duplex */ +pub(crate) const LPA_1000HALF: u32 = 0x0400; /* Link partner 1000BASE-T half duplex */ + +/* Flow control flags */ +pub(crate) const FLOW_CTRL_TX: u32 = 0x01; +pub(crate) const FLOW_CTRL_RX: u32 = 0x02; + +/* MMD Access Control register fields */ +pub(crate) const MII_MMD_CTRL_DEVAD_MASK: u32 = 0x1f; /* Mask MMD DEVAD*/ +pub(crate) const MII_MMD_CTRL_ADDR: u32 = 0x0000; /* Address */ +pub(crate) const MII_MMD_CTRL_NOINCR: u32 = 0x4000; /* no post increment */ +pub(crate) const MII_MMD_CTRL_INCR_RDWT: u32 = 0x8000; /* post increment on reads & writes */ +pub(crate) const MII_MMD_CTRL_INCR_ON_WT: u32 = 0xC000; /* post increment on writes only */ + +// MDIO +pub(crate) const MDIO_PRTAD_NONE: i32 = -1; +pub(crate) const MDIO_DEVAD_NONE: i32 = -1; +pub(crate) const MDIO_EMULATE_C22: i32 = 4; + +// PHY +pub(crate) const PHY_FIXED_ID: u32 = 0xa5a55a5a; +pub(crate) const PHY_NCSI_ID: u32 = 0xbeefcafe; +pub(crate) const PHY_GMII2RGMII_ID: u32 = 0x5a5a5a5a; +pub(crate) const PHY_MAX_ADDR: u32 = 32; +pub(crate) const PHY_FLAG_BROKEN_RESET: u32 = 1 << 0; /* soft reset not supported */ + +/* phy seed setup */ +pub(crate) const AUTO: u32 = 99; +pub(crate) const _1000BASET: u32 = 1000; +pub(crate) const _100BASET: u32 = 100; +pub(crate) const _10BASET: u32 = 10; +pub(crate) const HALF: u32 = 22; +pub(crate) const FULL: u32 = 44; + +/* phy register offsets */ +pub(crate) const MII_MIPSCR: u32 = 0x11; + +/* MII_LPA */ +pub(crate) const PHY_ANLPAR_PSB_802_3: u32 = 0x0001; +pub(crate) const PHY_ANLPAR_PSB_802_9: u32 = 0x0002; + +/* MII_CTRL1000 masks */ +pub(crate) const PHY_1000BTCR_1000FD: u32 = 0x0200; +pub(crate) const PHY_1000BTCR_1000HD: u32 = 0x0100; + +/* MII_STAT1000 masks */ +pub(crate) const PHY_1000BTSR_MSCF: u32 = 0x8000; +pub(crate) const PHY_1000BTSR_MSCR: u32 = 0x4000; +pub(crate) const PHY_1000BTSR_LRS: u32 = 0x2000; +pub(crate) const PHY_1000BTSR_RRS: u32 = 0x1000; +pub(crate) const PHY_1000BTSR_1000FD: u32 = 0x0800; +pub(crate) const PHY_1000BTSR_1000HD: u32 = 0x0400; + +/* phy EXSR */ +pub(crate) const ESTATUS_1000XF: u32 = 0x8000; +pub(crate) const ESTATUS_1000XH: u32 = 0x4000; + +/* Indicates what features are supported by the interface. */ +pub(crate) const SUPPORTED_10baseT_Half: u32 = 1 << 0; +pub(crate) const SUPPORTED_10baseT_Full: u32 = 1 << 1; +pub(crate) const SUPPORTED_100baseT_Half: u32 = 1 << 2; +pub(crate) const SUPPORTED_100baseT_Full: u32 = 1 << 3; +pub(crate) const SUPPORTED_1000baseT_Half: u32 = 1 << 4; +pub(crate) const SUPPORTED_1000baseT_Full: u32 = 1 << 5; +pub(crate) const SUPPORTED_Autoneg: u32 = 1 << 6; +pub(crate) const SUPPORTED_TP: u32 = 1 << 7; +pub(crate) const SUPPORTED_AUI: u32 = 1 << 8; +pub(crate) const SUPPORTED_MII: u32 = 1 << 9; +pub(crate) const SUPPORTED_FIBRE: u32 = 1 << 10; +pub(crate) const SUPPORTED_BNC: u32 = 1 << 11; +pub(crate) const SUPPORTED_10000baseT_Full: u32 = 1 << 12; +pub(crate) const SUPPORTED_Pause: u32 = 1 << 13; +pub(crate) const SUPPORTED_Asym_Pause: u32 = 1 << 14; +pub(crate) const SUPPORTED_2500baseX_Full: u32 = 1 << 15; +pub(crate) const SUPPORTED_Backplane: u32 = 1 << 16; +pub(crate) const SUPPORTED_1000baseKX_Full: u32 = 1 << 17; +pub(crate) const SUPPORTED_10000baseKX4_Full: u32 = 1 << 18; +pub(crate) const SUPPORTED_10000baseKR_Full: u32 = 1 << 19; +pub(crate) const SUPPORTED_10000baseR_FEC: u32 = 1 << 20; +pub(crate) const SUPPORTED_1000baseX_Half: u32 = 1 << 21; +pub(crate) const SUPPORTED_1000baseX_Full: u32 = 1 << 22; + +/* Indicates what features are advertised by the interface. */ +pub(crate) const ADVERTISED_10baseT_Half: u32 = 1 << 0; +pub(crate) const ADVERTISED_10baseT_Full: u32 = 1 << 1; +pub(crate) const ADVERTISED_100baseT_Half: u32 = 1 << 2; +pub(crate) const ADVERTISED_100baseT_Full: u32 = 1 << 3; +pub(crate) const ADVERTISED_1000baseT_Half: u32 = 1 << 4; +pub(crate) const ADVERTISED_1000baseT_Full: u32 = 1 << 5; +pub(crate) const ADVERTISED_Autoneg: u32 = 1 << 6; +pub(crate) const ADVERTISED_TP: u32 = 1 << 7; +pub(crate) const ADVERTISED_AUI: u32 = 1 << 8; +pub(crate) const ADVERTISED_MII: u32 = 1 << 9; +pub(crate) const ADVERTISED_FIBRE: u32 = 1 << 10; +pub(crate) const ADVERTISED_BNC: u32 = 1 << 11; +pub(crate) const ADVERTISED_10000baseT_Full: u32 = 1 << 12; +pub(crate) const ADVERTISED_Pause: u32 = 1 << 13; +pub(crate) const ADVERTISED_Asym_Pause: u32 = 1 << 14; +pub(crate) const ADVERTISED_2500baseX_Full: u32 = 1 << 15; +pub(crate) const ADVERTISED_Backplane: u32 = 1 << 16; +pub(crate) const ADVERTISED_1000baseKX_Full: u32 = 1 << 17; +pub(crate) const ADVERTISED_10000baseKX4_Full: u32 = 1 << 18; +pub(crate) const ADVERTISED_10000baseKR_Full: u32 = 1 << 19; +pub(crate) const ADVERTISED_10000baseR_FEC: u32 = 1 << 20; +pub(crate) const ADVERTISED_1000baseX_Half: u32 = 1 << 21; +pub(crate) const ADVERTISED_1000baseX_Full: u32 = 1 << 22; + +/* The forced speed, 10Mb, 100Mb, gigabit, 2.5Gb, 10GbE. */ +pub(crate) const SPEED_10: u32 = 10; +pub(crate) const SPEED_100: u32 = 100; +pub(crate) const SPEED_1000: u32 = 1000; +pub(crate) const SPEED_2500: u32 = 2500; +pub(crate) const SPEED_10000: u32 = 10000; +pub(crate) const SPEED_14000: u32 = 14000; +pub(crate) const SPEED_20000: u32 = 20000; +pub(crate) const SPEED_25000: u32 = 25000; +pub(crate) const SPEED_40000: u32 = 40000; +pub(crate) const SPEED_50000: u32 = 50000; +pub(crate) const SPEED_56000: u32 = 56000; +pub(crate) const SPEED_100000: u32 = 100000; +pub(crate) const SPEED_200000: u32 = 200000; + +/* Duplex, half or full. */ +pub(crate) const DUPLEX_HALF: u32 = 0x00; +pub(crate) const DUPLEX_FULL: u32 = 0x01; diff --git a/components/fxmac_rs/src/utils.rs b/components/fxmac_rs/src/utils.rs new file mode 100644 index 000000000..a7ddfcea3 --- /dev/null +++ b/components/fxmac_rs/src/utils.rs @@ -0,0 +1,262 @@ +//! Architecture helpers for FXMAC on supported targets. +//! +//! This module provides low-level helpers (CPU ID, barriers, cache ops) used by +//! the driver on aarch64 platforms. + +#[cfg(target_arch = "aarch64")] +mod arch { + use core::arch::asm; + + // PhytiumPi + pub const CORE0_AFF: u64 = 0x200; + pub const CORE1_AFF: u64 = 0x201; + pub const CORE2_AFF: u64 = 0x00; + pub const CORE3_AFF: u64 = 0x100; + pub const FCORE_NUM: u64 = 4; + + /// Converts MPIDR to CPU ID + pub(crate) fn mpidr2cpuid(mpidr: u64) -> usize { + // RK3588 + //((mpidr >> 8) & 0xff) as usize + + // Qemu + //(mpidr & 0xffffff & 0xff) as usize + + // PhytiumPi + match (mpidr & 0xfff) { + CORE0_AFF => 0, + CORE1_AFF => 1, + CORE2_AFF => 2, + CORE3_AFF => 3, + _ => { + error!("Failed to get PhytiumPi CPU Id from mpidr={:#x}", mpidr); + 0 + } + } + } + + #[inline] + /// Read reg: MPIDR_EL1 + fn read_mpidr() -> u64 { + let mut reg_r = 0; + unsafe { + core::arch::asm!("mrs {}, MPIDR_EL1", out(reg) reg_r); + } + reg_r + } + + pub(crate) fn get_cpu_id() -> usize { + let mpidr = read_mpidr(); + mpidr2cpuid(mpidr) + } + + /// Data Synchronization Barrier + pub(crate) fn DSB() { + unsafe { + core::arch::asm!("dsb sy"); + } + } + + #[inline] + // pseudo assembler instructions + fn MFCPSR() -> u32 { + let mut rval: u32 = 0; + unsafe { + asm!("mrs {0:x}, DAIF", out(reg) rval); + } + rval + } + + #[inline] + fn MTCPSR(val: u32) { + unsafe { + asm!("msr DAIF, {0:x}", in(reg) val); + } + } + #[inline] + fn MTCPDC_CIVAC(adr: u64) { + unsafe { + asm!("dc CIVAC, {}", in(reg) adr); + } + } + + #[inline] + fn MTCPDC_CVAC(adr: u64) { + unsafe { + asm!("dc CVAC, {}", in(reg) adr); + } + } + + /// CACHE of PhytiumPi + pub const CACHE_LINE_ADDR_MASK: u64 = 0x3F; + pub const CACHE_LINE: u64 = 64; + + /// Mask IRQ and FIQ interrupts in cpsr + pub const IRQ_FIQ_MASK: u32 = 0xC0; + + /// dc civac, virt_addr 通过虚拟地址清除和无效化cache + /// adr: 64bit start address of the range to be invalidated. + /// len: Length of the range to be invalidated in bytes. + pub(crate) fn FCacheDCacheInvalidateRange(mut adr: u64, len: u64) { + let end: u64 = adr + len; + adr &= !CACHE_LINE_ADDR_MASK; + let currmask: u32 = MFCPSR(); + MTCPSR(currmask | IRQ_FIQ_MASK); + if (len != 0) { + while adr < end { + MTCPDC_CIVAC(adr); /* Clean and Invalidate data cache by address to Point of Coherency */ + adr += CACHE_LINE; + } + } + /* Wait for invalidate to complete */ + DSB(); + MTCPSR(currmask); + } + + /// Flush Data cache + /// DC CVAC, Virtual address to use. No alignment restrictions apply to vaddr + /// adr: 64bit start address of the range to be flush. + pub(crate) fn FCacheDCacheFlushRange(mut adr: u64, len: u64) { + let end: u64 = adr + len; + adr &= !CACHE_LINE_ADDR_MASK; + let currmask: u32 = MFCPSR(); + MTCPSR(currmask | IRQ_FIQ_MASK); + if len != 0 { + while (adr < end) { + MTCPDC_CVAC(adr); /* Clean data cache by address to Point of Coherency */ + adr += CACHE_LINE; + } + } + /* Wait for Clean to complete */ + DSB(); + MTCPSR(currmask); + } + + use aarch64_cpu::registers::{Readable, CNTFRQ_EL0, CNTVCT_EL0}; + + #[inline] + pub fn now_tsc() -> u64 { + CNTVCT_EL0.get() + } + + #[inline] + pub fn timer_freq() -> u64 { + CNTFRQ_EL0.get() + } +} + +#[cfg(not(target_arch = "aarch64"))] +mod arch { + pub fn timer_freq() -> u64 { + unimplemented!() + } + pub fn now_tsc() -> u64 { + unimplemented!() + } + pub(crate) fn get_cpu_id() -> usize { + unimplemented!() + } + pub(crate) fn DSB() { + unimplemented!() + } + pub(crate) fn FCacheDCacheFlushRange(mut adr: u64, len: u64) { + unimplemented!() + } + pub(crate) fn FCacheDCacheInvalidateRange(mut adr: u64, len: u64) { + unimplemented!() + } +} + +use alloc::boxed::Box; +pub use arch::*; + +// 纳秒(ns) +#[inline] +pub(crate) fn now_ns() -> u64 { + let freq = timer_freq(); + now_tsc() * (1_000_000_000 / freq) +} + +pub(crate) fn ticks_to_nanos(ticks: u64) -> u64 { + let freq = timer_freq(); + ticks * (1_000_000_000 / freq) +} + +// 微秒(us) +pub(crate) fn usdelay(us: u64) { + let mut current_ticks: u64 = now_tsc(); + let delay2 = current_ticks + us * (timer_freq() / 1000000); + + while delay2 >= current_ticks { + core::hint::spin_loop(); + current_ticks = now_tsc(); + } + + trace!("usdelay current_ticks: {}", current_ticks); +} + +// 毫秒(ms) +pub(crate) fn msdelay(ms: u64) { + usdelay(ms * 1000); +} + +/// 虚拟地址转换成物理地址 +#[linkage = "weak"] +#[unsafe(export_name = "virt_to_phys_fxmac")] +pub(crate) fn virt_to_phys(addr: usize) -> usize { + debug!("fxmac: virt_to_phys_fxmac {:#x}", addr); + addr +} + +/// 物理地址转换成虚拟地址 +#[linkage = "weak"] +#[unsafe(export_name = "phys_to_virt_fxmac")] +pub(crate) fn phys_to_virt(addr: usize) -> usize { + debug!("fxmac: phys_to_virt_fxmac {:#x}", addr); + addr +} + +/// 申请DMA连续内存页 +#[linkage = "weak"] +#[unsafe(export_name = "dma_alloc_coherent_fxmac")] +pub(crate) fn dma_alloc_coherent(pages: usize) -> (usize, usize) { + let paddr: Box<[u32]> = if pages == 1 { + Box::new([0; 1024]) // 4096 + } else if pages == 8 { + Box::new([0; 1024 * 8]) // 4096 + } else { + warn!("Alloc {} pages failed", pages); + Box::new([0; 1024]) + }; + + let len = paddr.len(); + + let paddr = Box::into_raw(paddr) as *const u32 as usize; + //let vaddr = phys_to_virt(paddr); + let vaddr = paddr; + debug!("fxmac: dma alloc paddr: {:#x}, len={}", paddr, len); + + (vaddr, paddr) +} + +/// 释放DMA内存页 +#[linkage = "weak"] +#[unsafe(export_name = "dma_free_coherent_fxmac")] +pub(crate) fn dma_free_coherent(vaddr: usize, pages: usize) { + debug!("fxmac: dma free vaddr: {:#x}, pages={}", vaddr, pages); + let palloc = vaddr as *mut [u32; 1024]; + unsafe { + drop(Box::from_raw(palloc)); + } +} + +/// 请求分配irq +#[linkage = "weak"] +#[unsafe(export_name = "dma_request_irq_fxmac")] +pub(crate) fn dma_request_irq(irq: usize, handler: fn()) { + warn!("dma_request_irq_fxmac unimplemented"); + //unimplemented!() +} + +// 路由中断到指定的cpu,或所有的cpu +//pub(crate) fn InterruptSetTargetCpus() {} diff --git a/components/kernel_guard/Cargo.toml b/components/kernel_guard/Cargo.toml index c988af88f..0be3376c6 100644 --- a/components/kernel_guard/Cargo.toml +++ b/components/kernel_guard/Cargo.toml @@ -17,4 +17,4 @@ default = [] [dependencies] cfg-if = "1.0" -crate_interface = "0.1" +crate_interface = "0.3" diff --git a/components/range-alloc-arceos/.github/workflows/push.yml b/components/range-alloc-arceos/.github/workflows/push.yml new file mode 100644 index 000000000..a18de4a04 --- /dev/null +++ b/components/range-alloc-arceos/.github/workflows/push.yml @@ -0,0 +1,147 @@ +# ═══════════════════════════════════════════════════════════════════════════════ +# 组件仓库 GitHub Actions 配置模板 +# ═══════════════════════════════════════════════════════════════════════════════ +# +# 此文件用于子仓库,当子仓库有更新时通知主仓库进行 subtree pull 同步。 +# +# 【使用步骤】 +# ───────────────────────────────────────────────────────────────────────────── +# 1. 将此文件复制到子仓库的 .github/workflows/ 目录: +# cp scripts/push.yml <子仓库>/.github/workflows/push.yml +# +# 2. 在子仓库中配置 Secret: +# GitHub 仓库 → Settings → Secrets → Actions → New repository secret +# 名称: PARENT_REPO_TOKEN +# 值: 具有主仓库 repo 权限的 Personal Access Token +# +# 3. 修改下方 env 块中的一个变量(标注了「需要修改」的行): +# PARENT_REPO - 主仓库路径,例如 rcore-os/tgoskits +# (subtree 目录由主仓库自动从 git 历史中推断,无需手动指定) +# +# 【Token 权限要求】 +# ───────────────────────────────────────────────────────────────────────────── +# PARENT_REPO_TOKEN 需要 Classic Personal Access Token,权限包括: +# - repo (Full control of private repositories) +# 或 +# - Fine-grained token: Contents (Read and Write) +# +# 【触发条件】 +# ───────────────────────────────────────────────────────────────────────────── +# - 自动触发:推送到 dev 或 main 分支时 +# - 手动触发:Actions → Notify Parent Repository → Run workflow +# +# 【工作流程】 +# ───────────────────────────────────────────────────────────────────────────── +# 子仓库 push → 触发此工作流 → 调用主仓库 API → 主仓库 subtree pull +# +# 【注意事项】 +# ───────────────────────────────────────────────────────────────────────────── +# - 主仓库需要配置接收 repository_dispatch 事件的同步工作流 +# - 如果不需要子仓库到主仓库的同步,可以不使用此文件 +# +# ═══════════════════════════════════════════════════════════════════════════════ + +name: Notify Parent Repository + +# 当有新的推送时触发 +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Get repository info + id: repo + env: + GH_REPO_NAME: ${{ github.event.repository.name }} + GH_REF_NAME: ${{ github.ref_name }} + GH_SERVER_URL: ${{ github.server_url }} + GH_REPOSITORY: ${{ github.repository }} + run: | + # 直接使用 GitHub Actions 内置变量,通过 env 传入避免 shell 注入 + COMPONENT="$GH_REPO_NAME" + BRANCH="$GH_REF_NAME" + # 构造标准 HTTPS URL,供主仓库按 URL 精确匹配 repos.list + REPO_URL="${GH_SERVER_URL}/${GH_REPOSITORY}" + + echo "component=${COMPONENT}" >> $GITHUB_OUTPUT + echo "branch=${BRANCH}" >> $GITHUB_OUTPUT + echo "repo_url=${REPO_URL}" >> $GITHUB_OUTPUT + + echo "Component: ${COMPONENT}" + echo "Branch: ${BRANCH}" + echo "Repo URL: ${REPO_URL}" + + - name: Notify parent repository + env: + # ── 需要修改 ────────────────────────────────────────────────────────── + PARENT_REPO: "rcore-os/tgoskits" # 主仓库路径 + # ── 无需修改 ────────────────────────────────────────────────────────── + DISPATCH_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} + # 将用户可控内容通过 env 传入,避免直接插值到 shell 脚本 + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + GIT_ACTOR: ${{ github.actor }} + GIT_SHA: ${{ github.sha }} + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "Notifying parent repository about update in ${COMPONENT}:${BRANCH}" + + # 使用 jq 安全构建 JSON,避免 commit message 中任何特殊字符导致注入 + PAYLOAD=$(jq -n \ + --arg component "$COMPONENT" \ + --arg branch "$BRANCH" \ + --arg repo_url "$REPO_URL" \ + --arg commit "$GIT_SHA" \ + --arg message "$COMMIT_MESSAGE" \ + --arg author "$GIT_ACTOR" \ + '{ + event_type: "subtree-update", + client_payload: { + component: $component, + branch: $branch, + repo_url: $repo_url, + commit: $commit, + message: $message, + author: $author + } + }') + + curl --fail --show-error -X POST \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${DISPATCH_TOKEN}" \ + https://api.github.com/repos/${PARENT_REPO}/dispatches \ + -d "$PAYLOAD" + + echo "Notification sent successfully" + + - name: Create summary + env: + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + GIT_SHA: ${{ github.sha }} + GIT_ACTOR: ${{ github.actor }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "## Notification Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Component**: ${COMPONENT}" >> $GITHUB_STEP_SUMMARY + echo "- **Branch**: ${BRANCH}" >> $GITHUB_STEP_SUMMARY + echo "- **Repo URL**: ${REPO_URL}" >> $GITHUB_STEP_SUMMARY + echo "- **Commit**: \`${GIT_SHA}\`" >> $GITHUB_STEP_SUMMARY + echo "- **Author**: ${GIT_ACTOR}" >> $GITHUB_STEP_SUMMARY + echo "- **Status**: ✅ Notification sent" >> $GITHUB_STEP_SUMMARY diff --git a/components/riscv-h/.github/config.json b/components/riscv-h/.github/config.json index c750554c4..9c42e8300 100644 --- a/components/riscv-h/.github/config.json +++ b/components/riscv-h/.github/config.json @@ -1,7 +1,14 @@ { + "component": { + "name": "riscv-h", + "crate_name": "riscv_h" + }, "targets": [ "riscv64gc-unknown-none-elf" ], + "unit_test_targets": [ + "x86_64-unknown-linux-gnu" + ], "rust_components": [ "rust-src", "clippy", diff --git a/components/riscv-h/.github/workflows/check.yml b/components/riscv-h/.github/workflows/check.yml index 330fa15e9..765b0535a 100644 --- a/components/riscv-h/.github/workflows/check.yml +++ b/components/riscv-h/.github/workflows/check.yml @@ -1,66 +1,17 @@ -name: Quality Checks +# Quality Check Workflow +# References shared workflow from axci + +name: Check on: push: - branches: - - '**' - tags-ignore: - - '**' + branches: ['**'] + tags-ignore: ['**'] pull_request: - workflow_call: + workflow_dispatch: jobs: - load-config: - name: Load CI Configuration - runs-on: ubuntu-latest - outputs: - targets: ${{ steps.config.outputs.targets }} - rust_components: ${{ steps.config.outputs.rust_components }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Load configuration - id: config - run: | - TARGETS=$(jq -c '.targets' .github/config.json) - COMPONENTS=$(jq -r '.rust_components | join(", ")' .github/config.json) - - echo "targets=$TARGETS" >> $GITHUB_OUTPUT - echo "rust_components=$COMPONENTS" >> $GITHUB_OUTPUT - check: - name: Check - runs-on: ubuntu-latest - needs: load-config - strategy: - fail-fast: false - matrix: - target: ${{ fromJson(needs.load-config.outputs.targets) }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - with: - components: ${{ needs.load-config.outputs.rust_components }} - targets: ${{ matrix.target }} - - - name: Check rust version - run: rustc --version --verbose - - - name: Check code format - run: cargo fmt --all -- --check - - - name: Build - run: cargo build --target ${{ matrix.target }} --all-features - - - name: Run clippy - run: cargo clippy --target ${{ matrix.target }} --all-features -- -D warnings - - - name: Build documentation - env: - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - run: cargo doc --no-deps --target ${{ matrix.target }} --all-features + uses: arceos-hypervisor/axci/.github/workflows/check.yml@main + with: + targets: '["riscv64gc-unknown-none-elf"]' diff --git a/components/riscv-h/.github/workflows/deploy.yml b/components/riscv-h/.github/workflows/deploy.yml index fda9a323d..8f47dbe89 100644 --- a/components/riscv-h/.github/workflows/deploy.yml +++ b/components/riscv-h/.github/workflows/deploy.yml @@ -1,3 +1,6 @@ +# Deploy Workflow +# References shared workflow from axci + name: Deploy on: @@ -5,122 +8,10 @@ on: tags: - 'v[0-9]+.[0-9]+.[0-9]+' -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: 'pages' - cancel-in-progress: false - -env: - CARGO_TERM_COLOR: always - RUST_BACKTRACE: 1 - jobs: - verify-tag: - name: Verify Tag - runs-on: ubuntu-latest - outputs: - should_deploy: ${{ steps.check.outputs.should_deploy }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check if tag is on main or master branch - id: check - run: | - git fetch origin main master || true - BRANCHES=$(git branch -r --contains ${{ github.ref }}) - - if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then - echo "✓ Tag is on main or master branch" - echo "should_deploy=true" >> $GITHUB_OUTPUT - else - echo "✗ Tag is not on main or master branch, skipping deployment" - echo "Tag is on: $BRANCHES" - echo "should_deploy=false" >> $GITHUB_OUTPUT - fi - - - name: Verify version consistency - if: steps.check.outputs.should_deploy == 'true' - run: | - # Extract version from git tag (remove 'v' prefix) - TAG_VERSION="${{ github.ref_name }}" - TAG_VERSION="${TAG_VERSION#v}" - # Extract version from Cargo.toml - CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') - echo "Git tag version: $TAG_VERSION" - echo "Cargo.toml version: $CARGO_VERSION" - if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then - echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" - exit 1 - fi - echo "✓ Version check passed!" - - check: - uses: ./.github/workflows/check.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_deploy == 'true' - - test: - uses: ./.github/workflows/test.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_deploy == 'true' - - build: - name: Build documentation - runs-on: ubuntu-latest - needs: [verify-tag, check, test] - if: needs.verify-tag.outputs.should_deploy == 'true' - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Build docs - env: - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - run: | - # Build documentation - cargo doc --no-deps --all-features - - # Auto-detect documentation directory - # Check if doc exists in target/doc or target/*/doc - if [ -d "target/doc" ]; then - DOC_DIR="target/doc" - else - # Find doc directory under target/*/doc pattern - DOC_DIR=$(find target -type d -name doc -path "target/*/doc" | head -n 1) - if [ -z "$DOC_DIR" ]; then - echo "Error: Could not find documentation directory" - exit 1 - fi - fi - - echo "Documentation found in: $DOC_DIR" - printf '' $(cargo tree | head -1 | cut -d' ' -f1) > "${DOC_DIR}/index.html" - echo "DOC_DIR=${DOC_DIR}" >> $GITHUB_ENV - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: ${{ env.DOC_DIR }} - deploy: - name: Deploy to GitHub Pages - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: [verify-tag, build] - if: needs.verify-tag.outputs.should_deploy == 'true' - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 + uses: arceos-hypervisor/axci/.github/workflows/deploy.yml@main + with: + targets: '["riscv64gc-unknown-none-elf"]' + verify_branch: true + verify_version: true diff --git a/components/riscv-h/.github/workflows/push.yml b/components/riscv-h/.github/workflows/push.yml new file mode 100644 index 000000000..dda571781 --- /dev/null +++ b/components/riscv-h/.github/workflows/push.yml @@ -0,0 +1,16 @@ +name: Notify Parent Repository + +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + notify-parent: + name: Notify Parent Repository + # 调用 axci 仓库的可复用工作流 + uses: arceos-hypervisor/axci/.github/workflows/push.yml@main + secrets: + PARENT_REPO_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} diff --git a/components/riscv-h/.github/workflows/release.yml b/components/riscv-h/.github/workflows/release.yml index 2e857b48b..c6da7f462 100644 --- a/components/riscv-h/.github/workflows/release.yml +++ b/components/riscv-h/.github/workflows/release.yml @@ -1,3 +1,7 @@ +# Release Workflow +# References shared workflow from axci +# check + test must pass before release + name: Release on: @@ -6,155 +10,20 @@ on: - 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+-pre.[0-9]+' -permissions: - contents: write - jobs: - verify-tag: - name: Verify Tag - runs-on: ubuntu-latest - outputs: - should_release: ${{ steps.check.outputs.should_release }} - is_prerelease: ${{ steps.check.outputs.is_prerelease }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check tag type and branch - id: check - run: | - git fetch origin main master dev || true - - TAG="${{ github.ref_name }}" - BRANCHES=$(git branch -r --contains ${{ github.ref }}) - - echo "Tag: $TAG" - echo "Branches containing this tag: $BRANCHES" - - # Check if it's a prerelease tag - if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-pre\.[0-9]+$ ]]; then - echo "📦 Detected prerelease tag" - echo "is_prerelease=true" >> $GITHUB_OUTPUT - - if echo "$BRANCHES" | grep -q 'origin/dev'; then - echo "✓ Prerelease tag is on dev branch" - echo "should_release=true" >> $GITHUB_OUTPUT - else - echo "✗ Prerelease tag must be on dev branch, skipping release" - echo "should_release=false" >> $GITHUB_OUTPUT - fi - elif [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "📦 Detected stable release tag" - echo "is_prerelease=false" >> $GITHUB_OUTPUT - - if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then - echo "✓ Stable release tag is on main or master branch" - echo "should_release=true" >> $GITHUB_OUTPUT - else - echo "✗ Stable release tag must be on main or master branch, skipping release" - echo "should_release=false" >> $GITHUB_OUTPUT - fi - else - echo "✗ Unknown tag format, skipping release" - echo "is_prerelease=false" >> $GITHUB_OUTPUT - echo "should_release=false" >> $GITHUB_OUTPUT - fi - - - name: Verify version consistency - if: steps.check.outputs.should_release == 'true' - run: | - # Extract version from git tag (remove 'v' prefix) - TAG_VERSION="${{ github.ref_name }}" - TAG_VERSION="${TAG_VERSION#v}" - # Extract version from Cargo.toml - CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') - echo "Git tag version: $TAG_VERSION" - echo "Cargo.toml version: $CARGO_VERSION" - if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then - echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" - exit 1 - fi - echo "✓ Version check passed!" - check: - uses: ./.github/workflows/check.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_release == 'true' + uses: arceos-hypervisor/axci/.github/workflows/check.yml@main + with: + targets: '["riscv64gc-unknown-none-elf"]' test: - uses: ./.github/workflows/test.yml - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' + uses: arceos-hypervisor/axci/.github/workflows/test.yml@main release: - name: Create GitHub Release - runs-on: ubuntu-latest - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Generate release notes - id: release_notes - run: | - CURRENT_TAG="${{ github.ref_name }}" - - # Get previous tag - PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -A1 "^${CURRENT_TAG}$" | tail -n1) - - if [ -z "$PREVIOUS_TAG" ] || [ "$PREVIOUS_TAG" == "$CURRENT_TAG" ]; then - echo "No previous tag found, this is the first release" - CHANGELOG="Initial release" - else - echo "Generating changelog from $PREVIOUS_TAG to $CURRENT_TAG" - - # Generate changelog with commit messages - CHANGELOG=$(git log --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${CURRENT_TAG}") - - if [ -z "$CHANGELOG" ]; then - CHANGELOG="No changes" - fi - fi - - # Write changelog to output file (multi-line) - { - echo "changelog<> $GITHUB_OUTPUT - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - draft: false - prerelease: ${{ needs.verify-tag.outputs.is_prerelease == 'true' }} - body: | - ## Changes - ${{ steps.release_notes.outputs.changelog }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - publish: - name: Publish to crates.io - runs-on: ubuntu-latest - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Dry run publish - run: cargo publish --dry-run - - - name: Publish to crates.io - run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + needs: [check, test] + uses: arceos-hypervisor/axci/.github/workflows/release.yml@main + with: + verify_branch: true + verify_version: true + secrets: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/components/riscv-h/.github/workflows/test.yml b/components/riscv-h/.github/workflows/test.yml index dc3b293d9..6a58ef8e5 100644 --- a/components/riscv-h/.github/workflows/test.yml +++ b/components/riscv-h/.github/workflows/test.yml @@ -1,3 +1,6 @@ +# Integration Test Workflow +# References shared workflow from axci + name: Test on: @@ -7,44 +10,8 @@ on: tags-ignore: - '**' pull_request: - workflow_call: + workflow_dispatch: jobs: - load-config: - name: Load CI Configuration - runs-on: ubuntu-latest - outputs: - targets: ${{ steps.config.outputs.targets }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Load configuration - id: config - run: | - TARGETS=$(jq -c '.targets' .github/config.json) - echo "targets=$TARGETS" >> $GITHUB_OUTPUT - test: - name: Test - runs-on: ubuntu-latest - needs: load-config - strategy: - fail-fast: false - matrix: - target: ${{ fromJson(needs.load-config.outputs.targets) }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - # - name: Run tests - # run: cargo test --target ${{ matrix.target }} --all-features -- --nocapture - - # - name: Run doc tests - # run: cargo test --target ${{ matrix.target }} --doc - - name: Run tests - run: echo "Tests are skipped!" + uses: arceos-hypervisor/axci/.github/workflows/test.yml@main diff --git a/components/riscv-h/.gitignore b/components/riscv-h/.gitignore index 07283388f..6f3a252e4 100644 --- a/components/riscv-h/.gitignore +++ b/components/riscv-h/.gitignore @@ -24,3 +24,11 @@ target # Added by cargo /target + +# Test results (generated by shared test framework) +/test-results/ +/test_repos/ +*.log + +# Downloaded test framework +/scripts/.axci/ diff --git a/components/riscv-h/README.md b/components/riscv-h/README.md index 31523c42f..38e200e30 100644 --- a/components/riscv-h/README.md +++ b/components/riscv-h/README.md @@ -1,8 +1,11 @@ # riscv-h -[![CI](https://github.com/arceos-hypervisor/riscv-h/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/arceos-hypervisor/riscv-h/actions/workflows/ci.yml) [![Crates.io](https://img.shields.io/crates/v/riscv-h.svg)](https://crates.io/crates/riscv-h) -[![Documentation](https://docs.rs/riscv-h/badge.svg)](https://docs.rs/riscv-h) +[![Docs.rs](https://docs.rs/riscv-h/badge.svg)](https://docs.rs/riscv-h) +[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/arceos-hypervisor/riscv-h/blob/main/LICENSE) + +English | [中文](README_CN.md) RISC-V Hypervisor Extension Register Support diff --git a/components/riscv-h/README_CN.md b/components/riscv-h/README_CN.md new file mode 100644 index 000000000..4a488c233 --- /dev/null +++ b/components/riscv-h/README_CN.md @@ -0,0 +1,204 @@ +

riscv-h

+ +

RISC-V 虚拟化扩展寄存器支持库

+ +
+ +[![Crates.io](https://img.shields.io/crates/v/riscv-h.svg)](https://crates.io/crates/riscv-h) +[![Docs.rs](https://docs.rs/riscv-h/badge.svg)](https://docs.rs/riscv-h) +[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/arceos-hypervisor/riscv-h/blob/main/LICENSE) + +
+ +[English](README.md) | 中文 + +# 简介 + +RISC-V 虚拟化扩展寄存器支持库,提供 RISC-V Hypervisor Extension 中定义的控制和状态寄存器(CSR)的低级访问接口。支持 `#![no_std]`,可用于裸机和操作系统内核开发。 + +本库导出以下核心模块: + +- **`register::hstatus`** — 虚拟化管理员状态寄存器 +- **`register::hgatp`** — 虚拟化客户地址翻译和保护寄存器 +- **`register::hvip`** — 虚拟化虚拟中断挂起寄存器 +- **`register::vsstatus`** — 虚拟管理员状态寄存器 +- **`register::vsatp`** — 虚拟管理员地址翻译和保护寄存器 + +所有寄存器类型均实现了 `Copy`、`Clone`、`Debug` trait,并提供类型安全的位字段访问方法。 + +## 快速上手 + +### 环境要求 + +- Rust nightly 工具链 +- Rust 组件: rust-src, clippy, rustfmt, llvm-tools +- 目标平台: riscv64gc-unknown-none-elf + +```bash +# 安装 rustup(如未安装) +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# 安装 nightly 工具链及组件 +rustup install nightly +rustup component add rust-src clippy rustfmt llvm-tools --toolchain nightly + +# 添加 RISC-V 目标 +rustup target add riscv64gc-unknown-none-elf --toolchain nightly +``` + +### 运行检查和测试 + +```bash +# 1. 克隆仓库 +git clone https://github.com/arceos-hypervisor/riscv-h.git +cd riscv-h + +# 2. 代码检查(格式检查 + clippy + 构建 + 文档生成) +./scripts/check.sh + +# 3. 运行测试 +# 运行全部测试(单元测试 + 集成测试) +./scripts/test.sh + +# 仅运行单元测试 +./scripts/test.sh unit + +# 仅运行集成测试 +./scripts/test.sh integration + +# 列出所有可用的测试套件 +./scripts/test.sh list + +# 指定单元测试目标 +./scripts/test.sh unit --unit-targets x86_64-unknown-linux-gnu +``` + +## 集成使用 + +### 安装 + +在 `Cargo.toml` 中添加: + +```toml +[dependencies] +riscv-h = "0.2.0" +``` + +### 使用示例 + +```rust +#![no_std] + +use riscv_h::register::{hstatus, hgatp, hvip}; + +fn main() { + // 读取虚拟化管理员状态寄存器 + let hstatus = hstatus::read(); + + // 检查是否处于虚拟化模式 + if hstatus.spv() { + // 访问各个字段 + let vsxl = hstatus.vsxl(); // 虚拟管理员 XLEN + let vtw = hstatus.vtw(); // 陷阱 WFI + let vtsr = hstatus.vtsr(); // 陷阱 SRET + let vgein = hstatus.vgein(); // 虚拟客户外部中断号 + + setup_guest_translation(); + } + + // 配置虚拟中断挂起 + let mut hvip_val = hvip::Hvip::from_bits(0); + hvip_val.set_vssip(true); // 设置虚拟管理员软件中断挂起 + hvip_val.set_vstip(true); // 设置虚拟管理员定时器中断挂起 + hvip_val.set_vseip(true); // 设置虚拟管理员外部中断挂起 + + unsafe { + hvip_val.write(); + } +} + +fn setup_guest_translation() { + unsafe { + // 配置客户地址翻译 + let mut hgatp = hgatp::Hgatp::from_bits(0); + hgatp.set_mode(hgatp::HgatpValues::Sv48x4); // 使用 Sv48x4 模式 + hgatp.set_vmid(1); // 设置 VMID + hgatp.set_ppn(0x1000); // 设置根页表 PPN + hgatp.write(); + } +} +``` + +### 异常和中断委托 + +```rust +use riscv_h::register::{hedeleg, hideleg}; + +unsafe { + // 将常见异常委托给 VS-mode + hedeleg::set_ex2(true); // 非法指令 + hedeleg::set_ex8(true); // U-mode 环境调用 + hedeleg::set_ex12(true); // 指令页面错误 + hedeleg::set_ex13(true); // 加载页面错误 + hedeleg::set_ex15(true); // 存储页面错误 + + // 将定时器和软件中断委托给 VS-mode + hideleg::set_vstie(true); // VS-mode 定时器中断 + hideleg::set_vssie(true); // VS-mode 软件中断 +} +``` + +## 支持的寄存器 + +### 虚拟化控制寄存器 + +| 寄存器 | 描述 | CSR 地址 | +|--------|------|----------| +| `hstatus` | 虚拟化管理员状态寄存器 | 0x600 | +| `hedeleg` | 虚拟化异常委托寄存器 | 0x602 | +| `hideleg` | 虚拟化中断委托寄存器 | 0x603 | +| `hie` | 虚拟化中断使能寄存器 | 0x604 | +| `hcounteren` | 虚拟化计数器使能寄存器 | 0x606 | +| `hgatp` | 虚拟化客户地址翻译和保护寄存器 | 0x680 | + +### 虚拟管理员寄存器 + +| 寄存器 | 描述 | CSR 地址 | +|--------|------|----------| +| `vsstatus` | 虚拟管理员状态寄存器 | 0x200 | +| `vsie` | 虚拟管理员中断使能寄存器 | 0x204 | +| `vstvec` | 虚拟管理员陷阱向量寄存器 | 0x205 | +| `vsscratch` | 虚拟管理员暂存寄存器 | 0x240 | +| `vsepc` | 虚拟管理员异常程序计数器 | 0x241 | +| `vscause` | 虚拟管理员原因寄存器 | 0x242 | +| `vstval` | 虚拟管理员陷阱值寄存器 | 0x243 | +| `vsatp` | 虚拟管理员地址翻译和保护寄存器 | 0x280 | + +### 其他寄存器 + +- **中断管理**: `hip`, `hvip`, `hgeie`, `hgeip` +- **时间管理**: `htimedelta`, `htimedeltah` +- **陷阱信息**: `htval`, `htinst` +- **虚拟管理员中断**: `vsip` + +### 文档 + +生成并查看 API 文档: + +```bash +cargo doc --no-deps --open +``` + +在线文档:[docs.rs/riscv-h](https://docs.rs/riscv-h) + +# 贡献 + +1. Fork 仓库并创建分支 +2. 运行本地检查:`./scripts/check.sh` +3. 运行本地测试:`./scripts/test.sh` +4. 提交 PR 并通过 CI 检查 + +# 协议 + +本项目采用 Apache License, Version 2.0 许可证。详见 [LICENSE](LICENSE) 文件。 diff --git a/components/riscv-h/rust-toolchain.toml b/components/riscv-h/rust-toolchain.toml index 62898214e..adb3ba8db 100644 --- a/components/riscv-h/rust-toolchain.toml +++ b/components/riscv-h/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2025-05-20" +channel = "nightly-2026-03-18" components = ["rust-src", "llvm-tools", "rustfmt", "clippy"] profile = "minimal" targets = [ diff --git a/components/riscv-h/scripts/check.sh b/components/riscv-h/scripts/check.sh new file mode 100755 index 000000000..1613cbc49 --- /dev/null +++ b/components/riscv-h/scripts/check.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# +# riscv-h 代码检查脚本 +# 下载并调用 axci 仓库中的检查脚本 +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +COMPONENT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +COMPONENT_NAME="$(basename "$COMPONENT_DIR")" +AXCI_DIR="${SCRIPT_DIR}/.axci" +AXCI_REPO="https://github.com/arceos-hypervisor/axci.git" + +# 下载或更新 axci 仓库 +download_axci() { + if [ -d "$AXCI_DIR" ]; then + echo "Updating axci repository..." + cd "$AXCI_DIR" && git pull --quiet + else + echo "Downloading axci repository..." + git clone --quiet "$AXCI_REPO" "$AXCI_DIR" + fi +} + +# 主函数 +main() { + download_axci + + # 在组件目录中运行检查 + cd "$COMPONENT_DIR" + exec bash "$AXCI_DIR/check.sh" --component-dir "$COMPONENT_DIR" "$@" +} + +main "$@" diff --git a/components/riscv-h/scripts/test.sh b/components/riscv-h/scripts/test.sh new file mode 100755 index 000000000..a9489285b --- /dev/null +++ b/components/riscv-h/scripts/test.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# +# riscv-h 测试脚本 +# 下载并调用 axci 仓库中的测试框架 +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +COMPONENT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +AXCI_DIR="${SCRIPT_DIR}/.axci" +AXCI_REPO="https://github.com/arceos-hypervisor/axci.git" + +# 下载或更新 axci 仓库 +download_axci() { + if [ -d "$AXCI_DIR" ]; then + echo "Updating axci repository..." + cd "$AXCI_DIR" && git pull --quiet + else + echo "Downloading axci repository..." + git clone --quiet "$AXCI_REPO" "$AXCI_DIR" + fi +} + +# 主函数 +main() { + download_axci + + # 在组件目录中运行测试,自动指定当前组件 + cd "$COMPONENT_DIR" + exec bash "$AXCI_DIR/tests.sh" --component-dir "$COMPONENT_DIR" "$@" +} + +main "$@" diff --git a/components/riscv-h/tests/register_tests.rs b/components/riscv-h/tests/register_tests.rs new file mode 100644 index 000000000..0d91b623c --- /dev/null +++ b/components/riscv-h/tests/register_tests.rs @@ -0,0 +1,425 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Unit tests for individual RISC-V hypervisor registers + +use riscv_h::register::{hcounteren, hie, hip, hvip, vsatp, vsie, vsip, vstvec}; + +// ============================================================================ +// Hypervisor Control Registers Tests +// ============================================================================ + +mod hvip_tests { + use super::*; + + #[test] + fn test_hvip_bit_fields() { + let mut hvip = hvip::Hvip::from_bits(0); + + // Test VSSIP (bit 2) + assert!(!hvip.vssip()); + hvip.set_vssip(true); + assert!(hvip.vssip()); + assert_eq!(hvip.bits(), 0b100); + + // Test VSTIP (bit 6) + hvip.set_vstip(true); + assert!(hvip.vstip()); + assert_eq!(hvip.bits(), 0b100_0100); + + // Test VSEIP (bit 10) + hvip.set_vseip(true); + assert!(hvip.vseip()); + // bit 2 + bit 6 + bit 10 = 4 + 64 + 1024 = 1092 + assert_eq!(hvip.bits(), (1 << 2) | (1 << 6) | (1 << 10)); + } + + #[test] + fn test_hvip_bit_isolation() { + let mut hvip = hvip::Hvip::from_bits(0); + + // Set all interrupt pending bits + hvip.set_vssip(true); + hvip.set_vstip(true); + hvip.set_vseip(true); + + // Clear one at a time and verify others unchanged + hvip.set_vssip(false); + assert!(!hvip.vssip()); + assert!(hvip.vstip()); + assert!(hvip.vseip()); + + hvip.set_vstip(false); + assert!(!hvip.vssip()); + assert!(!hvip.vstip()); + assert!(hvip.vseip()); + } +} + +mod hcounteren_tests { + use super::*; + + #[test] + fn test_hcounteren_basic_fields() { + let mut hcounteren = hcounteren::Hcounteren::from_bits(0); + + // Test CY (bit 0) + hcounteren.set_cy(true); + assert!(hcounteren.cy()); + + // Test TM (bit 1) + hcounteren.set_tm(true); + assert!(hcounteren.tm()); + + // Test IR (bit 2) + hcounteren.set_ir(true); + assert!(hcounteren.ir()); + + assert_eq!(hcounteren.bits(), 0b111); + } + + #[test] + fn test_hcounteren_hpm_fields() { + let mut hcounteren = hcounteren::Hcounteren::from_bits(0); + + // Test a few HPM counters + hcounteren.set_hpm3(true); + hcounteren.set_hpm10(true); + hcounteren.set_hpm31(true); + + assert!(hcounteren.hpm3()); + assert!(hcounteren.hpm10()); + assert!(hcounteren.hpm31()); + + // Verify bits are at correct positions + assert_eq!(hcounteren.bits() & (1 << 3), 1 << 3); // hpm3 + assert_eq!(hcounteren.bits() & (1 << 10), 1 << 10); // hpm10 + assert_eq!(hcounteren.bits() & (1 << 31), 1 << 31); // hpm31 + } +} + +mod hie_tests { + use super::*; + + #[test] + fn test_hie_bit_fields() { + let mut hie = hie::Hie::from_bits(0); + + // Test VSSIE (bit 2) + hie.set_vssie(true); + assert!(hie.vssie()); + + // Test VSTIE (bit 6) + hie.set_vstie(true); + assert!(hie.vstie()); + + // Test VSEIE (bit 10) + hie.set_vseie(true); + assert!(hie.vseie()); + + // Test SGEIE (bit 12) + hie.set_sgeie(true); + assert!(hie.sgeie()); + + assert_eq!(hie.bits(), (1 << 2) | (1 << 6) | (1 << 10) | (1 << 12)); + } + + #[test] + fn test_hie_bit_isolation() { + let mut hie = hie::Hie::from_bits(0); + + hie.set_vssie(true); + hie.set_vstie(true); + hie.set_vseie(true); + hie.set_sgeie(true); + + // Clear one at a time + hie.set_vssie(false); + assert!(!hie.vssie()); + assert!(hie.vstie()); + assert!(hie.vseie()); + assert!(hie.sgeie()); + } +} + +mod hip_tests { + use super::*; + + #[test] + fn test_hip_bit_fields() { + let mut hip = hip::Hip::from_bits(0); + + // Test VSSIP (bit 2) + hip.set_vssip(true); + assert!(hip.vssip()); + + // Test VSTIP (bit 6) + hip.set_vstip(true); + assert!(hip.vstip()); + + // Test VSEIP (bit 10) + hip.set_vseip(true); + assert!(hip.vseip()); + + // Test SGEIP (bit 12) + hip.set_sgeip(true); + assert!(hip.sgeip()); + + assert_eq!(hip.bits(), (1 << 2) | (1 << 6) | (1 << 10) | (1 << 12)); + } + + #[test] + fn test_hip_bit_isolation() { + let mut hip = hip::Hip::from_bits(0); + + hip.set_vssip(true); + hip.set_vstip(true); + hip.set_vseip(true); + hip.set_sgeip(true); + + // Clear one at a time + hip.set_sgeip(false); + assert!(hip.vssip()); + assert!(hip.vstip()); + assert!(hip.vseip()); + assert!(!hip.sgeip()); + } +} + +// ============================================================================ +// Virtual Supervisor Registers Tests +// ============================================================================ + +mod vsatp_tests { + use super::*; + + #[test] + fn test_vsatp_mode() { + let mut vsatp = vsatp::Vsatp::from_bits(0); + + vsatp.set_mode(vsatp::HgatpValues::Bare); + assert!(matches!(vsatp.mode(), vsatp::HgatpValues::Bare)); + + vsatp.set_mode(vsatp::HgatpValues::Sv39x4); + assert!(matches!(vsatp.mode(), vsatp::HgatpValues::Sv39x4)); + + vsatp.set_mode(vsatp::HgatpValues::Sv48x4); + assert!(matches!(vsatp.mode(), vsatp::HgatpValues::Sv48x4)); + } + + #[test] + fn test_vsatp_asid() { + let mut vsatp = vsatp::Vsatp::from_bits(0); + + // ASID is 16 bits (bits 44-59) + vsatp.set_asid(0); + assert_eq!(vsatp.asid(), 0); + + vsatp.set_asid(0xFFFF); + assert_eq!(vsatp.asid(), 0xFFFF); + + vsatp.set_asid(0x1234); + assert_eq!(vsatp.asid(), 0x1234); + } + + #[test] + fn test_vsatp_ppn() { + let mut vsatp = vsatp::Vsatp::from_bits(0); + + // PPN is 44 bits (bits 0-43) + vsatp.set_ppn(0); + assert_eq!(vsatp.ppn(), 0); + + vsatp.set_ppn(0xFFFFFFFFFFF); + assert_eq!(vsatp.ppn(), 0xFFFFFFFFFFF); + } + + #[test] + fn test_vsatp_field_isolation() { + let mut vsatp = vsatp::Vsatp::from_bits(0); + + vsatp.set_mode(vsatp::HgatpValues::Sv48x4); + vsatp.set_asid(0xABCD); + vsatp.set_ppn(0x123456789); + + // Verify all fields are independent + assert!(matches!(vsatp.mode(), vsatp::HgatpValues::Sv48x4)); + assert_eq!(vsatp.asid(), 0xABCD); + assert_eq!(vsatp.ppn(), 0x123456789); + + // Modify one field shouldn't affect others + vsatp.set_asid(0); + assert!(matches!(vsatp.mode(), vsatp::HgatpValues::Sv48x4)); + assert_eq!(vsatp.asid(), 0); + assert_eq!(vsatp.ppn(), 0x123456789); + } +} + +mod vstvec_tests { + use super::*; + + #[test] + fn test_vstvec_base() { + let mut vstvec = vstvec::Vstvec::from_bits(0); + + // Base is bits 2-63, must be 4-byte aligned + vstvec.set_base(0x1000); + assert_eq!(vstvec.base(), 0x1000); + + vstvec.set_base(0x80000000); + assert_eq!(vstvec.base(), 0x80000000); + } + + #[test] + fn test_vstvec_mode() { + let mut vstvec = vstvec::Vstvec::from_bits(0); + + // Mode is bits 0-1 + vstvec.set_mode(0); // Direct + assert_eq!(vstvec.mode(), 0); + + vstvec.set_mode(1); // Vectored + assert_eq!(vstvec.mode(), 1); + } + + #[test] + fn test_vstvec_field_isolation() { + let mut vstvec = vstvec::Vstvec::from_bits(0); + + vstvec.set_base(0x12345678); + vstvec.set_mode(1); + + // base() returns the raw bits 2-63 value + assert_eq!(vstvec.base(), 0x12345678); + assert_eq!(vstvec.mode(), 1); + } +} + +mod vsie_tests { + use super::*; + + #[test] + fn test_vsie_bit_fields() { + let mut vsie = vsie::Vsie::from_bits(0); + + // Test SSIE (bit 1) + vsie.set_ssie(true); + assert!(vsie.ssie()); + + // Test STIE (bit 5) + vsie.set_stie(true); + assert!(vsie.stie()); + + // Test SEIE (bit 9) + vsie.set_seie(true); + assert!(vsie.seie()); + + assert_eq!(vsie.bits(), (1 << 1) | (1 << 5) | (1 << 9)); + } + + #[test] + fn test_vsie_bit_isolation() { + let mut vsie = vsie::Vsie::from_bits(0); + + vsie.set_ssie(true); + vsie.set_stie(true); + vsie.set_seie(true); + + vsie.set_ssie(false); + assert!(!vsie.ssie()); + assert!(vsie.stie()); + assert!(vsie.seie()); + } +} + +mod vsip_tests { + use super::*; + + #[test] + fn test_vsip_bit_fields() { + let mut vsip = vsip::Vsip::from_bits(0); + + // Test SSIP (bit 1) + vsip.set_ssip(true); + assert!(vsip.ssip()); + + // Test STIP (bit 5) + vsip.set_stip(true); + assert!(vsip.stip()); + + // Test SEIP (bit 9) + vsip.set_seip(true); + assert!(vsip.seip()); + + assert_eq!(vsip.bits(), (1 << 1) | (1 << 5) | (1 << 9)); + } + + #[test] + fn test_vsip_bit_isolation() { + let mut vsip = vsip::Vsip::from_bits(0); + + vsip.set_ssip(true); + vsip.set_stip(true); + vsip.set_seip(true); + + vsip.set_ssip(false); + assert!(!vsip.ssip()); + assert!(vsip.stip()); + assert!(vsip.seip()); + } +} + +// ============================================================================ +// Copy/Clone Trait Tests +// ============================================================================ + +mod trait_tests { + use super::*; + + #[test] + fn test_all_registers_copy_clone() { + // Test that all register types implement Copy and Clone + let hvip = hvip::Hvip::from_bits(0x123); + let _copy = hvip; + let _clone = hvip.clone(); + + let hcounteren = hcounteren::Hcounteren::from_bits(0xFF); + let _copy = hcounteren; + let _clone = hcounteren.clone(); + + let vsatp = vsatp::Vsatp::from_bits(0xABC); + let _copy = vsatp; + let _clone = vsatp.clone(); + + let vstvec = vstvec::Vstvec::from_bits(0x1000); + let _copy = vstvec; + let _clone = vstvec.clone(); + } + + #[test] + fn test_all_registers_debug() { + // Test that all register types implement Debug + let hvip = hvip::Hvip::from_bits(0x123); + assert!(format!("{:?}", hvip).contains("Hvip")); + + let hcounteren = hcounteren::Hcounteren::from_bits(0xFF); + assert!(format!("{:?}", hcounteren).contains("Hcounteren")); + + let vsatp = vsatp::Vsatp::from_bits(0xABC); + assert!(format!("{:?}", vsatp).contains("Vsatp")); + + let vstvec = vstvec::Vstvec::from_bits(0x1000); + assert!(format!("{:?}", vstvec).contains("Vstvec")); + } +} diff --git a/components/riscv_vcpu/.github/workflows/push.yml b/components/riscv_vcpu/.github/workflows/push.yml new file mode 100644 index 000000000..a18de4a04 --- /dev/null +++ b/components/riscv_vcpu/.github/workflows/push.yml @@ -0,0 +1,147 @@ +# ═══════════════════════════════════════════════════════════════════════════════ +# 组件仓库 GitHub Actions 配置模板 +# ═══════════════════════════════════════════════════════════════════════════════ +# +# 此文件用于子仓库,当子仓库有更新时通知主仓库进行 subtree pull 同步。 +# +# 【使用步骤】 +# ───────────────────────────────────────────────────────────────────────────── +# 1. 将此文件复制到子仓库的 .github/workflows/ 目录: +# cp scripts/push.yml <子仓库>/.github/workflows/push.yml +# +# 2. 在子仓库中配置 Secret: +# GitHub 仓库 → Settings → Secrets → Actions → New repository secret +# 名称: PARENT_REPO_TOKEN +# 值: 具有主仓库 repo 权限的 Personal Access Token +# +# 3. 修改下方 env 块中的一个变量(标注了「需要修改」的行): +# PARENT_REPO - 主仓库路径,例如 rcore-os/tgoskits +# (subtree 目录由主仓库自动从 git 历史中推断,无需手动指定) +# +# 【Token 权限要求】 +# ───────────────────────────────────────────────────────────────────────────── +# PARENT_REPO_TOKEN 需要 Classic Personal Access Token,权限包括: +# - repo (Full control of private repositories) +# 或 +# - Fine-grained token: Contents (Read and Write) +# +# 【触发条件】 +# ───────────────────────────────────────────────────────────────────────────── +# - 自动触发:推送到 dev 或 main 分支时 +# - 手动触发:Actions → Notify Parent Repository → Run workflow +# +# 【工作流程】 +# ───────────────────────────────────────────────────────────────────────────── +# 子仓库 push → 触发此工作流 → 调用主仓库 API → 主仓库 subtree pull +# +# 【注意事项】 +# ───────────────────────────────────────────────────────────────────────────── +# - 主仓库需要配置接收 repository_dispatch 事件的同步工作流 +# - 如果不需要子仓库到主仓库的同步,可以不使用此文件 +# +# ═══════════════════════════════════════════════════════════════════════════════ + +name: Notify Parent Repository + +# 当有新的推送时触发 +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Get repository info + id: repo + env: + GH_REPO_NAME: ${{ github.event.repository.name }} + GH_REF_NAME: ${{ github.ref_name }} + GH_SERVER_URL: ${{ github.server_url }} + GH_REPOSITORY: ${{ github.repository }} + run: | + # 直接使用 GitHub Actions 内置变量,通过 env 传入避免 shell 注入 + COMPONENT="$GH_REPO_NAME" + BRANCH="$GH_REF_NAME" + # 构造标准 HTTPS URL,供主仓库按 URL 精确匹配 repos.list + REPO_URL="${GH_SERVER_URL}/${GH_REPOSITORY}" + + echo "component=${COMPONENT}" >> $GITHUB_OUTPUT + echo "branch=${BRANCH}" >> $GITHUB_OUTPUT + echo "repo_url=${REPO_URL}" >> $GITHUB_OUTPUT + + echo "Component: ${COMPONENT}" + echo "Branch: ${BRANCH}" + echo "Repo URL: ${REPO_URL}" + + - name: Notify parent repository + env: + # ── 需要修改 ────────────────────────────────────────────────────────── + PARENT_REPO: "rcore-os/tgoskits" # 主仓库路径 + # ── 无需修改 ────────────────────────────────────────────────────────── + DISPATCH_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} + # 将用户可控内容通过 env 传入,避免直接插值到 shell 脚本 + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + GIT_ACTOR: ${{ github.actor }} + GIT_SHA: ${{ github.sha }} + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "Notifying parent repository about update in ${COMPONENT}:${BRANCH}" + + # 使用 jq 安全构建 JSON,避免 commit message 中任何特殊字符导致注入 + PAYLOAD=$(jq -n \ + --arg component "$COMPONENT" \ + --arg branch "$BRANCH" \ + --arg repo_url "$REPO_URL" \ + --arg commit "$GIT_SHA" \ + --arg message "$COMMIT_MESSAGE" \ + --arg author "$GIT_ACTOR" \ + '{ + event_type: "subtree-update", + client_payload: { + component: $component, + branch: $branch, + repo_url: $repo_url, + commit: $commit, + message: $message, + author: $author + } + }') + + curl --fail --show-error -X POST \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${DISPATCH_TOKEN}" \ + https://api.github.com/repos/${PARENT_REPO}/dispatches \ + -d "$PAYLOAD" + + echo "Notification sent successfully" + + - name: Create summary + env: + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + GIT_SHA: ${{ github.sha }} + GIT_ACTOR: ${{ github.actor }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "## Notification Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Component**: ${COMPONENT}" >> $GITHUB_STEP_SUMMARY + echo "- **Branch**: ${BRANCH}" >> $GITHUB_STEP_SUMMARY + echo "- **Repo URL**: ${REPO_URL}" >> $GITHUB_STEP_SUMMARY + echo "- **Commit**: \`${GIT_SHA}\`" >> $GITHUB_STEP_SUMMARY + echo "- **Author**: ${GIT_ACTOR}" >> $GITHUB_STEP_SUMMARY + echo "- **Status**: ✅ Notification sent" >> $GITHUB_STEP_SUMMARY diff --git a/components/riscv_vcpu/Cargo.toml b/components/riscv_vcpu/Cargo.toml index eb59152f5..6ffefc52a 100644 --- a/components/riscv_vcpu/Cargo.toml +++ b/components/riscv_vcpu/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "riscv_vcpu" -version = "0.2.2" +version = "0.3.0" edition = "2024" authors = [ "KeYang Hu ", @@ -23,7 +23,7 @@ log = "0.4" cfg-if = "1.0" bitflags = "2.2" bit_field = "0.10" -crate_interface = "0.1" +crate_interface = "0.3" riscv = { version = "0.14.0", features = ["s-mode"] } riscv-h = "0.2" @@ -32,13 +32,13 @@ riscv-decode = "0.2.3" rustsbi = { version = "0.4.0", features = ["forward"] } sbi-rt = { version = "0.0.3", features = ["integer-impls"] } sbi-spec = { version = "0.0.7", features = ["legacy"] } -tock-registers = "0.9" +tock-registers = "0.10" memoffset = { version = ">=0.6.5", features = ["unstable_const"] } -axerrno = "0.1.0" +axerrno = "0.2" page_table_entry = "0.6" memory_addr = "0.4" -axaddrspace = "0.1.4" -axvcpu = "0.2" -axvisor_api = "0.1" +axaddrspace = "0.3" +axvcpu = "0.3" +axvisor_api = "0.3" diff --git a/components/riscv_vcpu/rust-toolchain.toml b/components/riscv_vcpu/rust-toolchain.toml index 06e0e5b0f..f43861500 100644 --- a/components/riscv_vcpu/rust-toolchain.toml +++ b/components/riscv_vcpu/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2025-05-20" +channel = "nightly-2026-02-25" components = ["rust-src", "llvm-tools", "rustfmt", "clippy"] profile = "minimal" targets = [ diff --git a/components/riscv_vcpu/src/percpu.rs b/components/riscv_vcpu/src/percpu.rs index ff1d13426..ad56b3f05 100644 --- a/components/riscv_vcpu/src/percpu.rs +++ b/components/riscv_vcpu/src/percpu.rs @@ -12,29 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -use core::marker::PhantomData; - use axerrno::{AxError, AxResult}; -use axvcpu::{AxArchPerCpu, AxVCpuHal}; +use axvcpu::AxArchPerCpu; use riscv::register::sie; use riscv_h::register::{hedeleg, hideleg, hvip}; use crate::{consts::traps, has_hardware_support}; /// Risc-V per-CPU state. -pub struct RISCVPerCpu { - _marker: PhantomData, -} +pub struct RISCVPerCpu; -impl AxArchPerCpu for RISCVPerCpu { +impl AxArchPerCpu for RISCVPerCpu { fn new(_cpu_id: usize) -> AxResult { unsafe { setup_csrs(); } - Ok(Self { - _marker: PhantomData, - }) + Ok(Self) } fn is_enabled(&self) -> bool { diff --git a/components/riscv_vcpu/src/vcpu.rs b/components/riscv_vcpu/src/vcpu.rs index 322ced102..d3c97e09b 100644 --- a/components/riscv_vcpu/src/vcpu.rs +++ b/components/riscv_vcpu/src/vcpu.rs @@ -56,10 +56,9 @@ pub struct VCpuConfig {} #[derive(Default)] /// A virtual CPU within a guest -pub struct RISCVVCpu { +pub struct RISCVVCpu { regs: VmCpuRegisters, sbi: RISCVVCpuSbi, - _marker: core::marker::PhantomData, } #[derive(RustSBI)] @@ -75,7 +74,7 @@ impl Default for RISCVVCpuSbi { } } -impl axvcpu::AxArchVCpu for RISCVVCpu { +impl axvcpu::AxArchVCpu for RISCVVCpu { type CreateConfig = RISCVVCpuCreateConfig; type SetupConfig = (); @@ -91,7 +90,6 @@ impl axvcpu::AxArchVCpu for RISCVVCpu { Ok(Self { regs, sbi: RISCVVCpuSbi::default(), - _marker: core::marker::PhantomData, }) } @@ -227,7 +225,7 @@ impl axvcpu::AxArchVCpu for RISCVVCpu { } } -impl RISCVVCpu { +impl RISCVVCpu { /// Gets one of the vCPU's general purpose registers. pub fn get_gpr(&self, index: GprIndex) -> usize { self.regs.guest_regs.gprs.reg(index) @@ -249,7 +247,7 @@ impl RISCVVCpu { } } -impl RISCVVCpu { +impl RISCVVCpu { fn vmexit_handler(&mut self) -> AxResult { self.regs.trap_csrs.load_from_hw(); diff --git a/components/riscv_vplic/.github/config.json b/components/riscv_vplic/.github/config.json index c750554c4..684673b7a 100644 --- a/components/riscv_vplic/.github/config.json +++ b/components/riscv_vplic/.github/config.json @@ -1,7 +1,14 @@ { + "component": { + "name": "riscv_vplic", + "crate_name": "riscv_vplic" + }, "targets": [ "riscv64gc-unknown-none-elf" ], + "unit_test_targets": [ + "x86_64-unknown-linux-gnu" + ], "rust_components": [ "rust-src", "clippy", diff --git a/components/riscv_vplic/.github/workflows/check.yml b/components/riscv_vplic/.github/workflows/check.yml index 330fa15e9..765b0535a 100644 --- a/components/riscv_vplic/.github/workflows/check.yml +++ b/components/riscv_vplic/.github/workflows/check.yml @@ -1,66 +1,17 @@ -name: Quality Checks +# Quality Check Workflow +# References shared workflow from axci + +name: Check on: push: - branches: - - '**' - tags-ignore: - - '**' + branches: ['**'] + tags-ignore: ['**'] pull_request: - workflow_call: + workflow_dispatch: jobs: - load-config: - name: Load CI Configuration - runs-on: ubuntu-latest - outputs: - targets: ${{ steps.config.outputs.targets }} - rust_components: ${{ steps.config.outputs.rust_components }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Load configuration - id: config - run: | - TARGETS=$(jq -c '.targets' .github/config.json) - COMPONENTS=$(jq -r '.rust_components | join(", ")' .github/config.json) - - echo "targets=$TARGETS" >> $GITHUB_OUTPUT - echo "rust_components=$COMPONENTS" >> $GITHUB_OUTPUT - check: - name: Check - runs-on: ubuntu-latest - needs: load-config - strategy: - fail-fast: false - matrix: - target: ${{ fromJson(needs.load-config.outputs.targets) }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - with: - components: ${{ needs.load-config.outputs.rust_components }} - targets: ${{ matrix.target }} - - - name: Check rust version - run: rustc --version --verbose - - - name: Check code format - run: cargo fmt --all -- --check - - - name: Build - run: cargo build --target ${{ matrix.target }} --all-features - - - name: Run clippy - run: cargo clippy --target ${{ matrix.target }} --all-features -- -D warnings - - - name: Build documentation - env: - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - run: cargo doc --no-deps --target ${{ matrix.target }} --all-features + uses: arceos-hypervisor/axci/.github/workflows/check.yml@main + with: + targets: '["riscv64gc-unknown-none-elf"]' diff --git a/components/riscv_vplic/.github/workflows/deploy.yml b/components/riscv_vplic/.github/workflows/deploy.yml index 9938e2cc2..8f47dbe89 100644 --- a/components/riscv_vplic/.github/workflows/deploy.yml +++ b/components/riscv_vplic/.github/workflows/deploy.yml @@ -1,3 +1,6 @@ +# Deploy Workflow +# References shared workflow from axci + name: Deploy on: @@ -5,122 +8,10 @@ on: tags: - 'v[0-9]+.[0-9]+.[0-9]+' -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: 'pages' - cancel-in-progress: false - -env: - CARGO_TERM_COLOR: always - RUST_BACKTRACE: 1 - jobs: - verify-tag: - name: Verify Tag - runs-on: ubuntu-latest - outputs: - should_deploy: ${{ steps.check.outputs.should_deploy }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check if tag is on main or master branch - id: check - run: | - git fetch origin main master || true - BRANCHES=$(git branch -r --contains ${{ github.ref }}) - - if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then - echo "✓ Tag is on main or master branch" - echo "should_deploy=true" >> $GITHUB_OUTPUT - else - echo "✗ Tag is not on main or master branch, skipping deployment" - echo "Tag is on: $BRANCHES" - echo "should_deploy=false" >> $GITHUB_OUTPUT - fi - - - name: Verify version consistency - if: steps.check.outputs.should_deploy == 'true' - run: | - # Extract version from git tag (remove 'v' prefix) - TAG_VERSION="${{ github.ref_name }}" - TAG_VERSION="${TAG_VERSION#v}" - # Extract version from Cargo.toml - CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') - echo "Git tag version: $TAG_VERSION" - echo "Cargo.toml version: $CARGO_VERSION" - if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then - echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" - exit 1 - fi - echo "✓ Version check passed!" - - check: - uses: ./.github/workflows/check.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_deploy == 'true' - - test: - uses: ./.github/workflows/test.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_deploy == 'true' - - build: - name: Build documentation - runs-on: ubuntu-latest - needs: [verify-tag, check, test] - if: needs.verify-tag.outputs.should_deploy == 'true' - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Build docs - env: - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - run: | - # Build documentation - cargo doc --no-deps --all-features - - # Auto-detect documentation directory - # Check if doc exists in target/doc or target/*/doc - if [ -d "target/doc" ]; then - DOC_DIR="target/doc" - else - # Find doc directory under target/*/doc pattern - DOC_DIR=$(find target -type d -name doc -path "target/*/doc" | head -n 1) - if [ -z "$DOC_DIR" ]; then - echo "Error: Could not find documentation directory" - exit 1 - fi - fi - - echo "Documentation found in: $DOC_DIR" - printf '' $(cargo tree | head -1 | cut -d' ' -f1) > "${DOC_DIR}/index.html" - echo "DOC_DIR=${DOC_DIR}" >> $GITHUB_ENV - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: ${{ env.DOC_DIR }} - deploy: - name: Deploy to GitHub Pages - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: [verify-tag, build] - if: needs.verify-tag.outputs.should_deploy == 'true' - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 \ No newline at end of file + uses: arceos-hypervisor/axci/.github/workflows/deploy.yml@main + with: + targets: '["riscv64gc-unknown-none-elf"]' + verify_branch: true + verify_version: true diff --git a/components/riscv_vplic/.github/workflows/push.yml b/components/riscv_vplic/.github/workflows/push.yml new file mode 100644 index 000000000..dda571781 --- /dev/null +++ b/components/riscv_vplic/.github/workflows/push.yml @@ -0,0 +1,16 @@ +name: Notify Parent Repository + +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + notify-parent: + name: Notify Parent Repository + # 调用 axci 仓库的可复用工作流 + uses: arceos-hypervisor/axci/.github/workflows/push.yml@main + secrets: + PARENT_REPO_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} diff --git a/components/riscv_vplic/.github/workflows/release.yml b/components/riscv_vplic/.github/workflows/release.yml index 2e857b48b..c6da7f462 100644 --- a/components/riscv_vplic/.github/workflows/release.yml +++ b/components/riscv_vplic/.github/workflows/release.yml @@ -1,3 +1,7 @@ +# Release Workflow +# References shared workflow from axci +# check + test must pass before release + name: Release on: @@ -6,155 +10,20 @@ on: - 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+-pre.[0-9]+' -permissions: - contents: write - jobs: - verify-tag: - name: Verify Tag - runs-on: ubuntu-latest - outputs: - should_release: ${{ steps.check.outputs.should_release }} - is_prerelease: ${{ steps.check.outputs.is_prerelease }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check tag type and branch - id: check - run: | - git fetch origin main master dev || true - - TAG="${{ github.ref_name }}" - BRANCHES=$(git branch -r --contains ${{ github.ref }}) - - echo "Tag: $TAG" - echo "Branches containing this tag: $BRANCHES" - - # Check if it's a prerelease tag - if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-pre\.[0-9]+$ ]]; then - echo "📦 Detected prerelease tag" - echo "is_prerelease=true" >> $GITHUB_OUTPUT - - if echo "$BRANCHES" | grep -q 'origin/dev'; then - echo "✓ Prerelease tag is on dev branch" - echo "should_release=true" >> $GITHUB_OUTPUT - else - echo "✗ Prerelease tag must be on dev branch, skipping release" - echo "should_release=false" >> $GITHUB_OUTPUT - fi - elif [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "📦 Detected stable release tag" - echo "is_prerelease=false" >> $GITHUB_OUTPUT - - if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then - echo "✓ Stable release tag is on main or master branch" - echo "should_release=true" >> $GITHUB_OUTPUT - else - echo "✗ Stable release tag must be on main or master branch, skipping release" - echo "should_release=false" >> $GITHUB_OUTPUT - fi - else - echo "✗ Unknown tag format, skipping release" - echo "is_prerelease=false" >> $GITHUB_OUTPUT - echo "should_release=false" >> $GITHUB_OUTPUT - fi - - - name: Verify version consistency - if: steps.check.outputs.should_release == 'true' - run: | - # Extract version from git tag (remove 'v' prefix) - TAG_VERSION="${{ github.ref_name }}" - TAG_VERSION="${TAG_VERSION#v}" - # Extract version from Cargo.toml - CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') - echo "Git tag version: $TAG_VERSION" - echo "Cargo.toml version: $CARGO_VERSION" - if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then - echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" - exit 1 - fi - echo "✓ Version check passed!" - check: - uses: ./.github/workflows/check.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_release == 'true' + uses: arceos-hypervisor/axci/.github/workflows/check.yml@main + with: + targets: '["riscv64gc-unknown-none-elf"]' test: - uses: ./.github/workflows/test.yml - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' + uses: arceos-hypervisor/axci/.github/workflows/test.yml@main release: - name: Create GitHub Release - runs-on: ubuntu-latest - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Generate release notes - id: release_notes - run: | - CURRENT_TAG="${{ github.ref_name }}" - - # Get previous tag - PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -A1 "^${CURRENT_TAG}$" | tail -n1) - - if [ -z "$PREVIOUS_TAG" ] || [ "$PREVIOUS_TAG" == "$CURRENT_TAG" ]; then - echo "No previous tag found, this is the first release" - CHANGELOG="Initial release" - else - echo "Generating changelog from $PREVIOUS_TAG to $CURRENT_TAG" - - # Generate changelog with commit messages - CHANGELOG=$(git log --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${CURRENT_TAG}") - - if [ -z "$CHANGELOG" ]; then - CHANGELOG="No changes" - fi - fi - - # Write changelog to output file (multi-line) - { - echo "changelog<> $GITHUB_OUTPUT - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - draft: false - prerelease: ${{ needs.verify-tag.outputs.is_prerelease == 'true' }} - body: | - ## Changes - ${{ steps.release_notes.outputs.changelog }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - publish: - name: Publish to crates.io - runs-on: ubuntu-latest - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Dry run publish - run: cargo publish --dry-run - - - name: Publish to crates.io - run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + needs: [check, test] + uses: arceos-hypervisor/axci/.github/workflows/release.yml@main + with: + verify_branch: true + verify_version: true + secrets: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/components/riscv_vplic/.github/workflows/test.yml b/components/riscv_vplic/.github/workflows/test.yml index dc3b293d9..dc91940d4 100644 --- a/components/riscv_vplic/.github/workflows/test.yml +++ b/components/riscv_vplic/.github/workflows/test.yml @@ -1,3 +1,6 @@ +# Integration Test Workflow +# References shared workflow from axci + name: Test on: @@ -7,44 +10,8 @@ on: tags-ignore: - '**' pull_request: - workflow_call: + workflow_dispatch: jobs: - load-config: - name: Load CI Configuration - runs-on: ubuntu-latest - outputs: - targets: ${{ steps.config.outputs.targets }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Load configuration - id: config - run: | - TARGETS=$(jq -c '.targets' .github/config.json) - echo "targets=$TARGETS" >> $GITHUB_OUTPUT - test: - name: Test - runs-on: ubuntu-latest - needs: load-config - strategy: - fail-fast: false - matrix: - target: ${{ fromJson(needs.load-config.outputs.targets) }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - # - name: Run tests - # run: cargo test --target ${{ matrix.target }} --all-features -- --nocapture - - # - name: Run doc tests - # run: cargo test --target ${{ matrix.target }} --doc - - name: Run tests - run: echo "Tests are skipped!" + uses: arceos-hypervisor/axci/.github/workflows/test.yml@main \ No newline at end of file diff --git a/components/riscv_vplic/.gitignore b/components/riscv_vplic/.gitignore index 97e6d8325..3f3d87834 100644 --- a/components/riscv_vplic/.gitignore +++ b/components/riscv_vplic/.gitignore @@ -1,3 +1,11 @@ /target Cargo.lock .vscode/ + +# Test results (generated by shared test framework) +/test-results/ +/test_repos/ +*.log + +# Downloaded test framework +/scripts/.axci/ \ No newline at end of file diff --git a/components/riscv_vplic/Cargo.toml b/components/riscv_vplic/Cargo.toml index 181067472..b7ed2ffb2 100644 --- a/components/riscv_vplic/Cargo.toml +++ b/components/riscv_vplic/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "riscv_vplic" -version = "0.2.1" +version = "0.2.2" edition = "2021" authors = ["Jingyu liu "] description = "RISCV Virtual PLIC implementation." -documentation = "https://docs.rs/riscv_vplic" +documentation = "https://arceos-hypervisor.github.io/riscv_vplic" repository = "https://github.com/arceos-hypervisor/riscv_vplic" readme = "README.md" categories = ["os", "no-std"] @@ -15,19 +15,17 @@ license = "Apache-2.0" default = [] [dependencies] -axaddrspace = "0.1.4" -axdevice_base = "0.2.1" -axvisor_api = "0.1" +axaddrspace = "0.3" +axdevice_base = "0.2.2" +axvisor_api = "0.3" -axerrno = "0.1.0" +axerrno = "0.2" bitmaps = { version = "3.2", default-features = false } log = "0.4" -spin = "0.9" +spin = "0.10" -riscv-h = "0.1" +riscv-h = "0.2" [package.metadata.docs.rs] -# # 指定 docs.rs 默认显示的 target default-target = ["riscv64gc-unknown-none-elf"] -# 指定 docs.rs 使用的 target targets = ["riscv64gc-unknown-none-elf"] \ No newline at end of file diff --git a/components/riscv_vplic/README.md b/components/riscv_vplic/README.md index 5313e3eac..0e77cbf43 100644 --- a/components/riscv_vplic/README.md +++ b/components/riscv_vplic/README.md @@ -1,9 +1,160 @@ -# RISCV VPLIC - Virtual Platform-Level Interrupt Controller +

riscv_vplic

-A Virtual Platform-Level Interrupt Controller (VPLIC) implementation for RISC-V architecture, designed for the ArceOS hypervisor ecosystem. +

RISC-V Virtual Platform-Level Interrupt Controller

+ +
+ +[![Crates.io](https://img.shields.io/crates/v/riscv_vplic.svg)](https://crates.io/crates/riscv_vplic) +[![Docs.rs](https://docs.rs/riscv_vplic/badge.svg)](https://docs.rs/riscv_vplic) +[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/arceos-hypervisor/riscv_vplic/blob/main/LICENSE) + +
+ +English | [中文](README_CN.md) + +# Introduction + +RISC-V Virtual Platform-Level Interrupt Controller, providing a PLIC 1.0.0 compliant interrupt controller emulation for RISC-V Hypervisors. Supports `#![no_std]`, suitable for bare-metal and virtualization development. + +This crate exports the following core components: + +- **`VPlicGlobal`** — Virtual PLIC global controller, managing interrupt priorities, pending interrupts, and active interrupts +- **PLIC Constants** — PLIC 1.0.0 memory map constant definitions (priority, pending, enable, context control, etc.) + +## Key Features + +- PLIC 1.0.0 compliant memory map +- Interrupt priority, pending status, and enable management +- Context-based interrupt handling with Claim/Complete mechanism +- Integration with Hypervisor device emulation framework (implements `BaseDeviceOps` trait) + +# Quick Start + +### Prerequisites + +- Rust nightly toolchain +- Rust components: rust-src, clippy, rustfmt + +```bash +# Install rustup (if not installed) +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# Install nightly toolchain and components +rustup install nightly +rustup component add rust-src clippy rustfmt --toolchain nightly +``` + +### Running Checks and Tests + +```bash +# 1. Clone the repository +git clone https://github.com/arceos-hypervisor/riscv_vplic.git +cd riscv_vplic + +# 2. Code check (format check + clippy + build + doc generation) +./scripts/check.sh + +# 3. Run tests +# Run all tests (unit tests + integration tests) +./scripts/test.sh + +# Run unit tests only +./scripts/test.sh unit + +# Run integration tests only +./scripts/test.sh integration + +# List all available test suites +./scripts/test.sh list + +# Specify unit test target +./scripts/test.sh unit --unit-targets x86_64-unknown-linux-gnu +``` + +# Integration + +### Installation + +Add to your `Cargo.toml`: + +```toml +[dependencies] +riscv_vplic = "0.2.2" +``` + +### Usage Example + +```rust +use riscv_vplic::VPlicGlobal; +use axaddrspace::GuestPhysAddr; + +fn main() { + // Create a virtual PLIC with 2 contexts + let vplic = VPlicGlobal::new( + GuestPhysAddr::from(0x0c000000), + Some(0x4000), + 2 + ); + + // Access PLIC properties + println!("PLIC address: {:#x}", vplic.addr.as_usize()); + println!("PLIC size: {:#x}", vplic.size); + println!("Contexts: {}", vplic.contexts_num); + + // Check interrupt bitmap status + assert!(vplic.assigned_irqs.lock().is_empty()); + assert!(vplic.pending_irqs.lock().is_empty()); + assert!(vplic.active_irqs.lock().is_empty()); +} +``` + +### PLIC Memory Map Constants + +```rust +use riscv_vplic::*; + +// Number of interrupt sources (PLIC 1.0.0 defines 1024) +assert_eq!(PLIC_NUM_SOURCES, 1024); + +// Memory map offsets +assert_eq!(PLIC_PRIORITY_OFFSET, 0x000000); // Priority registers +assert_eq!(PLIC_PENDING_OFFSET, 0x001000); // Pending registers +assert_eq!(PLIC_ENABLE_OFFSET, 0x002000); // Enable registers +assert_eq!(PLIC_CONTEXT_CTRL_OFFSET, 0x200000); // Context control registers + +// Context strides +assert_eq!(PLIC_ENABLE_STRIDE, 0x80); // Enable region stride +assert_eq!(PLIC_CONTEXT_STRIDE, 0x1000); // Context stride + +// Context internal control offsets +assert_eq!(PLIC_CONTEXT_THRESHOLD_OFFSET, 0x00); // Threshold register +assert_eq!(PLIC_CONTEXT_CLAIM_COMPLETE_OFFSET, 0x04); // Claim/Complete register +``` + +### Documentation + +Generate and view API documentation: + +```bash +cargo doc --no-deps --open +``` + +Online documentation: [docs.rs/riscv_vplic](https://docs.rs/riscv_vplic) ## Related Projects - [axdevice_base](https://github.com/arceos-hypervisor/axdevice_base) - Basic device abstraction - [axvisor_api](https://github.com/arceos-hypervisor/axvisor_api) - Hypervisor API definitions - [axaddrspace](https://github.com/arceos-hypervisor/axaddrspace) - Address space management + +# Contributing + +1. Fork the repository and create a branch +2. Run local checks: `./scripts/check.sh` +3. Run local tests: `./scripts/test.sh` +4. Submit a PR and pass CI checks + +# License + +This project is licensed under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for details. diff --git a/components/riscv_vplic/README_CN.md b/components/riscv_vplic/README_CN.md new file mode 100644 index 000000000..595d47923 --- /dev/null +++ b/components/riscv_vplic/README_CN.md @@ -0,0 +1,160 @@ +

riscv_vplic

+ +

RISC-V 虚拟平台级中断控制器

+ +
+ +[![Crates.io](https://img.shields.io/crates/v/riscv_vplic.svg)](https://crates.io/crates/riscv_vplic) +[![Docs.rs](https://docs.rs/riscv_vplic/badge.svg)](https://docs.rs/riscv_vplic) +[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/arceos-hypervisor/riscv_vplic/blob/main/LICENSE) + +
+ +[English](README.md) | 中文 + +# 简介 + +RISC-V 虚拟平台级中断控制器(Virtual Platform-Level Interrupt Controller),为 RISC-V Hypervisor 提供符合 PLIC 1.0.0 规范的中断控制器模拟实现。支持 `#![no_std]`,可用于裸机和虚拟化开发。 + +本库导出以下核心内容: + +- **`VPlicGlobal`** — 虚拟 PLIC 全局控制器,管理中断优先级、待处理中断和活跃中断 +- **PLIC 常量** — PLIC 1.0.0 内存映射常量定义(优先级、待处理、使能、上下文控制等) + +## 主要特性 + +- 符合 PLIC 1.0.0 规范的内存映射 +- 支持中断优先级、待处理状态和使能管理 +- 基于 Context 的中断处理,支持 Claim/Complete 机制 +- 与 Hypervisor 设备模拟框架集成(实现 `BaseDeviceOps` trait) + +## 快速上手 + +### 环境要求 + +- Rust nightly 工具链 +- Rust 组件: rust-src, clippy, rustfmt + +```bash +# 安装 rustup(如未安装) +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# 安装 nightly 工具链及组件 +rustup install nightly +rustup component add rust-src clippy rustfmt --toolchain nightly +``` + +### 运行检查和测试 + +```bash +# 1. 克隆仓库 +git clone https://github.com/arceos-hypervisor/riscv_vplic.git +cd riscv_vplic + +# 2. 代码检查(格式检查 + clippy + 构建 + 文档生成) +./scripts/check.sh + +# 3. 运行测试 +# 运行全部测试(单元测试 + 集成测试) +./scripts/test.sh + +# 仅运行单元测试 +./scripts/test.sh unit + +# 仅运行集成测试 +./scripts/test.sh integration + +# 列出所有可用的测试套件 +./scripts/test.sh list + +# 指定单元测试目标 +./scripts/test.sh unit --unit-targets x86_64-unknown-linux-gnu +``` + +## 集成使用 + +### 安装 + +在 `Cargo.toml` 中添加: + +```toml +[dependencies] +riscv_vplic = "0.2.2" +``` + +### 使用示例 + +```rust +use riscv_vplic::VPlicGlobal; +use axaddrspace::GuestPhysAddr; + +fn main() { + // 创建一个支持 2 个上下文的虚拟 PLIC + let vplic = VPlicGlobal::new( + GuestPhysAddr::from(0x0c000000), + Some(0x4000), + 2 + ); + + // 访问 PLIC 属性 + println!("PLIC address: {:#x}", vplic.addr.as_usize()); + println!("PLIC size: {:#x}", vplic.size); + println!("Contexts: {}", vplic.contexts_num); + + // 检查中断位图状态 + assert!(vplic.assigned_irqs.lock().is_empty()); + assert!(vplic.pending_irqs.lock().is_empty()); + assert!(vplic.active_irqs.lock().is_empty()); +} +``` + +### PLIC 内存映射常量 + +```rust +use riscv_vplic::*; + +// 中断源数量(PLIC 1.0.0 定义为 1024) +assert_eq!(PLIC_NUM_SOURCES, 1024); + +// 内存映射偏移量 +assert_eq!(PLIC_PRIORITY_OFFSET, 0x000000); // 优先级寄存器 +assert_eq!(PLIC_PENDING_OFFSET, 0x001000); // 待处理寄存器 +assert_eq!(PLIC_ENABLE_OFFSET, 0x002000); // 使能寄存器 +assert_eq!(PLIC_CONTEXT_CTRL_OFFSET, 0x200000); // 上下文控制寄存器 + +// Context 间距 +assert_eq!(PLIC_ENABLE_STRIDE, 0x80); // 使能区域间距 +assert_eq!(PLIC_CONTEXT_STRIDE, 0x1000); // 上下文间距 + +// 上下文内部控制偏移 +assert_eq!(PLIC_CONTEXT_THRESHOLD_OFFSET, 0x00); // 阈值寄存器 +assert_eq!(PLIC_CONTEXT_CLAIM_COMPLETE_OFFSET, 0x04); // Claim/Complete 寄存器 +``` + +### 文档 + +生成并查看 API 文档: + +```bash +cargo doc --no-deps --open +``` + +在线文档:[docs.rs/riscv_vplic](https://docs.rs/riscv_vplic) + +## 相关项目 + +- [axdevice_base](https://github.com/arceos-hypervisor/axdevice_base) - 基础设备抽象 +- [axvisor_api](https://github.com/arceos-hypervisor/axvisor_api) - Hypervisor API 定义 +- [axaddrspace](https://github.com/arceos-hypervisor/axaddrspace) - 地址空间管理 + +# 贡献 + +1. Fork 仓库并创建分支 +2. 运行本地检查:`./scripts/check.sh` +3. 运行本地测试:`./scripts/test.sh` +4. 提交 PR 并通过 CI 检查 + +# 协议 + +本项目采用 Apache License, Version 2.0 许可证。详见 [LICENSE](LICENSE) 文件。 diff --git a/components/riscv_vplic/rust-toolchain.toml b/components/riscv_vplic/rust-toolchain.toml index 06e0e5b0f..8fff43970 100644 --- a/components/riscv_vplic/rust-toolchain.toml +++ b/components/riscv_vplic/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2025-05-20" +channel = "nightly-2026-03-18" components = ["rust-src", "llvm-tools", "rustfmt", "clippy"] profile = "minimal" targets = [ diff --git a/components/riscv_vplic/scripts/check.sh b/components/riscv_vplic/scripts/check.sh new file mode 100755 index 000000000..a7966f2d3 --- /dev/null +++ b/components/riscv_vplic/scripts/check.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# +# riscv_vplic 代码检查脚本 +# 下载并调用 axci 仓库中的检查脚本 +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +COMPONENT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +COMPONENT_NAME="$(basename "$COMPONENT_DIR")" +AXCI_DIR="${SCRIPT_DIR}/.axci" +AXCI_REPO="https://github.com/arceos-hypervisor/axci.git" + +# 下载或更新 axci 仓库 +download_axci() { + if [ -d "$AXCI_DIR" ]; then + echo "Updating axci repository..." + cd "$AXCI_DIR" && git pull --quiet + else + echo "Downloading axci repository..." + git clone --quiet "$AXCI_REPO" "$AXCI_DIR" + fi +} + +# 主函数 +main() { + download_axci + + # 在组件目录中运行检查 + cd "$COMPONENT_DIR" + exec bash "$AXCI_DIR/check.sh" --component-dir "$COMPONENT_DIR" "$@" +} + +main "$@" diff --git a/components/riscv_vplic/scripts/test.sh b/components/riscv_vplic/scripts/test.sh new file mode 100755 index 000000000..94b4c1f72 --- /dev/null +++ b/components/riscv_vplic/scripts/test.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# +# riscv_vplic 测试脚本 +# 下载并调用 axci 仓库中的测试框架 +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +COMPONENT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +AXCI_DIR="${SCRIPT_DIR}/.axci" +AXCI_REPO="https://github.com/arceos-hypervisor/axci.git" + +# 下载或更新 axci 仓库 +download_axci() { + if [ -d "$AXCI_DIR" ]; then + echo "Updating axci repository..." + cd "$AXCI_DIR" && git pull --quiet + else + echo "Downloading axci repository..." + git clone --quiet "$AXCI_REPO" "$AXCI_DIR" + fi +} + +# 主函数 +main() { + download_axci + + # 在组件目录中运行测试,自动指定当前组件 + cd "$COMPONENT_DIR" + exec bash "$AXCI_DIR/tests.sh" --component-dir "$COMPONENT_DIR" "$@" +} + +main "$@" diff --git a/components/riscv_vplic/src/devops_impl.rs b/components/riscv_vplic/src/devops_impl.rs index 4a66ace51..22503c170 100644 --- a/components/riscv_vplic/src/devops_impl.rs +++ b/components/riscv_vplic/src/devops_impl.rs @@ -54,7 +54,7 @@ impl BaseDeviceOps for VPlicGlobal { // threshold offset if offset >= PLIC_CONTEXT_CTRL_OFFSET - && (offset - PLIC_CONTEXT_CTRL_OFFSET) % PLIC_CONTEXT_STRIDE == 0 => + && (offset - PLIC_CONTEXT_CTRL_OFFSET).is_multiple_of(PLIC_CONTEXT_STRIDE) => { perform_mmio_read(host_addr, width) } @@ -62,8 +62,7 @@ impl BaseDeviceOps for VPlicGlobal { offset if offset >= PLIC_CONTEXT_CTRL_OFFSET && (offset - PLIC_CONTEXT_CTRL_OFFSET - PLIC_CONTEXT_CLAIM_COMPLETE_OFFSET) - % PLIC_CONTEXT_STRIDE - == 0 => + .is_multiple_of(PLIC_CONTEXT_STRIDE) => { let context_id = (offset - PLIC_CONTEXT_CTRL_OFFSET - PLIC_CONTEXT_CLAIM_COMPLETE_OFFSET) @@ -144,7 +143,7 @@ impl BaseDeviceOps for VPlicGlobal { // threshold offset if offset >= PLIC_CONTEXT_CTRL_OFFSET - && (offset - PLIC_CONTEXT_CTRL_OFFSET) % PLIC_CONTEXT_STRIDE == 0 => + && (offset - PLIC_CONTEXT_CTRL_OFFSET).is_multiple_of(PLIC_CONTEXT_STRIDE) => { perform_mmio_write(host_addr, width, val) } @@ -152,8 +151,7 @@ impl BaseDeviceOps for VPlicGlobal { offset if offset >= PLIC_CONTEXT_CTRL_OFFSET && (offset - PLIC_CONTEXT_CTRL_OFFSET - PLIC_CONTEXT_CLAIM_COMPLETE_OFFSET) - % PLIC_CONTEXT_STRIDE - == 0 => + .is_multiple_of(PLIC_CONTEXT_STRIDE) => { // info!("vPlicGlobal: Writing to CLAIM/COMPLETE reg {reg:#x} val {val:#x}"); let context_id = diff --git a/components/riscv_vplic/tests/consts_tests.rs b/components/riscv_vplic/tests/consts_tests.rs new file mode 100644 index 000000000..31f1f6e48 --- /dev/null +++ b/components/riscv_vplic/tests/consts_tests.rs @@ -0,0 +1,46 @@ +use riscv_vplic::*; + +#[test] +fn test_plic_num_sources() { + assert_eq!(PLIC_NUM_SOURCES, 1024); +} + +#[test] +fn test_plic_priority_offset() { + assert_eq!(PLIC_PRIORITY_OFFSET, 0x000000); +} + +#[test] +fn test_plic_pending_offset() { + assert_eq!(PLIC_PENDING_OFFSET, 0x001000); +} + +#[test] +fn test_plic_enable_offset() { + assert_eq!(PLIC_ENABLE_OFFSET, 0x002000); +} + +#[test] +fn test_plic_enable_stride() { + assert_eq!(PLIC_ENABLE_STRIDE, 0x80); +} + +#[test] +fn test_plic_context_ctrl_offset() { + assert_eq!(PLIC_CONTEXT_CTRL_OFFSET, 0x200000); +} + +#[test] +fn test_plic_context_stride() { + assert_eq!(PLIC_CONTEXT_STRIDE, 0x1000); +} + +#[test] +fn test_plic_context_threshold_offset() { + assert_eq!(PLIC_CONTEXT_THRESHOLD_OFFSET, 0x00); +} + +#[test] +fn test_plic_context_claim_complete_offset() { + assert_eq!(PLIC_CONTEXT_CLAIM_COMPLETE_OFFSET, 0x04); +} diff --git a/components/riscv_vplic/tests/vplic_tests.rs b/components/riscv_vplic/tests/vplic_tests.rs new file mode 100644 index 000000000..0aa9b03c0 --- /dev/null +++ b/components/riscv_vplic/tests/vplic_tests.rs @@ -0,0 +1,67 @@ +use axaddrspace::GuestPhysAddr; +use riscv_vplic::{ + PLIC_CONTEXT_CLAIM_COMPLETE_OFFSET, PLIC_CONTEXT_CTRL_OFFSET, PLIC_CONTEXT_STRIDE, VPlicGlobal, +}; + +/// Calculate minimum required size for VPlicGlobal with given contexts +fn calculate_min_size(contexts_num: usize) -> usize { + contexts_num * PLIC_CONTEXT_STRIDE + + PLIC_CONTEXT_CTRL_OFFSET + + PLIC_CONTEXT_CLAIM_COMPLETE_OFFSET + + 0x1000 +} + +#[test] +fn test_vplic_global_creation() { + let addr = GuestPhysAddr::from(0x0c000000); + let contexts_num = 2; + let size = calculate_min_size(contexts_num); + + let vplic = VPlicGlobal::new(addr, Some(size), contexts_num); + + assert_eq!(vplic.addr, addr); + assert_eq!(vplic.size, size); + assert_eq!(vplic.contexts_num, contexts_num); +} + +#[test] +fn test_vplic_global_with_different_contexts() { + let addr = GuestPhysAddr::from(0x0c000000); + + // Test with 1 context + let vplic = VPlicGlobal::new(addr, Some(0x400000), 1); + assert_eq!(vplic.contexts_num, 1); + + // Test with 4 contexts + let vplic = VPlicGlobal::new(addr, Some(0x400000), 4); + assert_eq!(vplic.contexts_num, 4); + + // Test with 8 contexts + let vplic = VPlicGlobal::new(addr, Some(0x400000), 8); + assert_eq!(vplic.contexts_num, 8); +} + +#[test] +#[should_panic(expected = "Size must be specified")] +fn test_vplic_global_size_none_panics() { + let addr = GuestPhysAddr::from(0x0c000000); + let _ = VPlicGlobal::new(addr, None, 2); +} + +#[test] +#[should_panic(expected = "exceeds region")] +fn test_vplic_global_insufficient_size_panics() { + let addr = GuestPhysAddr::from(0x0c000000); + // Size too small for 2 contexts + let _ = VPlicGlobal::new(addr, Some(0x1000), 2); +} + +#[test] +fn test_vplic_global_bitmaps_initialized_empty() { + let addr = GuestPhysAddr::from(0x0c000000); + let vplic = VPlicGlobal::new(addr, Some(0x400000), 2); + + assert!(vplic.assigned_irqs.lock().is_empty()); + assert!(vplic.pending_irqs.lock().is_empty()); + assert!(vplic.active_irqs.lock().is_empty()); +} diff --git a/components/x86_vcpu/.github/workflows/push.yml b/components/x86_vcpu/.github/workflows/push.yml new file mode 100644 index 000000000..a18de4a04 --- /dev/null +++ b/components/x86_vcpu/.github/workflows/push.yml @@ -0,0 +1,147 @@ +# ═══════════════════════════════════════════════════════════════════════════════ +# 组件仓库 GitHub Actions 配置模板 +# ═══════════════════════════════════════════════════════════════════════════════ +# +# 此文件用于子仓库,当子仓库有更新时通知主仓库进行 subtree pull 同步。 +# +# 【使用步骤】 +# ───────────────────────────────────────────────────────────────────────────── +# 1. 将此文件复制到子仓库的 .github/workflows/ 目录: +# cp scripts/push.yml <子仓库>/.github/workflows/push.yml +# +# 2. 在子仓库中配置 Secret: +# GitHub 仓库 → Settings → Secrets → Actions → New repository secret +# 名称: PARENT_REPO_TOKEN +# 值: 具有主仓库 repo 权限的 Personal Access Token +# +# 3. 修改下方 env 块中的一个变量(标注了「需要修改」的行): +# PARENT_REPO - 主仓库路径,例如 rcore-os/tgoskits +# (subtree 目录由主仓库自动从 git 历史中推断,无需手动指定) +# +# 【Token 权限要求】 +# ───────────────────────────────────────────────────────────────────────────── +# PARENT_REPO_TOKEN 需要 Classic Personal Access Token,权限包括: +# - repo (Full control of private repositories) +# 或 +# - Fine-grained token: Contents (Read and Write) +# +# 【触发条件】 +# ───────────────────────────────────────────────────────────────────────────── +# - 自动触发:推送到 dev 或 main 分支时 +# - 手动触发:Actions → Notify Parent Repository → Run workflow +# +# 【工作流程】 +# ───────────────────────────────────────────────────────────────────────────── +# 子仓库 push → 触发此工作流 → 调用主仓库 API → 主仓库 subtree pull +# +# 【注意事项】 +# ───────────────────────────────────────────────────────────────────────────── +# - 主仓库需要配置接收 repository_dispatch 事件的同步工作流 +# - 如果不需要子仓库到主仓库的同步,可以不使用此文件 +# +# ═══════════════════════════════════════════════════════════════════════════════ + +name: Notify Parent Repository + +# 当有新的推送时触发 +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Get repository info + id: repo + env: + GH_REPO_NAME: ${{ github.event.repository.name }} + GH_REF_NAME: ${{ github.ref_name }} + GH_SERVER_URL: ${{ github.server_url }} + GH_REPOSITORY: ${{ github.repository }} + run: | + # 直接使用 GitHub Actions 内置变量,通过 env 传入避免 shell 注入 + COMPONENT="$GH_REPO_NAME" + BRANCH="$GH_REF_NAME" + # 构造标准 HTTPS URL,供主仓库按 URL 精确匹配 repos.list + REPO_URL="${GH_SERVER_URL}/${GH_REPOSITORY}" + + echo "component=${COMPONENT}" >> $GITHUB_OUTPUT + echo "branch=${BRANCH}" >> $GITHUB_OUTPUT + echo "repo_url=${REPO_URL}" >> $GITHUB_OUTPUT + + echo "Component: ${COMPONENT}" + echo "Branch: ${BRANCH}" + echo "Repo URL: ${REPO_URL}" + + - name: Notify parent repository + env: + # ── 需要修改 ────────────────────────────────────────────────────────── + PARENT_REPO: "rcore-os/tgoskits" # 主仓库路径 + # ── 无需修改 ────────────────────────────────────────────────────────── + DISPATCH_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} + # 将用户可控内容通过 env 传入,避免直接插值到 shell 脚本 + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + GIT_ACTOR: ${{ github.actor }} + GIT_SHA: ${{ github.sha }} + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "Notifying parent repository about update in ${COMPONENT}:${BRANCH}" + + # 使用 jq 安全构建 JSON,避免 commit message 中任何特殊字符导致注入 + PAYLOAD=$(jq -n \ + --arg component "$COMPONENT" \ + --arg branch "$BRANCH" \ + --arg repo_url "$REPO_URL" \ + --arg commit "$GIT_SHA" \ + --arg message "$COMMIT_MESSAGE" \ + --arg author "$GIT_ACTOR" \ + '{ + event_type: "subtree-update", + client_payload: { + component: $component, + branch: $branch, + repo_url: $repo_url, + commit: $commit, + message: $message, + author: $author + } + }') + + curl --fail --show-error -X POST \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${DISPATCH_TOKEN}" \ + https://api.github.com/repos/${PARENT_REPO}/dispatches \ + -d "$PAYLOAD" + + echo "Notification sent successfully" + + - name: Create summary + env: + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + GIT_SHA: ${{ github.sha }} + GIT_ACTOR: ${{ github.actor }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "## Notification Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Component**: ${COMPONENT}" >> $GITHUB_STEP_SUMMARY + echo "- **Branch**: ${BRANCH}" >> $GITHUB_STEP_SUMMARY + echo "- **Repo URL**: ${REPO_URL}" >> $GITHUB_STEP_SUMMARY + echo "- **Commit**: \`${GIT_SHA}\`" >> $GITHUB_STEP_SUMMARY + echo "- **Author**: ${GIT_ACTOR}" >> $GITHUB_STEP_SUMMARY + echo "- **Status**: ✅ Notification sent" >> $GITHUB_STEP_SUMMARY diff --git a/components/x86_vcpu/Cargo.toml b/components/x86_vcpu/Cargo.toml index 93cd1ede5..07a59a54c 100644 --- a/components/x86_vcpu/Cargo.toml +++ b/components/x86_vcpu/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "x86_vcpu" -version = "0.2.2" +version = "0.3.0" edition = "2024" authors = [ "Mingxian Su ", @@ -23,18 +23,18 @@ x86_64 = "0.15" raw-cpuid = "11.0" numeric-enum-macro = "0.2" -axerrno = "0.1.0" +axerrno = "0.2.0" page_table_entry = "0.6" memory_addr = "0.4" -crate_interface = "0.1" +crate_interface = "0.3" -axaddrspace = "0.1.4" -axvcpu = "0.2" -x86_vlapic = "0.2.1" -axdevice_base = "0.2.1" -axvisor_api = "0.1.0" +axaddrspace = "0.3" +axvcpu = "0.3" +x86_vlapic = "0.2.2" +axdevice_base = "0.2.2" +axvisor_api = "0.3" -spin = { version = "0.9", default-features = false } +spin = { version = "0.10", default-features = false } [features] default = ["vmx"] diff --git a/components/x86_vcpu/rust-toolchain.toml b/components/x86_vcpu/rust-toolchain.toml index 899aca3bd..542c57474 100644 --- a/components/x86_vcpu/rust-toolchain.toml +++ b/components/x86_vcpu/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] profile = "minimal" -channel = "nightly-2025-05-20" +channel = "nightly-2026-02-25" components = ["rust-src", "llvm-tools", "rustfmt", "clippy"] targets = [ "x86_64-unknown-none", diff --git a/components/x86_vcpu/src/test_utils.rs b/components/x86_vcpu/src/test_utils.rs index 237b469a3..2ca3fa5cb 100644 --- a/components/x86_vcpu/src/test_utils.rs +++ b/components/x86_vcpu/src/test_utils.rs @@ -14,12 +14,10 @@ #[cfg(test)] pub mod mock { - use axaddrspace::AxMmHal; + use axvisor_api::{api_impl, memory::MemoryIf}; + use memory_addr::{PhysAddr, VirtAddr}; use spin::Mutex; - #[derive(Debug)] - pub struct MockMmHal; - static GLOBAL_LOCK: Mutex = Mutex::new(MockMmHalState::new()); // State for the mock memory allocator @@ -40,9 +38,13 @@ pub mod mock { } } - impl AxMmHal for MockMmHal { - // Allocate a frame of memory - fn alloc_frame() -> Option { + #[derive(Debug)] + pub struct MockMmHal; + + #[api_impl] + impl MemoryIf for MockMmHal { + /// Allocate a frame. + fn alloc_frame() -> Option { let mut state = GLOBAL_LOCK.lock(); for i in 0..16 { @@ -56,8 +58,16 @@ pub mod mock { None } - // Deallocate a frame - fn dealloc_frame(paddr: memory_addr::PhysAddr) { + /// Allocate a number of contiguous frames, with a specified alignment. + fn alloc_contiguous_frames( + _num_frames: usize, + _frame_align_pow2: usize, + ) -> Option { + unimplemented!() + } + + /// Deallocate a frame allocated previously by [`alloc_frame`]. + fn dealloc_frame(paddr: PhysAddr) { let mut state = GLOBAL_LOCK.lock(); let addr = paddr.as_usize(); @@ -68,8 +78,14 @@ pub mod mock { } } - // Convert physical address to virtual address - fn phys_to_virt(paddr: memory_addr::PhysAddr) -> memory_addr::VirtAddr { + /// Deallocate a number of contiguous frames allocated previously by + /// [`alloc_contiguous_frames`]. + fn dealloc_contiguous_frames(_first_addr: PhysAddr, _num_frames: usize) { + unimplemented!() + } + + /// Convert a physical address to a virtual address. + fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { let state = GLOBAL_LOCK.lock(); let addr = paddr.as_usize(); @@ -84,8 +100,8 @@ pub mod mock { } } - // Convert virtual address to physical address - fn virt_to_phys(vaddr: memory_addr::VirtAddr) -> memory_addr::PhysAddr { + /// Convert a virtual address to a physical address. + fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr { let state = GLOBAL_LOCK.lock(); let pool_start = state.memory_pool.as_ptr() as usize; @@ -139,19 +155,12 @@ pub mod mock { state.reset_counter } } - - #[derive(Debug)] - pub struct MockVCpuHal; - - impl axvcpu::AxVCpuHal for MockVCpuHal { - type MmHal = MockMmHal; - } } #[cfg(test)] mod tests { use crate::test_utils::mock::MockMmHal; - use axaddrspace::AxMmHal; + use axvisor_api::memory::MemoryIf; #[test] fn test_mock_allocator() { diff --git a/components/x86_vcpu/src/vmx/percpu.rs b/components/x86_vcpu/src/vmx/percpu.rs index f382fe358..0c290d4b7 100644 --- a/components/x86_vcpu/src/vmx/percpu.rs +++ b/components/x86_vcpu/src/vmx/percpu.rs @@ -16,7 +16,7 @@ use x86::bits64::vmx; use x86_64::registers::control::{Cr0, Cr4, Cr4Flags}; use axerrno::{AxResult, ax_err, ax_err_type}; -use axvcpu::{AxArchPerCpu, AxVCpuHal}; +use axvcpu::AxArchPerCpu; use memory_addr::PAGE_SIZE_4K as PAGE_SIZE; use crate::msr::Msr; @@ -29,7 +29,7 @@ use crate::vmx::structs::{FeatureControl, FeatureControlFlags, VmxBasic, VmxRegi /// when operating in VMX mode, including the VMCS revision identifier and /// the VMX region. #[derive(Debug)] -pub struct VmxPerCpuState { +pub struct VmxPerCpuState { /// The VMCS (Virtual Machine Control Structure) revision identifier. /// /// This identifier is used to ensure compatibility between the software @@ -40,10 +40,10 @@ pub struct VmxPerCpuState { /// /// This region typically contains the VMCS and other state information /// required for managing virtual machines on this particular CPU. - vmx_region: VmxRegion, + vmx_region: VmxRegion, } -impl AxArchPerCpu for VmxPerCpuState { +impl AxArchPerCpu for VmxPerCpuState { fn new(_cpu_id: usize) -> AxResult { Ok(Self { vmcs_revision_id: 0, @@ -159,14 +159,14 @@ impl AxArchPerCpu for VmxPerCpuState { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::mock::{MockMmHal, MockVCpuHal}; + use crate::test_utils::mock::MockMmHal; use alloc::format; use alloc::vec::Vec; #[test] fn test_vmx_per_cpu_state_new() { MockMmHal::reset(); // Reset before test - let result = VmxPerCpuState::::new(0); + let result = VmxPerCpuState::new(0); assert!(result.is_ok()); let state = result.unwrap(); @@ -176,7 +176,7 @@ mod tests { #[test] fn test_vmx_per_cpu_state_default_values() { MockMmHal::reset(); // Reset before test - let state = VmxPerCpuState::::new(0).unwrap(); + let state = VmxPerCpuState::new(0).unwrap(); // Test that vmcs_revision_id is initialized to 0 assert_eq!(state.vmcs_revision_id, 0); @@ -193,7 +193,7 @@ mod tests { // Create states for multiple CPUs for cpu_id in 0..4 { - let state = VmxPerCpuState::::new(cpu_id).unwrap(); + let state = VmxPerCpuState::new(cpu_id).unwrap(); states.push(state); } @@ -211,7 +211,7 @@ mod tests { #[test] fn test_vmx_per_cpu_state_debug() { MockMmHal::reset(); // Reset before test - let state = VmxPerCpuState::::new(0).unwrap(); + let state = VmxPerCpuState::new(0).unwrap(); // Test that Debug trait is implemented and doesn't panic let debug_str = format!("{:?}", state); @@ -223,7 +223,7 @@ mod tests { use core::mem; // Test that the struct has a reasonable size - let size = mem::size_of::>(); + let size = mem::size_of::(); // Should be larger than just the u32 field due to the VmxRegion assert!(size > 4); diff --git a/components/x86_vcpu/src/vmx/structs.rs b/components/x86_vcpu/src/vmx/structs.rs index fef13f968..d949d2429 100644 --- a/components/x86_vcpu/src/vmx/structs.rs +++ b/components/x86_vcpu/src/vmx/structs.rs @@ -17,18 +17,19 @@ use bitflags::bitflags; use memory_addr::PAGE_SIZE_4K as PAGE_SIZE; -use axaddrspace::{AxMmHal, HostPhysAddr, PhysFrame}; +use axaddrspace::HostPhysAddr; use axerrno::AxResult; +use axvisor_api::memory::PhysFrame; use crate::msr::{Msr, MsrReadWrite}; /// VMCS/VMXON region in 4K size. (SDM Vol. 3C, Section 24.2) #[derive(Debug)] -pub struct VmxRegion { - frame: PhysFrame, +pub struct VmxRegion { + frame: PhysFrame, } -impl VmxRegion { +impl VmxRegion { pub const unsafe fn uninit() -> Self { Self { frame: unsafe { PhysFrame::uninit() }, @@ -55,12 +56,12 @@ impl VmxRegion { // I/O bitmap A contains one bit for each I/O port in the range 0000H through 7FFFH; // I/O bitmap B contains bits for ports in the range 8000H through FFFFH. #[derive(Debug)] -pub struct IOBitmap { - io_bitmap_a_frame: PhysFrame, - io_bitmap_b_frame: PhysFrame, +pub struct IOBitmap { + io_bitmap_a_frame: PhysFrame, + io_bitmap_b_frame: PhysFrame, } -impl IOBitmap { +impl IOBitmap { pub fn passthrough_all() -> AxResult { Ok(Self { io_bitmap_a_frame: PhysFrame::alloc_zero()?, @@ -115,11 +116,11 @@ impl IOBitmap { } #[derive(Debug)] -pub struct MsrBitmap { - frame: PhysFrame, +pub struct MsrBitmap { + frame: PhysFrame, } -impl MsrBitmap { +impl MsrBitmap { pub fn passthrough_all() -> AxResult { Ok(Self { frame: PhysFrame::alloc_zero()?, @@ -291,7 +292,7 @@ mod tests { #[test] fn test_vmx_region_uninit() { - let region = unsafe { VmxRegion::::uninit() }; + let region = unsafe { VmxRegion::uninit() }; // Test that we can create an uninitialized region // Can't test much more without allocating memory @@ -305,7 +306,7 @@ mod tests { MockMmHal::reset(); // Test VmxRegion::new with valid parameters - let region = VmxRegion::::new(0x12345, false); + let region = VmxRegion::new(0x12345, false); assert!(region.is_ok()); let region = region.unwrap(); @@ -321,10 +322,10 @@ mod tests { MockMmHal::reset(); // Test VmxRegion::new with different shadow indicator values - let region_no_shadow = VmxRegion::::new(0x12345, false); + let region_no_shadow = VmxRegion::new(0x12345, false); assert!(region_no_shadow.is_ok()); - let region_with_shadow = VmxRegion::::new(0x12345, true); + let region_with_shadow = VmxRegion::new(0x12345, true); assert!(region_with_shadow.is_ok()); // Test that both regions have valid physical addresses @@ -347,11 +348,11 @@ mod tests { MockMmHal::reset(); // Test passthrough_all creation - let passthrough_bitmap = IOBitmap::::passthrough_all(); + let passthrough_bitmap = IOBitmap::passthrough_all(); assert!(passthrough_bitmap.is_ok()); // Test intercept_all creation - let intercept_bitmap = IOBitmap::::intercept_all(); + let intercept_bitmap = IOBitmap::intercept_all(); assert!(intercept_bitmap.is_ok()); // Test that phys_addr returns valid addresses @@ -368,11 +369,11 @@ mod tests { MockMmHal::reset(); // Test passthrough_all creation - let passthrough_bitmap = MsrBitmap::::passthrough_all(); + let passthrough_bitmap = MsrBitmap::passthrough_all(); assert!(passthrough_bitmap.is_ok()); // Test intercept_all creation - let intercept_bitmap = MsrBitmap::::intercept_all(); + let intercept_bitmap = MsrBitmap::intercept_all(); assert!(intercept_bitmap.is_ok()); // Test that phys_addr returns valid addresses @@ -466,13 +467,13 @@ mod tests { #[test] fn test_debug_implementations() { // Test that all our structs implement Debug properly - let vmx_region = unsafe { VmxRegion::::uninit() }; + let vmx_region = unsafe { VmxRegion::uninit() }; let _debug_str = format!("{:?}", vmx_region); - let io_bitmap = IOBitmap::::passthrough_all().unwrap(); + let io_bitmap = IOBitmap::passthrough_all().unwrap(); let _debug_str = format!("{:?}", io_bitmap); - let msr_bitmap = MsrBitmap::::passthrough_all().unwrap(); + let msr_bitmap = MsrBitmap::passthrough_all().unwrap(); let _debug_str = format!("{:?}", msr_bitmap); let flags = FeatureControlFlags::LOCKED; diff --git a/components/x86_vcpu/src/vmx/vcpu.rs b/components/x86_vcpu/src/vmx/vcpu.rs index bff610096..531600c03 100644 --- a/components/x86_vcpu/src/vmx/vcpu.rs +++ b/components/x86_vcpu/src/vmx/vcpu.rs @@ -35,7 +35,7 @@ use axaddrspace::{ }; use axdevice_base::BaseDeviceOps; use axerrno::{AxResult, ax_err, ax_err_type}; -use axvcpu::{AxArchVCpu, AxVCpuExitReason, AxVCpuHal}; +use axvcpu::{AxArchVCpu, AxVCpuExitReason}; use axvisor_api::vmm::{VCpuId, VMId}; use super::VmxExitInfo; @@ -167,7 +167,7 @@ const CR0_PE: usize = 1 << 0; /// A virtual CPU within a guest. #[repr(C)] -pub struct VmxVcpu { +pub struct VmxVcpu { // The order of `guest_regs` and `host_stack_top` is mandatory. They must be the first two fields. If you want to // change the order or the type of these fields, you must also change the assembly in this file. /// Guest general-purpose registers. @@ -189,11 +189,11 @@ pub struct VmxVcpu { // VMCS-related fields /// The VMCS region. - vmcs: VmxRegion, + vmcs: VmxRegion, /// The I/O bitmap for the VMCS. - io_bitmap: IOBitmap, + io_bitmap: IOBitmap, /// The MSR bitmap for the VMCS. - msr_bitmap: MsrBitmap, + msr_bitmap: MsrBitmap, // Interrupt-related fields /// Pending events to be injected to the guest. @@ -211,7 +211,7 @@ pub struct VmxVcpu { guest_regs_exiting: GeneralRegisters, } -impl VmxVcpu { +impl VmxVcpu { /// Create a new [`VmxVcpu`]. pub fn new(vm_id: VMId, vcpu_id: VCpuId) -> AxResult { let vmcs_revision_id = super::read_vmcs_revision_id(); @@ -509,7 +509,7 @@ impl VmxVcpu { } // Implementation of private methods -impl VmxVcpu { +impl VmxVcpu { fn setup_io_bitmap(&mut self) -> AxResult { // By default, I/O bitmap is set as `intercept_all`. // Todo: these should be combined with emulated pio device management, @@ -797,7 +797,7 @@ impl VmxVcpu { // Implementaton for type1.5 hypervisor // #[cfg(feature = "type1_5")] -impl VmxVcpu { +impl VmxVcpu { fn set_cr(&mut self, cr_idx: usize, val: u64) { (|| -> AxResult { // debug!("set guest CR{} to val {:#x}", cr_idx, val); @@ -871,7 +871,7 @@ macro_rules! vmx_entry_with { } } -impl VmxVcpu { +impl VmxVcpu { #[unsafe(naked)] /// Enter guest with vmlaunch. /// @@ -1230,7 +1230,7 @@ impl VmxVcpu { } } -impl Drop for VmxVcpu { +impl Drop for VmxVcpu { fn drop(&mut self) { unsafe { vmx::vmclear(self.vmcs.phys_addr().as_usize() as u64).unwrap() }; info!("[HV] dropped VmxVcpu(vmcs: {:#x})", self.vmcs.phys_addr()); @@ -1253,7 +1253,7 @@ fn get_tr_base(tr: SegmentSelector, gdt: &DescriptorTablePointer) -> u64 { } } -impl Debug for VmxVcpu { +impl Debug for VmxVcpu { fn fmt(&self, f: &mut Formatter) -> Result { (|| -> AxResult { Ok(f.debug_struct("VmxVcpu") @@ -1274,7 +1274,7 @@ impl Debug for VmxVcpu { } } -impl AxArchVCpu for VmxVcpu { +impl AxArchVCpu for VmxVcpu { type CreateConfig = (); type SetupConfig = (); diff --git a/components/x86_vlapic/.github/config.json b/components/x86_vlapic/.github/config.json new file mode 100644 index 000000000..12a9fab08 --- /dev/null +++ b/components/x86_vlapic/.github/config.json @@ -0,0 +1,10 @@ +{ + "targets": [ + "x86_64-unknown-none" + ], + "rust_components": [ + "rust-src", + "clippy", + "rustfmt" + ] +} diff --git a/components/x86_vlapic/.github/workflows/check.yml b/components/x86_vlapic/.github/workflows/check.yml new file mode 100644 index 000000000..330fa15e9 --- /dev/null +++ b/components/x86_vlapic/.github/workflows/check.yml @@ -0,0 +1,66 @@ +name: Quality Checks + +on: + push: + branches: + - '**' + tags-ignore: + - '**' + pull_request: + workflow_call: + +jobs: + load-config: + name: Load CI Configuration + runs-on: ubuntu-latest + outputs: + targets: ${{ steps.config.outputs.targets }} + rust_components: ${{ steps.config.outputs.rust_components }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Load configuration + id: config + run: | + TARGETS=$(jq -c '.targets' .github/config.json) + COMPONENTS=$(jq -r '.rust_components | join(", ")' .github/config.json) + + echo "targets=$TARGETS" >> $GITHUB_OUTPUT + echo "rust_components=$COMPONENTS" >> $GITHUB_OUTPUT + + check: + name: Check + runs-on: ubuntu-latest + needs: load-config + strategy: + fail-fast: false + matrix: + target: ${{ fromJson(needs.load-config.outputs.targets) }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@nightly + with: + components: ${{ needs.load-config.outputs.rust_components }} + targets: ${{ matrix.target }} + + - name: Check rust version + run: rustc --version --verbose + + - name: Check code format + run: cargo fmt --all -- --check + + - name: Build + run: cargo build --target ${{ matrix.target }} --all-features + + - name: Run clippy + run: cargo clippy --target ${{ matrix.target }} --all-features -- -D warnings + + - name: Build documentation + env: + RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs + run: cargo doc --no-deps --target ${{ matrix.target }} --all-features diff --git a/components/x86_vlapic/.github/workflows/deploy.yml b/components/x86_vlapic/.github/workflows/deploy.yml new file mode 100644 index 000000000..fda9a323d --- /dev/null +++ b/components/x86_vlapic/.github/workflows/deploy.yml @@ -0,0 +1,126 @@ +name: Deploy + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: 'pages' + cancel-in-progress: false + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 + +jobs: + verify-tag: + name: Verify Tag + runs-on: ubuntu-latest + outputs: + should_deploy: ${{ steps.check.outputs.should_deploy }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check if tag is on main or master branch + id: check + run: | + git fetch origin main master || true + BRANCHES=$(git branch -r --contains ${{ github.ref }}) + + if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then + echo "✓ Tag is on main or master branch" + echo "should_deploy=true" >> $GITHUB_OUTPUT + else + echo "✗ Tag is not on main or master branch, skipping deployment" + echo "Tag is on: $BRANCHES" + echo "should_deploy=false" >> $GITHUB_OUTPUT + fi + + - name: Verify version consistency + if: steps.check.outputs.should_deploy == 'true' + run: | + # Extract version from git tag (remove 'v' prefix) + TAG_VERSION="${{ github.ref_name }}" + TAG_VERSION="${TAG_VERSION#v}" + # Extract version from Cargo.toml + CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') + echo "Git tag version: $TAG_VERSION" + echo "Cargo.toml version: $CARGO_VERSION" + if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then + echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" + exit 1 + fi + echo "✓ Version check passed!" + + check: + uses: ./.github/workflows/check.yml + needs: verify-tag + if: needs.verify-tag.outputs.should_deploy == 'true' + + test: + uses: ./.github/workflows/test.yml + needs: verify-tag + if: needs.verify-tag.outputs.should_deploy == 'true' + + build: + name: Build documentation + runs-on: ubuntu-latest + needs: [verify-tag, check, test] + if: needs.verify-tag.outputs.should_deploy == 'true' + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@nightly + + - name: Build docs + env: + RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs + run: | + # Build documentation + cargo doc --no-deps --all-features + + # Auto-detect documentation directory + # Check if doc exists in target/doc or target/*/doc + if [ -d "target/doc" ]; then + DOC_DIR="target/doc" + else + # Find doc directory under target/*/doc pattern + DOC_DIR=$(find target -type d -name doc -path "target/*/doc" | head -n 1) + if [ -z "$DOC_DIR" ]; then + echo "Error: Could not find documentation directory" + exit 1 + fi + fi + + echo "Documentation found in: $DOC_DIR" + printf '' $(cargo tree | head -1 | cut -d' ' -f1) > "${DOC_DIR}/index.html" + echo "DOC_DIR=${DOC_DIR}" >> $GITHUB_ENV + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ${{ env.DOC_DIR }} + + deploy: + name: Deploy to GitHub Pages + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: [verify-tag, build] + if: needs.verify-tag.outputs.should_deploy == 'true' + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/components/x86_vlapic/.github/workflows/release.yml b/components/x86_vlapic/.github/workflows/release.yml new file mode 100644 index 000000000..2e857b48b --- /dev/null +++ b/components/x86_vlapic/.github/workflows/release.yml @@ -0,0 +1,160 @@ +name: Release + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + - 'v[0-9]+.[0-9]+.[0-9]+-pre.[0-9]+' + +permissions: + contents: write + +jobs: + verify-tag: + name: Verify Tag + runs-on: ubuntu-latest + outputs: + should_release: ${{ steps.check.outputs.should_release }} + is_prerelease: ${{ steps.check.outputs.is_prerelease }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check tag type and branch + id: check + run: | + git fetch origin main master dev || true + + TAG="${{ github.ref_name }}" + BRANCHES=$(git branch -r --contains ${{ github.ref }}) + + echo "Tag: $TAG" + echo "Branches containing this tag: $BRANCHES" + + # Check if it's a prerelease tag + if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-pre\.[0-9]+$ ]]; then + echo "📦 Detected prerelease tag" + echo "is_prerelease=true" >> $GITHUB_OUTPUT + + if echo "$BRANCHES" | grep -q 'origin/dev'; then + echo "✓ Prerelease tag is on dev branch" + echo "should_release=true" >> $GITHUB_OUTPUT + else + echo "✗ Prerelease tag must be on dev branch, skipping release" + echo "should_release=false" >> $GITHUB_OUTPUT + fi + elif [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "📦 Detected stable release tag" + echo "is_prerelease=false" >> $GITHUB_OUTPUT + + if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then + echo "✓ Stable release tag is on main or master branch" + echo "should_release=true" >> $GITHUB_OUTPUT + else + echo "✗ Stable release tag must be on main or master branch, skipping release" + echo "should_release=false" >> $GITHUB_OUTPUT + fi + else + echo "✗ Unknown tag format, skipping release" + echo "is_prerelease=false" >> $GITHUB_OUTPUT + echo "should_release=false" >> $GITHUB_OUTPUT + fi + + - name: Verify version consistency + if: steps.check.outputs.should_release == 'true' + run: | + # Extract version from git tag (remove 'v' prefix) + TAG_VERSION="${{ github.ref_name }}" + TAG_VERSION="${TAG_VERSION#v}" + # Extract version from Cargo.toml + CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') + echo "Git tag version: $TAG_VERSION" + echo "Cargo.toml version: $CARGO_VERSION" + if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then + echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" + exit 1 + fi + echo "✓ Version check passed!" + + check: + uses: ./.github/workflows/check.yml + needs: verify-tag + if: needs.verify-tag.outputs.should_release == 'true' + + test: + uses: ./.github/workflows/test.yml + needs: [verify-tag, check] + if: needs.verify-tag.outputs.should_release == 'true' + + release: + name: Create GitHub Release + runs-on: ubuntu-latest + needs: [verify-tag, check] + if: needs.verify-tag.outputs.should_release == 'true' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Generate release notes + id: release_notes + run: | + CURRENT_TAG="${{ github.ref_name }}" + + # Get previous tag + PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -A1 "^${CURRENT_TAG}$" | tail -n1) + + if [ -z "$PREVIOUS_TAG" ] || [ "$PREVIOUS_TAG" == "$CURRENT_TAG" ]; then + echo "No previous tag found, this is the first release" + CHANGELOG="Initial release" + else + echo "Generating changelog from $PREVIOUS_TAG to $CURRENT_TAG" + + # Generate changelog with commit messages + CHANGELOG=$(git log --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${CURRENT_TAG}") + + if [ -z "$CHANGELOG" ]; then + CHANGELOG="No changes" + fi + fi + + # Write changelog to output file (multi-line) + { + echo "changelog<> $GITHUB_OUTPUT + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + draft: false + prerelease: ${{ needs.verify-tag.outputs.is_prerelease == 'true' }} + body: | + ## Changes + ${{ steps.release_notes.outputs.changelog }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + publish: + name: Publish to crates.io + runs-on: ubuntu-latest + needs: [verify-tag, check] + if: needs.verify-tag.outputs.should_release == 'true' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@nightly + + - name: Dry run publish + run: cargo publish --dry-run + + - name: Publish to crates.io + run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/components/x86_vlapic/.github/workflows/test.yml b/components/x86_vlapic/.github/workflows/test.yml new file mode 100644 index 000000000..dc3b293d9 --- /dev/null +++ b/components/x86_vlapic/.github/workflows/test.yml @@ -0,0 +1,50 @@ +name: Test + +on: + push: + branches: + - '**' + tags-ignore: + - '**' + pull_request: + workflow_call: + +jobs: + load-config: + name: Load CI Configuration + runs-on: ubuntu-latest + outputs: + targets: ${{ steps.config.outputs.targets }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Load configuration + id: config + run: | + TARGETS=$(jq -c '.targets' .github/config.json) + echo "targets=$TARGETS" >> $GITHUB_OUTPUT + + test: + name: Test + runs-on: ubuntu-latest + needs: load-config + strategy: + fail-fast: false + matrix: + target: ${{ fromJson(needs.load-config.outputs.targets) }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@nightly + + # - name: Run tests + # run: cargo test --target ${{ matrix.target }} --all-features -- --nocapture + + # - name: Run doc tests + # run: cargo test --target ${{ matrix.target }} --doc + - name: Run tests + run: echo "Tests are skipped!" diff --git a/components/x86_vlapic/.gitignore b/components/x86_vlapic/.gitignore new file mode 100644 index 000000000..ff78c42af --- /dev/null +++ b/components/x86_vlapic/.gitignore @@ -0,0 +1,4 @@ +/target +/.vscode +.DS_Store +Cargo.lock diff --git a/components/x86_vlapic/Cargo.toml b/components/x86_vlapic/Cargo.toml new file mode 100644 index 000000000..4fb7597c1 --- /dev/null +++ b/components/x86_vlapic/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "x86_vlapic" +version = "0.2.2" +edition = "2024" +description = "x86 Virtual Local APIC" +authors = ["Keyang Hu ", "Mingxian Su "] +license = "Apache-2.0" +categories = ["virtualization"] +repository = "https://github.com/arceos-hypervisor/x86_vlapic" +keywords = ["x86", "hypervisor", "arceos"] + +[dependencies] +log = "0.4.19" +paste = "1.0.15" +tock-registers = "0.10.0" +bit = "0.1.1" + +memory_addr = "0.4" +axerrno = "0.2" + +axaddrspace = "0.3" +axdevice_base = "0.2.2" +axvisor_api = "0.3" diff --git a/components/x86_vlapic/LICENSE b/components/x86_vlapic/LICENSE new file mode 100644 index 000000000..b1a313f58 --- /dev/null +++ b/components/x86_vlapic/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/components/x86_vlapic/README.md b/components/x86_vlapic/README.md new file mode 100644 index 000000000..9455fb078 --- /dev/null +++ b/components/x86_vlapic/README.md @@ -0,0 +1,80 @@ +# x86_vlapic + +[![CI](https://github.com/arceos-hypervisor/x86_vlapic/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/arceos-hypervisor/x86_vlapic/actions/workflows/ci.yml) + +A Rust library for virtualizing x86 Local Advanced Programmable Interrupt Controller (LAPIC) functionality. **[Work in Progress]**. + +## Overview + +This library provides a software implementation of the x86 Local APIC (Advanced Programmable Interrupt Controller) for hypervisor use cases. It virtualizes the LAPIC registers and functionality according to the Intel Software Developer's Manual (SDM) specifications. + +⚠️ **Important**: This is an early-stage library focused solely on timer virtualization. Do not use for full LAPIC emulation yet. + +## Current Status +- ✅ **Timer Virtualization**: Fully implemented with support for one-shot, periodic, and TSC-deadline modes +- 🚧 **Register Definitions**: Complete register layout and bitfield definitions for all LAPIC registers +- 🚧 **Interrupt Handling**: Framework implemented, core interrupt delivery logic in development +- 🚧 **IPI Support**: Partial implementation, some functions are placeholders + +## Architecture + +The library is structured into several key modules: + +### Core Modules + +- [`src/vlapic.rs`](src/vlapic.rs) - Main virtual LAPIC implementation +- [`src/timer.rs`](src/timer.rs) - LAPIC timer virtualization +- [`src/consts.rs`](src/consts.rs) - Constants and register offset definitions +- [`src/utils.rs`](src/utils.rs) - Utility functions + +### Register Definitions + +- [`src/regs/mod.rs`](src/regs/mod.rs) - Main register structure definitions +- [`src/regs/lvt/`](src/regs/lvt/) - Local Vector Table register implementations + - [`timer.rs`](src/regs/lvt/timer.rs) - LVT Timer Register + - [`lint0.rs`](src/regs/lvt/lint0.rs) - LVT LINT0 Register + - [`lint1.rs`](src/regs/lvt/lint1.rs) - LVT LINT1 Register + - [`error.rs`](src/regs/lvt/error.rs) - LVT Error Register + - [`thermal.rs`](src/regs/lvt/thermal.rs) - LVT Thermal Monitor Register + - [`perfmon.rs`](src/regs/lvt/perfmon.rs) - LVT Performance Counter Register + - [`cmci.rs`](src/regs/lvt/cmci.rs) - LVT CMCI Register +- [`src/regs/timer/`](src/regs/timer/) - Timer-related register definitions + - [`dcr.rs`](src/regs/timer/dcr.rs) - Divide Configuration Register +- Other register modules for ICR, ESR, SVR, etc. + +## Basic Example + +``` rust,ignore +use x86_vlapic::EmulatedLocalApic; +use axvisor_api::vmm::{VMId, VCpuId}; + +// Create a new emulated Local APIC for VM 1, VCPU 0 +let vm_id = VMId::from(1 as usize); +let vcpu_id = VCpuId::from(0 as usize); +let apic = EmulatedLocalApic::new(vm_id, vcpu_id); + +// Get the shared virtual APIC access page address (static for all instances) +let access_addr = EmulatedLocalApic::virtual_apic_access_addr(); +assert!(access_addr.is_aligned(PAGE_SIZE_4K)); + +// Get the per-VCPU virtual APIC page address +let page_addr = apic.virtual_apic_page_addr(); +assert!(page_addr.is_aligned(PAGE_SIZE_4K)); +``` + +## Target Platform + +This library is designed for x86_64 architecture and targets `x86_64-unknown-none` for no-std environments, making it suitable for hypervisor and kernel development. + +## Related Projects + +[ArceOS](https://github.com/arceos-org/arceos) - An experimental modular OS (or Unikernel) +[AxVisor](https://github.com/arceos-hypervisor/axvisor) - Hypervisor implementation + +--- + +**Note**: This is a virtualization library and does not interact with actual hardware LAPIC. It's designed for use in hypervisors and virtual machine monitors. + +## License + +X86_vlapic is licensed under the Apache License, Version 2.0. See the [LICENSE](./LICENSE) file for details. diff --git a/components/x86_vlapic/src/consts.rs b/components/x86_vlapic/src/consts.rs new file mode 100644 index 000000000..9e84423a7 --- /dev/null +++ b/components/x86_vlapic/src/consts.rs @@ -0,0 +1,250 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use paste::paste; + +macro_rules! define_index_enum { + ($name:ident) => { + paste! { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum $name { + [<$name 0>] = 0, + [<$name 1>] = 1, + [<$name 2>] = 2, + [<$name 3>] = 3, + [<$name 4>] = 4, + [<$name 5>] = 5, + [<$name 6>] = 6, + [<$name 7>] = 7, + } + + impl $name { + const fn from(value: usize) -> Self { + match value { + 0 => $name::[<$name 0>], + 1 => $name::[<$name 1>], + 2 => $name::[<$name 2>], + 3 => $name::[<$name 3>], + 4 => $name::[<$name 4>], + 5 => $name::[<$name 5>], + 6 => $name::[<$name 6>], + 7 => $name::[<$name 7>], + _ => panic!("Invalid index"), + } + } + + pub const fn as_usize(&self) -> usize { + *self as usize + } + } + + impl core::fmt::Display for $name { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + $name::[<$name 0>] => write!(f, "{}0", stringify!($name)), + $name::[<$name 1>] => write!(f, "{}1", stringify!($name)), + $name::[<$name 2>] => write!(f, "{}2", stringify!($name)), + $name::[<$name 3>] => write!(f, "{}3", stringify!($name)), + $name::[<$name 4>] => write!(f, "{}4", stringify!($name)), + $name::[<$name 5>] => write!(f, "{}5", stringify!($name)), + $name::[<$name 6>] => write!(f, "{}6", stringify!($name)), + $name::[<$name 7>] => write!(f, "{}7", stringify!($name)), + } + } + } + } + }; +} + +define_index_enum!(ISRIndex); +define_index_enum!(TMRIndex); +define_index_enum!(IRRIndex); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[allow(clippy::upper_case_acronyms)] +pub enum ApicRegOffset { + /// ID register 0x2. + ID, + /// Version register 0x3. + Version, + /// Task Priority register 0x8. + TPR, + /// Arbitration Priority register 0x9. + APR, + /// Processor Priority register 0xA. + PPR, + /// EOI register 0xB. + EOI, + /// Remote Read register 0xC. + RRR, + /// Logical Destination Register 0xD. + LDR, + /// Destination Format register 0xE. + DFR, + /// Spurious Interrupt Vector register 0xF. + SIVR, + /// In-Service register 0x10..=0x17. + ISR(ISRIndex), + /// Trigger Mode register 0x18..=0x1F. + TMR(TMRIndex), + /// Interrupt Request register 0x20..=0x27. + IRR(IRRIndex), + /// Error Status register 0x28. + ESR, + /// LVT CMCI register 0x2F. + LvtCMCI, + /// Interrupt Command register 0x30. + ICRLow, + /// Interrupt Command register high 0x30. + ICRHi, + /// LVT Timer Interrupt register 0x32. + LvtTimer, + /// LVT Thermal Sensor Interrupt register 0x33. + LvtThermal, + /// LVT Performance Monitoring Counters Register 0x34. + LvtPmc, + /// LVT LINT0 register 0x35. + LvtLint0, + /// LVT LINT1 register 0x36. + LvtLint1, + /// LVT Error register 0x37. + LvtErr, + /// Initial Count register (for Timer) 0x38. + TimerInitCount, + /// Current Count register (for Timer) 0x39. + TimerCurCount, + /// Divide Configuration register (for Timer) 0x3E. + TimerDivConf, + /// Self IPI register 0x3F. + /// Available only in x2APIC mode. + SelfIPI, +} + +impl ApicRegOffset { + const fn from(value: usize) -> Self { + match value as u32 { + 0x2 => ApicRegOffset::ID, + 0x3 => ApicRegOffset::Version, + 0x8 => ApicRegOffset::TPR, + 0x9 => ApicRegOffset::APR, + 0xA => ApicRegOffset::PPR, + 0xB => ApicRegOffset::EOI, + 0xC => ApicRegOffset::RRR, + 0xD => ApicRegOffset::LDR, + 0xE => ApicRegOffset::DFR, + 0xF => ApicRegOffset::SIVR, + 0x10..=0x17 => ApicRegOffset::ISR(ISRIndex::from(value - 0x10)), + 0x18..=0x1F => ApicRegOffset::TMR(TMRIndex::from(value - 0x18)), + 0x20..=0x27 => ApicRegOffset::IRR(IRRIndex::from(value - 0x20)), + 0x28 => ApicRegOffset::ESR, + 0x2F => ApicRegOffset::LvtCMCI, + 0x30 => ApicRegOffset::ICRLow, + 0x31 => ApicRegOffset::ICRHi, + 0x32 => ApicRegOffset::LvtTimer, + 0x33 => ApicRegOffset::LvtThermal, + 0x34 => ApicRegOffset::LvtPmc, + 0x35 => ApicRegOffset::LvtLint0, + 0x36 => ApicRegOffset::LvtLint1, + 0x37 => ApicRegOffset::LvtErr, + 0x38 => ApicRegOffset::TimerInitCount, + 0x39 => ApicRegOffset::TimerCurCount, + 0x3E => ApicRegOffset::TimerDivConf, + 0x3F => ApicRegOffset::SelfIPI, + _ => panic!("Invalid APIC register offset"), + } + } +} + +impl core::fmt::Display for ApicRegOffset { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + ApicRegOffset::ID => write!(f, "ID"), + ApicRegOffset::Version => write!(f, "Version"), + ApicRegOffset::TPR => write!(f, "TPR"), + ApicRegOffset::APR => write!(f, "APR"), + ApicRegOffset::PPR => write!(f, "PPR"), + ApicRegOffset::EOI => write!(f, "EOI"), + ApicRegOffset::RRR => write!(f, "RRR"), + ApicRegOffset::LDR => write!(f, "LDR"), + ApicRegOffset::DFR => write!(f, "DFR"), + ApicRegOffset::SIVR => write!(f, "SIVR"), + ApicRegOffset::ISR(index) => write!(f, "{index:?}"), + ApicRegOffset::TMR(index) => write!(f, "{index:?}"), + ApicRegOffset::IRR(index) => write!(f, "{index:?}"), + ApicRegOffset::ESR => write!(f, "ESR"), + ApicRegOffset::LvtCMCI => write!(f, "LvtCMCI"), + ApicRegOffset::ICRLow => write!(f, "ICR_LOW"), + ApicRegOffset::ICRHi => write!(f, "ICR_HI"), + ApicRegOffset::LvtTimer => write!(f, "LvtTimer"), + ApicRegOffset::LvtThermal => write!(f, "LvtThermal"), + ApicRegOffset::LvtPmc => write!(f, "LvtPerformanceMonitoringCounter"), + ApicRegOffset::LvtLint0 => write!(f, "LvtLint0"), + ApicRegOffset::LvtLint1 => write!(f, "LvtLint1"), + ApicRegOffset::LvtErr => write!(f, "LvtErr"), + ApicRegOffset::TimerInitCount => write!(f, "TimerInitCount"), + ApicRegOffset::TimerCurCount => write!(f, "TimerCurCount"), + ApicRegOffset::TimerDivConf => write!(f, "TimerDivConf"), + ApicRegOffset::SelfIPI => write!(f, "SelfIPI"), + } + } +} + +pub const APIC_LVT_M: u32 = 0x00010000; +pub const APIC_LVT_DS: u32 = 0x00001000; +pub const APIC_LVT_VECTOR: u32 = 0x000000ff; + +/// 11.5.1 Local Vector Table +/// Figure 11-8. Local Vector Table (LVT) +/// - Value After Reset: 0001 0000H +pub const RESET_LVT_REG: u32 = APIC_LVT_M; +/// 11.9 SPURIOUS INTERRUPT +/// - Address: FEE0 00F0H +/// - Value after reset: 0000 00FFH +pub const RESET_SPURIOUS_INTERRUPT_VECTOR: u32 = 0x0000_00FF; + +#[allow(dead_code)] +pub const LAPIC_TRIG_LEVEL: bool = true; +pub const LAPIC_TRIG_EDGE: bool = false; + +pub mod xapic { + use axaddrspace::GuestPhysAddr; + + use super::ApicRegOffset; + + pub const DEFAULT_APIC_BASE: usize = 0xFEE0_0000; + pub const APIC_MMIO_SIZE: usize = 0x1000; + + pub const XAPIC_BROADCAST_DEST_ID: u32 = 0xFF; + + pub(crate) const fn xapic_mmio_access_reg_offset(addr: GuestPhysAddr) -> ApicRegOffset { + ApicRegOffset::from((addr.as_usize() & (APIC_MMIO_SIZE - 1)) >> 4) + } +} + +pub mod x2apic { + use axaddrspace::device::SysRegAddr; + + use super::ApicRegOffset; + + pub const X2APIC_MSE_REG_BASE: usize = 0x800; + pub const X2APIC_MSE_REG_SIZE: usize = 0x100; + + /// A destination ID value of FFFF_FFFFH is used for broadcast of interrupts + /// in both logical destination and physical destination modes. + pub const X2APIC_BROADCAST_DEST_ID: u32 = 0xFFFF_FFFF; + + pub(crate) const fn x2apic_msr_access_reg(addr: SysRegAddr) -> ApicRegOffset { + ApicRegOffset::from(addr.addr() - X2APIC_MSE_REG_BASE) + } +} diff --git a/components/x86_vlapic/src/lib.rs b/components/x86_vlapic/src/lib.rs new file mode 100644 index 000000000..0f65335e0 --- /dev/null +++ b/components/x86_vlapic/src/lib.rs @@ -0,0 +1,148 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Emulated Local APIC. +#![no_std] +#![doc = include_str!("../README.md")] + +extern crate alloc; + +#[macro_use] +extern crate log; + +mod consts; +mod regs; +mod timer; +mod utils; +mod vlapic; + +use core::cell::UnsafeCell; + +use axerrno::AxResult; +use axvisor_api::{ + memory, + vmm::{VCpuId, VMId}, +}; +use memory_addr::{AddrRange, PAGE_SIZE_4K}; + +use axaddrspace::{ + GuestPhysAddr, HostPhysAddr, HostVirtAddr, + device::{AccessWidth, SysRegAddr, SysRegAddrRange}, +}; +use axdevice_base::{BaseDeviceOps, EmuDeviceType}; + +use crate::consts::x2apic::x2apic_msr_access_reg; +use crate::consts::xapic::xapic_mmio_access_reg_offset; +use crate::vlapic::VirtualApicRegs; + +#[repr(align(4096))] +struct APICAccessPage([u8; PAGE_SIZE_4K]); + +static VIRTUAL_APIC_ACCESS_PAGE: APICAccessPage = APICAccessPage([0; PAGE_SIZE_4K]); + +/// A emulated local APIC device. +pub struct EmulatedLocalApic { + vlapic_regs: UnsafeCell, +} + +impl EmulatedLocalApic { + /// Create a new `EmulatedLocalApic`. + pub fn new(vm_id: VMId, vcpu_id: VCpuId) -> Self { + EmulatedLocalApic { + vlapic_regs: UnsafeCell::new(VirtualApicRegs::new(vm_id, vcpu_id)), + } + } + + fn get_vlapic_regs(&self) -> &VirtualApicRegs { + unsafe { &*self.vlapic_regs.get() } + } + + #[allow(clippy::mut_from_ref)] // SAFETY: get_mut_vlapic_regs is never called concurrently. + fn get_mut_vlapic_regs(&self) -> &mut VirtualApicRegs { + unsafe { &mut *self.vlapic_regs.get() } + } +} + +impl EmulatedLocalApic { + /// APIC-access address (64 bits). + /// This field contains the physical address of the 4-KByte APIC-access page. + /// If the “virtualize APIC accesses” VM-execution control is 1, + /// access to this page may cause VM exits or be virtualized by the processor. + /// See Section 30.4. + pub fn virtual_apic_access_addr() -> HostPhysAddr { + memory::virt_to_phys(HostVirtAddr::from_usize( + VIRTUAL_APIC_ACCESS_PAGE.0.as_ptr() as usize, + )) + } + + /// Virtual-APIC address (64 bits). + /// This field contains the physical address of the 4-KByte virtual-APIC page. + /// The processor uses the virtual-APIC page to virtualize certain accesses to APIC registers and to manage virtual interrupts; + /// see Chapter 30. + pub fn virtual_apic_page_addr(&self) -> HostPhysAddr { + self.get_vlapic_regs().virtual_apic_page_addr() + } +} + +impl BaseDeviceOps> for EmulatedLocalApic { + fn emu_type(&self) -> EmuDeviceType { + EmuDeviceType::InterruptController + } + + fn address_range(&self) -> AddrRange { + use crate::consts::xapic::{APIC_MMIO_SIZE, DEFAULT_APIC_BASE}; + AddrRange::new( + GuestPhysAddr::from_usize(DEFAULT_APIC_BASE), + GuestPhysAddr::from_usize(DEFAULT_APIC_BASE + APIC_MMIO_SIZE), + ) + } + + fn handle_read(&self, addr: GuestPhysAddr, width: AccessWidth) -> AxResult { + debug!("EmulatedLocalApic::handle_read: addr={addr:?}, width={width:?}"); + let reg_off = xapic_mmio_access_reg_offset(addr); + self.get_vlapic_regs().handle_read(reg_off, width) + } + + fn handle_write(&self, addr: GuestPhysAddr, width: AccessWidth, val: usize) -> AxResult { + debug!("EmulatedLocalApic::handle_write: addr={addr:?}, width={width:?}, val={val:#x}"); + let reg_off = xapic_mmio_access_reg_offset(addr); + self.get_mut_vlapic_regs().handle_write(reg_off, val, width) + } +} + +impl BaseDeviceOps for EmulatedLocalApic { + fn emu_type(&self) -> EmuDeviceType { + EmuDeviceType::InterruptController + } + + fn address_range(&self) -> SysRegAddrRange { + use crate::consts::x2apic::{X2APIC_MSE_REG_BASE, X2APIC_MSE_REG_SIZE}; + SysRegAddrRange::new( + SysRegAddr(X2APIC_MSE_REG_BASE), + SysRegAddr(X2APIC_MSE_REG_BASE + X2APIC_MSE_REG_SIZE), + ) + } + + fn handle_read(&self, addr: SysRegAddr, width: AccessWidth) -> AxResult { + debug!("EmulatedLocalApic::handle_read: addr={addr:?}, width={width:?}"); + let reg_off = x2apic_msr_access_reg(addr); + self.get_vlapic_regs().handle_read(reg_off, width) + } + + fn handle_write(&self, addr: SysRegAddr, width: AccessWidth, val: usize) -> AxResult { + debug!("EmulatedLocalApic::handle_write: addr={addr:?}, width={width:?}, val={val:#x}"); + let reg_off = x2apic_msr_access_reg(addr); + self.get_mut_vlapic_regs().handle_write(reg_off, val, width) + } +} diff --git a/components/x86_vlapic/src/regs/apic_base.rs b/components/x86_vlapic/src/regs/apic_base.rs new file mode 100644 index 000000000..72827fd21 --- /dev/null +++ b/components/x86_vlapic/src/regs/apic_base.rs @@ -0,0 +1,75 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! 11.4.4 Local APIC Status and Location +//! The status and location of the local APIC are contained in the IA32_APIC_BASE MSR (see Figure 11-5). +//! Figure 11-26. IA32_APIC_BASE MSR Supporting x2APIC +//! Processor support for x2APIC mode can be detected by executing CPUID with EAX=1 and then checking ECX, bit 21 ECX. +//! If CPUID.(EAX=1):ECX.21 is set , the processor supports the x2APIC capability and can be placed into the x2APIC mode. +//! System software can place the local APIC in the x2APIC mode by setting the x2APIC mode enable bit (bit 10) in the IA32_APIC_BASE MSR at MSR address 01BH. + +use tock_registers::LocalRegisterCopy; +use tock_registers::register_bitfields; + +register_bitfields! { + u64, + pub APIC_BASE [ + /// Reserved2 + Reserved2 OFFSET(36) NUMBITS(28) [], + /// APIC Base field, bits 12 through 35 + /// Specifies the base address of the APIC registers. + /// This 24-bit value is extended by 12 bits at the low end to form the base address. + /// This automatically aligns the address on a 4-KByte boundary. + /// Following a power-up or reset, the field is set to FEE0 0000H. + APIC_BASE OFFSET(12) NUMBITS(24) [], + /// APIC Global Enable flag, bit 11 + /// Enables or disables the local APIC (see Section 11.4.3, “Enabling or Disabling the Local APIC”). + /// This flag is available in the Pentium 4, Intel Xeon, and P6 family processors. + /// It is not guaranteed to be available or available at the same location in future Intel 64 or IA-32 processors. + /// EN—xAPIC global enable/disable + /// - 0: xAPIC disabled + /// - 1: xAPIC enabled + XAPIC_ENABLED OFFSET(11) NUMBITS(1) [], + /// EXTD—Enable x2APIC mode + /// - 0: xAPIC mode + /// - 1: x2APIC mode + X2APIC_Enabled OFFSET(10) NUMBITS(1) [], + /// Reserved1 + Reserved1 OFFSET(9) NUMBITS(1) [], + /// BSP flag, bit 8 + /// Indicates if the processor is the bootstrap processor (BSP). + /// See Section 9.4, “MultipleProcessor (MP) Initialization.” + /// Following a power-up or reset, this flag is set to 1 for the processor selected as the BSP and set to 0 for the remaining processors (APs). + /// - 0: Processor is not BSP + /// - 1: Processor is BSP + BSP OFFSET(8) NUMBITS(1) [], + /// Reserved0 + Reserved0 OFFSET(0) NUMBITS(8) [], + ] +} + +/// IA32_APIC_BASE MSR (Model Specific Register) supporting x2APIC. +/// - Address: 1B0H +/// - Value after reset: FEE_0000_0000H +/// +/// Table 11-5, "x2APIC operating mode configurations" describe the possible combinations of the enable bit (EN - bit 11) +/// and the extended mode bit (EXTD - bit 10) in the IA32_APIC_BASE MSR. +/// +/// (xAPIC global enable (IA32_APIC_BASE[11]),x2APIC enable (IA32_APIC_BASE[10])) = Description +/// +/// - (0, 0) = local APIC is disabled +/// - (0, 1) = Invalid +/// - (1, 0) = local APIC is enabled in xAPIC mode +/// - (1, 1) = local APIC is enabled in x2APIC mode +pub type ApicBaseRegisterMsr = LocalRegisterCopy; diff --git a/components/x86_vlapic/src/regs/dfr.rs b/components/x86_vlapic/src/regs/dfr.rs new file mode 100644 index 000000000..ba7baf0fc --- /dev/null +++ b/components/x86_vlapic/src/regs/dfr.rs @@ -0,0 +1,53 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use tock_registers::LocalRegisterCopy; +use tock_registers::register_bitfields; +use tock_registers::registers::ReadWrite; + +register_bitfields! { + u32, + pub DESTINATION_FORMAT [ + /// Model + Model OFFSET(28) NUMBITS(4) [ + /// Flat model + Flat = 0b1111, + /// Cluster model + Cluster = 0b0000 + ], + /// Reserved (All 1s) + ReservedALL1 OFFSET(0) NUMBITS(28) [], + ] +} + +/// Destination Format Register using MMIO. +/// - Address: FEE0 00E0H +/// - Value after reset: FFFF FFFFH +/// +/// The 4-bit model field in this register selects one of two models (flat or cluster) that can be used to interpret the MDA when using logical destination mode. +/// **Flat Model** — This model is selected by programming DFR bits 28 through 31 to 1111. +/// Here, a unique logical APIC ID can be established for up to 8 local APICs by setting a different bit in the logical APIC ID field of the LDR for each local APIC. +/// A group of local APICs can then be selected by setting one or more bits in the MDA. +/// +/// **Cluster Model** — This model is selected by programming DFR bits 28 through 31 to 0000. +/// This model supports two basic destination schemes: flat cluster and hierarchical cluster. +pub type DestinationFormatRegisterMmio = ReadWrite; + +/// A read-write copy of Destination Format Register (FEE0 00E0H). +/// +/// This behaves very similarly to a MMIO read-write register, but instead of doing a +/// volatile read to MMIO to get the value for each function call, a copy of the +/// register contents are stored locally in memory. +#[allow(dead_code)] +pub type DestinationFormatRegisterLocal = LocalRegisterCopy; diff --git a/components/x86_vlapic/src/regs/esr.rs b/components/x86_vlapic/src/regs/esr.rs new file mode 100644 index 000000000..8eeacb373 --- /dev/null +++ b/components/x86_vlapic/src/regs/esr.rs @@ -0,0 +1,80 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Figure 11-9. Error Status Register (ESR) +//! 11.5.3 Error Handling +//! The local APIC records errors detected during interrupt handling in the error status register (ESR). + +use tock_registers::LocalRegisterCopy; +use tock_registers::fields::FieldValue; +use tock_registers::register_bitfields; +use tock_registers::registers::ReadWrite; + +register_bitfields! { + u32, + pub ERROR_STATUS [ + /// Reserved + Reserved2 OFFSET(8) NUMBITS(24) [], + /// Bit 7: Illegal Register Address + /// Set when the local APIC is in xAPIC mode and software attempts to access a register that is reserved in the processor's local-APIC register-address space; see Table 10-1. + /// (The local-APIC register-address space comprises the 4 KBytes at the physical address specified in the IA32_APIC_BASE MSR.) + /// Used only on Intel Core, Intel Atom, Pentium 4, Intel Xeon, and P6 family processors. + IllegalRegisterAddress OFFSET(7) NUMBITS(1) [], + /// Bit 6: Receive Illegal Vector. + /// Set when the local APIC detects an illegal vector (one in the range 0 to 15) in an interrupt message it receives or in an interrupt generated locally from the local vector table or via a self IPI. + /// Such interrupts are not delivered to the processor; the local APIC will never set an IRR bit in the range 0 to 15. + ReceiveIllegalVector OFFSET(6) NUMBITS(1) [], + /// Bit 5: Send Illegal Vector. + /// Set when the local APIC detects an illegal vector (one in the range 0 to 15) in the message that it is sending. + /// This occurs as the result of a write to the ICR (in both xAPIC and x2APIC modes) or to SELF IPI register (x2APIC mode only) with an illegal vector. + /// If the local APIC does not support the sending of lowest-priority IPIs and software writes the ICR to send a lowest-priority IPI with an illegal vector, the local APIC sets only the “redirectable IPI” error bit. + /// The interrupt is not processed and hence the “Send Illegal Vector” bit is not set in the ESR. + SendIllegalVector OFFSET(5) NUMBITS(1) [], + /// Bit 4: Redirectable IPI. + /// Set when the local APIC detects an attempt to send an IPI with the lowest-priority delivery mode and the local APIC does not support the sending of such IPIs. + /// This bit is used on some Intel Core and Intel Xeon processors. ] + /// As noted in Section 11.6.2, the ability of a processor to send a lowest-priority IPI is model-specific and should be avoided. + RedirectableIPI OFFSET(4) NUMBITS(1) [], + /// Bit 3: Receive Accept Error. + /// Set when the local APIC detects that the message it received was not accepted by any APIC on the APIC bus, including itself. + /// Used only on P6 family and Pentium processors. + ReceiveAcceptError OFFSET(3) NUMBITS(1) [], + /// Bit 2: Send Accept Error. + /// Set when the local APIC detects that a message it sent was not accepted by any APIC on the APIC bus. + /// Used only on P6 family and Pentium processors. + SendAcceptError OFFSET(2) NUMBITS(1) [], + /// Bit 1: Receive Checksum Error. + /// Set when the local APIC detects a checksum error for a message that it received on the APIC bus. + /// Used only on P6 family and Pentium processors. + ReceiveChecksumError OFFSET(1) NUMBITS(1) [], + /// Bit 0: Send Checksum Error. + /// Set when the local APIC detects a checksum error for a message that it sent on the APIC bus. + /// Used only on P6 family and Pentium processors. + SendChecksumError OFFSET(0) NUMBITS(1) [], + ] +} + +/// Error Status Register (ESR) using MMIO. +/// The local APIC records errors detected during interrupt handling in the error status register (ESR). +/// - Address: FEE0 0280H +/// - Value after reset: 0H +pub type ErrorStatusRegisterMmio = ReadWrite; + +/// A read-write copy of Error Status Register (ESR). +/// This behaves very similarly to a MMIO read-write register, but instead of doing a +/// volatile read to MMIO to get the value for each function call, a copy of the +/// register contents are stored locally in memory. +pub type ErrorStatusRegisterLocal = LocalRegisterCopy; + +pub type ErrorStatusRegisterValue = FieldValue; diff --git a/components/x86_vlapic/src/regs/icr.rs b/components/x86_vlapic/src/regs/icr.rs new file mode 100644 index 000000000..d8d227282 --- /dev/null +++ b/components/x86_vlapic/src/regs/icr.rs @@ -0,0 +1,172 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! 11.6.1 Interrupt Command Register (ICR) +//! The interrupt command register (ICR) is a 64-bit1 local APIC register (see Figure 11-12) +//! that allows software running on the processor to specify and send interprocessor interrupts (IPIs) to other processors in the system. + +use tock_registers::LocalRegisterCopy; +use tock_registers::register_bitfields; +use tock_registers::registers::ReadWrite; + +register_bitfields! { + u32, + pub INTERRUPT_COMMAND_LOW [ + /// Reserved + Reserved2 OFFSET(20) NUMBITS(12) [], + /// Destination Shorthand + /// Indicates whether a shorthand notation is used to specify the destination of the interrupt and, if so, which shorthand is used. Destination shorthands are used in place of the 8-bit destination field, and can be sent by software using a single write to the low doubleword of the ICR. Shorthands are defined for the following cases: software self interrupt, IPIs to all processors in the system including the sender, IPIs to all processors in the system excluding the sender. + /// - 00: (No Shorthand) + /// The destination is specified in the destination field. + /// - 01: (Self) + /// The issuing APIC is the one and only destination of the IPI. This destination shorthand allows software to interrupt the processor on which it is executing. An APIC implementation is free to deliver the self-interrupt message internally or to issue the message to the bus and “snoop” it as with any other IPI message. + /// - 10: (All Including Self) + /// The IPI is sent to all processors in the system including the processor sending the IPI. The APIC will broadcast an IPI message with the destination field set to FH for Pentium and P6 family processors and to FFH for Pentium 4 and Intel Xeon processors. + /// - 11: (All Excluding Self) + /// The IPI is sent to all processors in a system with the exception of the processor sending the IPI. The APIC broadcasts a message with the physical destination mode and destination field set to FH for Pentium and P6 family processors and to FFH for Pentium 4 and Intel Xeon processors. Support for this destination shorthand in conjunction with the lowest-priority delivery mode is model specific. For Pentium 4 and Intel Xeon processors, when this shorthand is used together with lowest priority delivery mode, the IPI may be redirected back to the issuing processor. + DestinationShorthand OFFSET(18) NUMBITS(2) [ + /// No Shorthand + NoShorthand = 0b00, + /// Self + SELF = 0b01, + /// All Including Self + AllIncludingSelf = 0b10, + /// All Excluding Self + AllExcludingSelf = 0b11 + ], + /// Reserved + Reserved1 OFFSET(16) NUMBITS(2) [], + /// Trigger Mode + /// Selects the trigger mode when using the INIT level de-assert delivery mode: + /// edge (0) or level (1). + /// It is ignored for all other delivery modes. + /// (This flag has no meaning in Pentium 4 and Intel Xeon processors, and will always be issued as a 0.) + TriggerMode OFFSET(15) NUMBITS(1) [ + /// Edge + Edge = 0, + /// Level + Level = 1 + ], + /// Level + /// For the INIT level de-assert delivery mode this flag must be set to 0; + /// for all other delivery modes it must be set to 1. + /// (This flag has no meaning in Pentium 4 and Intel Xeon processors, and will always be issued as a 1.) + Level OFFSET(14) NUMBITS(1) [ + /// De-assert + DeAssert = 0, + /// Assert + Assert = 1 + ], + /// Reserved + Reserved0 OFFSET(13) NUMBITS(1) [], + /// Delivery Status (Read Only) Indicates the IPI delivery status, as follows: + /// - 0 (Idle) Indicates that this local APIC has completed sending any previous IPIs. + /// - 1 (Send Pending) Indicates that this local APIC has not completed sending the last IPI. + DeliveryStatus OFFSET(12) NUMBITS(1) [ + /// Idle + Idle = 0, + /// Send Pending + SendPending = 1 + ], + /// Destination Mode Selects either physical (0) or logical (1) destination mode (see Section 11.6.2, “Determining IPI Destination”). + DestinationMode OFFSET(11) NUMBITS(1) [ + /// Physical + Physical = 0, + /// Logical + Logical = 1 + ], + /// Delivery Mode Specifies the type of IPI to be sent. + /// This field is also know as the IPI message type field. + /// - 000 (Fixed) + /// Delivers the interrupt specified in the vector field to the target processor or processors. + /// - 001 (Lowest Priority) + /// Same as fixed mode, except that the interrupt is delivered to the processor executing at the lowest priority among the set of processors specified in the destination field. The ability for a processor to send a lowest priority IPI is model specific and should be avoided by BIOS and operating system software. + /// - 010 (SMI) + /// Delivers an SMI interrupt to the target processor or processors. The vector field must be programmed to 00H for future compatibility. + /// - 011 (Reserved) + /// - 100 (NMI) + /// Delivers an NMI interrupt to the target processor or processors. The vector information is ignored. + /// - 101 (INIT) + /// Delivers an INIT request to the target processor or processors, which causes them to perform an INIT. + /// As a result of this IPI message, all the target processors perform an INIT. + /// The vector field must be programmed to 00H for future compatibility. + /// - 101 (INIT Level De-assert) + /// (Not supported in the Pentium 4 and Intel Xeon processors.) + /// Sends a synchronization message to all the local APICs in the system to set their arbitration IDs (stored in their Arb ID registers) to the values of their APIC IDs (see Section 11.7, “System and APIC Bus Arbitration”). + /// For this delivery mode, the level flag must be set to 0 and trigger mode flag to 1. + /// This IPI is sent to all processors, regardless of the value in the destination field or the destination shorthand field; + /// however, software should specify the “all including self” shorthand. + /// - 110 (Start-Up) + /// Sends a special “start-up” IPI (called a SIPI) to the target processor or processors. + /// The vector typically points to a start-up routine that is part of the BIOS boot-strap code + /// (see Section 9.4, “Multiple-Processor (MP) Initialization”). + /// IPIs sent with this delivery mode are not automatically retried if the source APIC is unable to deliver it. + /// It is up to the software to determine if the SIPI was not successfully delivered and to reissue the SIPI if necessary. + /// - 111 (Reserved) + DeliveryMode OFFSET(8) NUMBITS(3) [ + /// Fixed + Fixed = 0b000, + /// Lowest Priority + LowestPriority = 0b001, + /// SMI + SMI = 0b010, + /// Reserved + Reserved011 = 0b011, + /// NMI + NMI = 0b100, + /// INIT + INIT = 0b101, + /// Start-Up + StartUp = 0b110, + /// Start-Up + Reserved111 = 0b111 + ], + /// Vector The vector number of the interrupt being sent. + Vector OFFSET(0) NUMBITS(8) [] + ] +} + +register_bitfields! { + u32, + pub INTERRUPT_COMMAND_HIGH [ + /// Destination [56:63] + /// Specifies the target processor or processors. + /// This field is only used when the destination shorthand field is set to 00B. + /// If the destination mode is set to physical, then bits 56 through 59 contain the APIC ID of the target processor for Pentium and P6 family processors and bits 56 through 63 contain the APIC ID of the target processor the for Pentium 4 and Intel Xeon processors. + /// If the destination mode is set to logical, the interpretation of the 8-bit destination field depends on the settings of the DFR and LDR registers of the local APICs in all the processors in the system + /// (see Section 11.6.2, “Determining IPI Destination”). + /// + /// 11.12.9 ICR Operation in x2APIC Mode + /// The destination ID field is expanded to 32 bits in x2APIC mode. + Destination OFFSET(24) NUMBITS(8) [], + /// Reserved + Reserved OFFSET(0) NUMBITS(24) [] + ] +} + +/// Interrupt Command Register (ICR) LOW using MMIO. +/// - Address: FEE0 0300H (0 - 31) +/// - Value after Reset: 0H +pub type InterruptCommandRegisterLowMmio = ReadWrite; + +/// A read-write copy of Interrupt Command Register (ICR) LOW. +/// This behaves very similarly to a MMIO read-write register, but instead of doing a +/// volatile read to MMIO to get the value for each function call, a copy of the +/// register contents are stored locally in memory. +pub type InterruptCommandRegisterLowLocal = LocalRegisterCopy; + +/// Interrupt Command Register (ICR) HIGH using MMIO. +/// - Address: FEE0 0310H (32 - 63) +/// - Value after Reset: 0H +pub type InterruptCommandRegisterHighMmio = ReadWrite; diff --git a/components/x86_vlapic/src/regs/lvt/cmci.rs b/components/x86_vlapic/src/regs/lvt/cmci.rs new file mode 100644 index 000000000..7feb8753c --- /dev/null +++ b/components/x86_vlapic/src/regs/lvt/cmci.rs @@ -0,0 +1,87 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use tock_registers::LocalRegisterCopy; +use tock_registers::register_bitfields; +use tock_registers::registers::ReadWrite; + +register_bitfields! { + u32, + pub LVT_CMCI [ + /// Reserved2 + Reserved2 OFFSET(17) NUMBITS(15) [], + /// Mask + Mask OFFSET(16) NUMBITS(1) [ + /// Not masked. + NotMasked = 0, + /// Masked. + Masked = 1 + ], + Reserved1 OFFSET(13) NUMBITS(3) [], + /// Delivery Status (Read Only): Indicates the interrupt delivery status + DeliveryStatus OFFSET(12) NUMBITS(1) [ + /// 0 (Idle) + /// There is currently no activity for this interrupt source, + /// or the previous interrupt from this source was delivered to the processor core and accepted. + Idle = 0, + /// 1 (Send Pending) + /// Indicates that an interrupt from this source has been delivered to the processor core + /// but has not yet been accepted (see Section 11.5.5, “Local Interrupt Acceptance”). + SendPending = 1 + ], + Reserved0 OFFSET(11) NUMBITS(1) [], + /// Delivery Mode: Specifies the type of interrupt to be sent to the processor. + /// Some delivery modes will only operate as intended when used in conjunction with a specific trigger mode. + DeliveryMode OFFSET(8) NUMBITS(3) [ + /// 000 (Fixed) Delivers the interrupt specified in the vector field. + Fixed = 0b000, + /// 010 (SMI) Delivers an SMI interrupt to the processor core through + /// the processor’s local SMI signal path. + /// When using this delivery mode, the vector field should be set to 00H for future compatibility. + SMI = 0b010, + /// 100 (NMI) Delivers an NMI interrupt to the processor. The vector information is ignored. + NMI = 0b100, + /// 101 (INIT) Delivers an INIT request to the processor core, + /// which causes the processor to perform an INIT. + /// When using this delivery mode, the vector field should be set to 00H for future compatibility. + /// Not supported for the LVT CMCI register, the LVT thermal monitor register, or the LVT performance counter register. + INIT = 0b101, + /// 110 Reserved; not supported for any LVT register. + Reserved = 0b110, + /// 111 (ExtINT) Causes the processor to respond to the interrupt + /// as if the interrupt originated in an externally connected (8259A-compatible) interrupt controller. + /// A special INTA bus cycle corresponding to ExtINT, is routed to the external controller. + /// The external controller is expected to supply the vector information. + /// The APIC architecture supports only one ExtINT source in a system, usually contained in the compatibility bridge. + /// Only one processor in the system should have an LVT entry configured to use the ExtINT delivery mode. + /// Not supported for the LVT CMCI register, the LVT thermal monitor register, or the LVT performance counter register. + ExtINT = 0b111 + ], + /// Vector: Interrupt vector number. + Vector OFFSET(0) NUMBITS(8) [], + ] +} + +/// LVT CMCI Register (FEE0 02F0H) using MMIO. +/// Specifies interrupt delivery when an overflow condition of corrected machine check error count +/// reaching a threshold value occurred in a machine check bank supporting CMCI +/// (see Section 16.5.1, “CMCI Local APIC Interface”). +pub type LvtCmciRegisterMmio = ReadWrite; + +/// A read-write copy of LVT CMCI Register (FEE0 02F0H). +/// +/// This behaves very similarly to a MMIO read-write register, but instead of doing a +/// volatile read to MMIO to get the value for each function call, a copy of the +/// register contents are stored locally in memory. +pub type LvtCmciRegisterLocal = LocalRegisterCopy; diff --git a/components/x86_vlapic/src/regs/lvt/error.rs b/components/x86_vlapic/src/regs/lvt/error.rs new file mode 100644 index 000000000..3d61100fa --- /dev/null +++ b/components/x86_vlapic/src/regs/lvt/error.rs @@ -0,0 +1,58 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use tock_registers::LocalRegisterCopy; +use tock_registers::register_bitfields; +use tock_registers::registers::ReadWrite; + +register_bitfields! { + u32, + pub LVT_ERROR [ + /// Reserved2 + Reserved2 OFFSET(17) NUMBITS(15) [], + /// Mask + Mask OFFSET(16) NUMBITS(1) [ + /// Not masked. + NotMasked = 0, + /// Masked. + Masked = 1 + ], + Reserved1 OFFSET(13) NUMBITS(3) [], + /// Delivery Status (Read Only): Indicates the interrupt delivery status + DeliveryStatus OFFSET(12) NUMBITS(1) [ + /// 0 (Idle) + /// There is currently no activity for this interrupt source, + /// or the previous interrupt from this source was delivered to the processor core and accepted. + Idle = 0, + /// 1 (Send Pending) + /// Indicates that an interrupt from this source has been delivered to the processor core + /// but has not yet been accepted (see Section 11.5.5, “Local Interrupt Acceptance”). + SendPending = 1 + ], + Reserved0 OFFSET(8) NUMBITS(4) [], + /// Vector: Interrupt vector number. + Vector OFFSET(0) NUMBITS(8) [], + ] +} + +/// LVT Error Register (FEE0 0370H) +/// Specifies interrupt delivery when the APIC detects an internal error (see Section 11.5.3, “Error Handling”). +pub type LvtErrorRegisterMmio = ReadWrite; + +/// A read-write copy of LVT Error Register (FEE0 0370H). +/// +/// This behaves very similarly to a MMIO read-write register, but instead of doing a +/// volatile read to MMIO to get the value for each function call, a copy of the +/// register contents are stored locally in memory. +pub type LvtErrorRegisterLocal = LocalRegisterCopy; diff --git a/components/x86_vlapic/src/regs/lvt/lint0.rs b/components/x86_vlapic/src/regs/lvt/lint0.rs new file mode 100644 index 000000000..fc6aa2fe9 --- /dev/null +++ b/components/x86_vlapic/src/regs/lvt/lint0.rs @@ -0,0 +1,111 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use tock_registers::LocalRegisterCopy; +use tock_registers::register_bitfields; +use tock_registers::registers::ReadWrite; + +register_bitfields! { + u32, + pub LVT_LINT0 [ + /// Reserved1 + Reserved1 OFFSET(17) NUMBITS(15) [], + /// Mask + Mask OFFSET(16) NUMBITS(1) [ + /// Not masked. + NotMasked = 0, + /// Masked. + Masked = 1 + ], + /// Trigger Mode Selects the trigger mode for the local LINT0 and LINT1 pins: + /// (0) edge sensitive and (1) level sensitive. + /// This flag is only used when the delivery mode is Fixed. + /// When the delivery mode is NMI, SMI, or INIT, the trigger mode is always edge sensitive. + /// When the delivery mode is ExtINT, the trigger mode is always level sensitive. The timer and error interrupts are always treated as edge sensitive. + /// If the local APIC is not used in conjunction with an I/O APIC and fixed delivery mode is selected; + /// the Pentium 4, Intel Xeon, and P6 family processors will always use level-sensitive triggering, regardless if edge-sensitive triggering is selected. + /// Software should always set the trigger mode in the LVT LINT1 register to 0 (edge sensitive). Level-sensitive interrupts are not supported for LINT1. + TriggerMode OFFSET(15) NUMBITS(1) [ + /// Edge sensitive. + EdgeSensitive = 0, + /// Level sensitive. + LevelSensitive = 1 + ], + /// Remote IRR Flag (Read Only) + /// For fixed mode, level-triggered interrupts; + /// this flag is set when the local APIC accepts the interrupt for servicing and is reset when an EOI command is received from the processor. + /// The meaning of this flag is undefined for edge-triggered interrupts and other delivery modes. + RemoteIRR OFFSET(14) NUMBITS(1) [], + /// Interrupt Input Pin Polarity Specifies the polarity of the corresponding interrupt pin: + /// (0) active high or (1) active low. + InterruptInputPinPolarity OFFSET(13) NUMBITS(1) [ + /// Active high. + ActiveHigh = 0, + /// Active low. + ActiveLow = 1 + ], + /// Delivery Status (Read Only): Indicates the interrupt delivery status + DeliveryStatus OFFSET(12) NUMBITS(1) [ + /// 0 (Idle) + /// There is currently no activity for this interrupt source, + /// or the previous interrupt from this source was delivered to the processor core and accepted. + Idle = 0, + /// 1 (Send Pending) + /// Indicates that an interrupt from this source has been delivered to the processor core + /// but has not yet been accepted (see Section 11.5.5, “Local Interrupt Acceptance”). + SendPending = 1 + ], + Reserved0 OFFSET(11) NUMBITS(1) [], + /// Delivery Mode: Specifies the type of interrupt to be sent to the processor. + /// Some delivery modes will only operate as intended when used in conjunction with a specific trigger mode. + DeliveryMode OFFSET(8) NUMBITS(3) [ + /// 000 (Fixed) Delivers the interrupt specified in the vector field. + Fixed = 0b000, + /// 010 (SMI) Delivers an SMI interrupt to the processor core through + /// the processor’s local SMI signal path. + /// When using this delivery mode, the vector field should be set to 00H for future compatibility. + SMI = 0b010, + /// 100 (NMI) Delivers an NMI interrupt to the processor. The vector information is ignored. + NMI = 0b100, + /// 101 (INIT) Delivers an INIT request to the processor core, + /// which causes the processor to perform an INIT. + /// When using this delivery mode, the vector field should be set to 00H for future compatibility. + /// Not supported for the LVT CMCI register, the LVT thermal monitor register, or the LVT performance counter register. + INIT = 0b101, + /// 110 Reserved; not supported for any LVT register. + Reserved = 0b110, + /// 111 (ExtINT) Causes the processor to respond to the interrupt + /// as if the interrupt originated in an externally connected (8259A-compatible) interrupt controller. + /// A special INTA bus cycle corresponding to ExtINT, is routed to the external controller. + /// The external controller is expected to supply the vector information. + /// The APIC architecture supports only one ExtINT source in a system, usually contained in the compatibility bridge. + /// Only one processor in the system should have an LVT entry configured to use the ExtINT delivery mode. + /// Not supported for the LVT CMCI register, the LVT thermal monitor register, or the LVT performance counter register. + ExtINT = 0b111 + ], + /// Vector: Interrupt vector number. + Vector OFFSET(0) NUMBITS(8) [], + ] +} + +/// LVT LINT0 Register (FEE0 0350H) +/// Specifies interrupt delivery when an interrupt is signaled at the LINT0 pin. +pub type LvtLint0RegisterMmio = ReadWrite; + +/// A read-write copy of LVT LINT0 Register (FEE0 0350H). +/// +/// This behaves very similarly to a MMIO read-write register, but instead of doing a +/// volatile read to MMIO to get the value for each function call, a copy of the +/// register contents are stored locally in memory. +pub type LvtLint0RegisterLocal = LocalRegisterCopy; diff --git a/components/x86_vlapic/src/regs/lvt/lint1.rs b/components/x86_vlapic/src/regs/lvt/lint1.rs new file mode 100644 index 000000000..f3c2f2a31 --- /dev/null +++ b/components/x86_vlapic/src/regs/lvt/lint1.rs @@ -0,0 +1,111 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use tock_registers::LocalRegisterCopy; +use tock_registers::register_bitfields; +use tock_registers::registers::ReadWrite; + +register_bitfields! { + u32, + pub LVT_LINT1 [ + /// Reserved1 + Reserved1 OFFSET(17) NUMBITS(15) [], + /// Mask + Mask OFFSET(16) NUMBITS(1) [ + /// Not masked. + NotMasked = 0, + /// Masked. + Masked = 1 + ], + /// Trigger Mode Selects the trigger mode for the local LINT0 and LINT1 pins: + /// (0) edge sensitive and (1) level sensitive. + /// This flag is only used when the delivery mode is Fixed. + /// When the delivery mode is NMI, SMI, or INIT, the trigger mode is always edge sensitive. + /// When the delivery mode is ExtINT, the trigger mode is always level sensitive. The timer and error interrupts are always treated as edge sensitive. + /// If the local APIC is not used in conjunction with an I/O APIC and fixed delivery mode is selected; + /// the Pentium 4, Intel Xeon, and P6 family processors will always use level-sensitive triggering, regardless if edge-sensitive triggering is selected. + /// Software should always set the trigger mode in the LVT LINT1 register to 0 (edge sensitive). Level-sensitive interrupts are not supported for LINT1. + TriggerMode OFFSET(15) NUMBITS(1) [ + /// Edge sensitive. + EdgeSensitive = 0, + /// Level sensitive. + LevelSensitive = 1 + ], + /// Remote IRR Flag (Read Only) + /// For fixed mode, level-triggered interrupts; + /// this flag is set when the local APIC accepts the interrupt for servicing and is reset when an EOI command is received from the processor. + /// The meaning of this flag is undefined for edge-triggered interrupts and other delivery modes. + RemoteIRR OFFSET(14) NUMBITS(1) [], + /// Interrupt Input Pin Polarity Specifies the polarity of the corresponding interrupt pin: + /// (0) active high or (1) active low. + InterruptInputPinPolarity OFFSET(13) NUMBITS(1) [ + /// Active high. + ActiveHigh = 0, + /// Active low. + ActiveLow = 1 + ], + /// Delivery Status (Read Only): Indicates the interrupt delivery status + DeliveryStatus OFFSET(12) NUMBITS(1) [ + /// 0 (Idle) + /// There is currently no activity for this interrupt source, + /// or the previous interrupt from this source was delivered to the processor core and accepted. + Idle = 0, + /// 1 (Send Pending) + /// Indicates that an interrupt from this source has been delivered to the processor core + /// but has not yet been accepted (see Section 11.5.5, “Local Interrupt Acceptance”). + SendPending = 1 + ], + Reserved0 OFFSET(11) NUMBITS(1) [], + /// Delivery Mode: Specifies the type of interrupt to be sent to the processor. + /// Some delivery modes will only operate as intended when used in conjunction with a specific trigger mode. + DeliveryMode OFFSET(8) NUMBITS(3) [ + /// 000 (Fixed) Delivers the interrupt specified in the vector field. + Fixed = 0b000, + /// 010 (SMI) Delivers an SMI interrupt to the processor core through + /// the processor’s local SMI signal path. + /// When using this delivery mode, the vector field should be set to 00H for future compatibility. + SMI = 0b010, + /// 100 (NMI) Delivers an NMI interrupt to the processor. The vector information is ignored. + NMI = 0b100, + /// 101 (INIT) Delivers an INIT request to the processor core, + /// which causes the processor to perform an INIT. + /// When using this delivery mode, the vector field should be set to 00H for future compatibility. + /// Not supported for the LVT CMCI register, the LVT thermal monitor register, or the LVT performance counter register. + INIT = 0b101, + /// 110 Reserved; not supported for any LVT register. + Reserved = 0b110, + /// 111 (ExtINT) Causes the processor to respond to the interrupt + /// as if the interrupt originated in an externally connected (8259A-compatible) interrupt controller. + /// A special INTA bus cycle corresponding to ExtINT, is routed to the external controller. + /// The external controller is expected to supply the vector information. + /// The APIC architecture supports only one ExtINT source in a system, usually contained in the compatibility bridge. + /// Only one processor in the system should have an LVT entry configured to use the ExtINT delivery mode. + /// Not supported for the LVT CMCI register, the LVT thermal monitor register, or the LVT performance counter register. + ExtINT = 0b111 + ], + /// Vector: Interrupt vector number. + Vector OFFSET(0) NUMBITS(8) [], + ] +} + +/// LVT LINT1 Register (FEE0 0360H) +/// Specifies interrupt delivery when an interrupt is signaled at the LINT1 pin. +pub type LvtLint1RegisterMmio = ReadWrite; + +/// A read-write copy of LVT LINT1 Register (FEE0 0360H). +/// +/// This behaves very similarly to a MMIO read-write register, but instead of doing a +/// volatile read to MMIO to get the value for each function call, a copy of the +/// register contents are stored locally in memory. +pub type LvtLint1RegisterLocal = LocalRegisterCopy; diff --git a/components/x86_vlapic/src/regs/lvt/mod.rs b/components/x86_vlapic/src/regs/lvt/mod.rs new file mode 100644 index 000000000..617b6755b --- /dev/null +++ b/components/x86_vlapic/src/regs/lvt/mod.rs @@ -0,0 +1,65 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Local Vector Table + +mod cmci; +mod error; +mod lint0; +mod lint1; +mod perfmon; +mod thermal; +mod timer; + +pub use cmci::*; +pub use error::*; +pub use lint0::*; +pub use lint1::*; +pub use perfmon::*; +pub use thermal::*; +pub use timer::*; + +pub use crate::consts::RESET_LVT_REG; + +/// A read-write copy of LVT registers. +pub struct LocalVectorTable { + /// LVT CMCI Register (FEE0 02F0H) + pub lvt_cmci: LvtCmciRegisterLocal, + /// LVT Timer Register (FEE0 0320H) + pub lvt_timer: LvtTimerRegisterLocal, + /// LVT Thermal Monitor Register (FEE0 0330H) + pub lvt_thermal: LvtThermalMonitorRegisterLocal, + /// LVT Performance Counter Register (FEE0 0340H) + pub lvt_perf_count: LvtPerformanceCounterRegisterLocal, + /// LVT LINT0 Register (FEE0 0350H) + pub lvt_lint0: LvtLint0RegisterLocal, + /// LVT LINT1 Register (FEE0 0360H) + pub lvt_lint1: LvtLint1RegisterLocal, + /// LVT Error register 0x37. + pub lvt_err: LvtErrorRegisterLocal, +} + +impl Default for LocalVectorTable { + fn default() -> Self { + LocalVectorTable { + lvt_cmci: LvtCmciRegisterLocal::new(RESET_LVT_REG), + lvt_timer: LvtTimerRegisterLocal::new(RESET_LVT_REG), + lvt_thermal: LvtThermalMonitorRegisterLocal::new(RESET_LVT_REG), + lvt_perf_count: LvtPerformanceCounterRegisterLocal::new(RESET_LVT_REG), + lvt_lint0: LvtLint0RegisterLocal::new(RESET_LVT_REG), + lvt_lint1: LvtLint1RegisterLocal::new(RESET_LVT_REG), + lvt_err: LvtErrorRegisterLocal::new(RESET_LVT_REG), + } + } +} diff --git a/components/x86_vlapic/src/regs/lvt/perfmon.rs b/components/x86_vlapic/src/regs/lvt/perfmon.rs new file mode 100644 index 000000000..6d1f3f472 --- /dev/null +++ b/components/x86_vlapic/src/regs/lvt/perfmon.rs @@ -0,0 +1,88 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use tock_registers::LocalRegisterCopy; +use tock_registers::register_bitfields; +use tock_registers::registers::ReadWrite; + +register_bitfields! { + u32, + pub LVT_PERFORMANCE_COUNTER [ + /// Reserved2 + Reserved2 OFFSET(17) NUMBITS(15) [], + /// Mask + Mask OFFSET(16) NUMBITS(1) [ + /// Not masked. + NotMasked = 0, + /// Masked. + Masked = 1 + ], + Reserved1 OFFSET(13) NUMBITS(3) [], + /// Delivery Status (Read Only): Indicates the interrupt delivery status + DeliveryStatus OFFSET(12) NUMBITS(1) [ + /// 0 (Idle) + /// There is currently no activity for this interrupt source, + /// or the previous interrupt from this source was delivered to the processor core and accepted. + Idle = 0, + /// 1 (Send Pending) + /// Indicates that an interrupt from this source has been delivered to the processor core + /// but has not yet been accepted (see Section 11.5.5, “Local Interrupt Acceptance”). + SendPending = 1 + ], + Reserved0 OFFSET(11) NUMBITS(1) [], + /// Delivery Mode: Specifies the type of interrupt to be sent to the processor. + /// Some delivery modes will only operate as intended when used in conjunction with a specific trigger mode. + DeliveryMode OFFSET(8) NUMBITS(3) [ + /// 000 (Fixed) Delivers the interrupt specified in the vector field. + Fixed = 0b000, + /// 010 (SMI) Delivers an SMI interrupt to the processor core through + /// the processor’s local SMI signal path. + /// When using this delivery mode, the vector field should be set to 00H for future compatibility. + SMI = 0b010, + /// 100 (NMI) Delivers an NMI interrupt to the processor. The vector information is ignored. + NMI = 0b100, + /// 101 (INIT) Delivers an INIT request to the processor core, + /// which causes the processor to perform an INIT. + /// When using this delivery mode, the vector field should be set to 00H for future compatibility. + /// Not supported for the LVT CMCI register, the LVT thermal monitor register, or the LVT performance counter register. + INIT = 0b101, + /// 110 Reserved; not supported for any LVT register. + Reserved = 0b110, + /// 111 (ExtINT) Causes the processor to respond to the interrupt + /// as if the interrupt originated in an externally connected (8259A-compatible) interrupt controller. + /// A special INTA bus cycle corresponding to ExtINT, is routed to the external controller. + /// The external controller is expected to supply the vector information. + /// The APIC architecture supports only one ExtINT source in a system, usually contained in the compatibility bridge. + /// Only one processor in the system should have an LVT entry configured to use the ExtINT delivery mode. + /// Not supported for the LVT CMCI register, the LVT thermal monitor register, or the LVT performance counter register. + ExtINT = 0b111 + ], + /// Vector: Interrupt vector number. + Vector OFFSET(0) NUMBITS(8) [], + ] +} + +/// LVT Performance Counter Register (FEE0 0340H) +/// Specifies interrupt delivery when a performance counter generates an interrupt on overflow (see Section 20.6.3.5.8, “Generating an Interrupt on Overflow”) +/// or when Intel PT signals a ToPA PMI (see Section 33.2.7.2). +/// This LVT entry is implementation specific, not architectural. If implemented, it is not guaranteed to be at base address FEE0 0340H. +pub type LvtPerformanceCounterRegisterMmio = ReadWrite; + +/// A read-write copy of LVT Performance Counter Register (FEE0 0340H). +/// +/// This behaves very similarly to a MMIO read-write register, but instead of doing a +/// volatile read to MMIO to get the value for each function call, a copy of the +/// register contents are stored locally in memory. +pub type LvtPerformanceCounterRegisterLocal = + LocalRegisterCopy; diff --git a/components/x86_vlapic/src/regs/lvt/thermal.rs b/components/x86_vlapic/src/regs/lvt/thermal.rs new file mode 100644 index 000000000..8c5c36e5a --- /dev/null +++ b/components/x86_vlapic/src/regs/lvt/thermal.rs @@ -0,0 +1,87 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use tock_registers::LocalRegisterCopy; +use tock_registers::register_bitfields; +use tock_registers::registers::ReadWrite; + +register_bitfields! { + u32, + pub LVT_THERMAL_MONITOR [ + /// Reserved2 + Reserved2 OFFSET(17) NUMBITS(15) [], + /// Mask + Mask OFFSET(16) NUMBITS(1) [ + /// Not masked. + NotMasked = 0, + /// Masked. + Masked = 1 + ], + Reserved1 OFFSET(13) NUMBITS(3) [], + /// Delivery Status (Read Only): Indicates the interrupt delivery status + DeliveryStatus OFFSET(12) NUMBITS(1) [ + /// 0 (Idle) + /// There is currently no activity for this interrupt source, + /// or the previous interrupt from this source was delivered to the processor core and accepted. + Idle = 0, + /// 1 (Send Pending) + /// Indicates that an interrupt from this source has been delivered to the processor core + /// but has not yet been accepted (see Section 11.5.5, “Local Interrupt Acceptance”). + SendPending = 1 + ], + Reserved0 OFFSET(11) NUMBITS(1) [], + /// Delivery Mode: Specifies the type of interrupt to be sent to the processor. + /// Some delivery modes will only operate as intended when used in conjunction with a specific trigger mode. + DeliveryMode OFFSET(8) NUMBITS(3) [ + /// 000 (Fixed) Delivers the interrupt specified in the vector field. + Fixed = 0b000, + /// 010 (SMI) Delivers an SMI interrupt to the processor core through + /// the processor’s local SMI signal path. + /// When using this delivery mode, the vector field should be set to 00H for future compatibility. + SMI = 0b010, + /// 100 (NMI) Delivers an NMI interrupt to the processor. The vector information is ignored. + NMI = 0b100, + /// 101 (INIT) Delivers an INIT request to the processor core, + /// which causes the processor to perform an INIT. + /// When using this delivery mode, the vector field should be set to 00H for future compatibility. + /// Not supported for the LVT CMCI register, the LVT thermal monitor register, or the LVT performance counter register. + INIT = 0b101, + /// 110 Reserved; not supported for any LVT register. + Reserved = 0b110, + /// 111 (ExtINT) Causes the processor to respond to the interrupt + /// as if the interrupt originated in an externally connected (8259A-compatible) interrupt controller. + /// A special INTA bus cycle corresponding to ExtINT, is routed to the external controller. + /// The external controller is expected to supply the vector information. + /// The APIC architecture supports only one ExtINT source in a system, usually contained in the compatibility bridge. + /// Only one processor in the system should have an LVT entry configured to use the ExtINT delivery mode. + /// Not supported for the LVT CMCI register, the LVT thermal monitor register, or the LVT performance counter register. + ExtINT = 0b111 + ], + /// Vector: Interrupt vector number. + Vector OFFSET(0) NUMBITS(8) [], + ] +} + +/// LVT Thermal Monitor Register (FEE0 0330H) +/// Specifies interrupt delivery when the thermal sensor generates an interrupt (see Section 15.8.2, “Thermal Monitor”). +/// This LVT entry is implementation specific, not architectural. +/// If implemented, it will always be at base address FEE0 0330H. +pub type LvtThermalMonitorRegisterMmio = ReadWrite; + +/// A read-write copy of LVT Thermal Monitor Register (FEE0 0330H). +/// +/// This behaves very similarly to a MMIO read-write register, but instead of doing a +/// volatile read to MMIO to get the value for each function call, a copy of the +/// register contents are stored locally in memory. +pub type LvtThermalMonitorRegisterLocal = LocalRegisterCopy; diff --git a/components/x86_vlapic/src/regs/lvt/timer.rs b/components/x86_vlapic/src/regs/lvt/timer.rs new file mode 100644 index 000000000..05aeccec4 --- /dev/null +++ b/components/x86_vlapic/src/regs/lvt/timer.rs @@ -0,0 +1,73 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use tock_registers::LocalRegisterCopy; +use tock_registers::register_bitfields; +use tock_registers::registers::ReadWrite; + +register_bitfields! { + u32, + pub LVT_TIMER [ + /// Reserved2 + Reserved2 OFFSET(19) NUMBITS(13) [], + /// Timer Mode + TimerMode OFFSET(17) NUMBITS(2) [ + /// (00b) one-shot mode using a count-down value + OneShot = 0b00, + /// (01b) periodic mode reloading a count-down value + Periodic = 0b01, + /// (10b) TSC-Deadline mode using absolute target value in IA32_TSC_DEADLINE MSR (see Section 11.5.4.1) + TSCDeadline = 0b10, + /// (11b) is reserved + Reserved = 0b11 + ], + /// Mask: Interrupt mask: + /// (0) enables reception of the interrupt and (1) inhibits reception of the interrupt. + /// When the local APIC handles a performance-monitoring counters interrupt, + /// it automatically sets the mask flag in the LVT performance counter register. + /// This flag is set to 1 on reset. + /// It can be cleared only by software. + Mask OFFSET(16) NUMBITS(1) [ + /// Not masked, enables reception of the interrupt. + NotMasked = 0, + /// Masked, inhibits reception of the interrupt. + Masked = 1 + ], + Reserved1 OFFSET(13) NUMBITS(3) [], + /// Delivery Status (Read Only): Indicates the interrupt delivery status + DeliveryStatus OFFSET(12) NUMBITS(1) [ + /// 0 (Idle) + /// There is currently no activity for this interrupt source, + /// or the previous interrupt from this source was delivered to the processor core and accepted. + Idle = 0, + /// 1 (Send Pending) + /// Indicates that an interrupt from this source has been delivered to the processor core + /// but has not yet been accepted (see Section 11.5.5, “Local Interrupt Acceptance”). + SendPending = 1 + ], + Reserved0 OFFSET(8) NUMBITS(4) [], + /// Vector: Interrupt vector number. + Vector OFFSET(0) NUMBITS(8) [], + ] +} + +/// LVT Timer Register (FEE0 0320H) +pub type LvtTimerRegisterMmio = ReadWrite; + +/// A read-write copy of LVT Timer Register (FEE0 0320H). +/// +/// This behaves very similarly to a MMIO read-write register, but instead of doing a +/// volatile read to MMIO to get the value for each function call, a copy of the +/// register contents are stored locally in memory. +pub type LvtTimerRegisterLocal = LocalRegisterCopy; diff --git a/components/x86_vlapic/src/regs/mod.rs b/components/x86_vlapic/src/regs/mod.rs new file mode 100644 index 000000000..c7ee9884e --- /dev/null +++ b/components/x86_vlapic/src/regs/mod.rs @@ -0,0 +1,131 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod lvt; +pub mod timer; + +mod apic_base; +mod dfr; +mod esr; +mod icr; +mod svr; + +pub use apic_base::*; +pub use dfr::*; +pub use esr::*; +pub use icr::*; +pub use svr::*; + +use tock_registers::register_structs; +use tock_registers::registers::{ReadOnly, ReadWrite, WriteOnly}; + +use lvt::{ + LvtCmciRegisterMmio, LvtErrorRegisterMmio, LvtLint0RegisterMmio, LvtLint1RegisterMmio, + LvtPerformanceCounterRegisterMmio, LvtThermalMonitorRegisterMmio, LvtTimerRegisterMmio, +}; +use timer::DivideConfigurationRegisterMmio; + +register_structs! { + #[allow(non_snake_case)] + pub LocalAPICRegs { + (0x00 => _reserved0), + /// Local APIC ID register (VID): the 32-bit field located at offset 000H on the virtual-APIC page. + (0x20 => pub ID: ReadWrite), + (0x24 => _reserved1), + /// Local APIC Version register (VVER): the 32-bit field located at offset 030H on the virtual-APIC page. + (0x30 => pub VERSION: ReadOnly), + (0x34 => _reserved2), + /// Virtual task-priority register (VTPR): the 32-bit field located at offset 080H on the virtual-APIC page. + (0x80 => pub TPR: ReadWrite), + (0x84 => _reserved3), + /// Virtual APIC-priority register (VAPR): the 32-bit field located at offset 090H on the virtual-APIC page. + (0x90 => pub APR: ReadOnly), + (0x94 => _reserved4), + /// Virtual processor-priority register (VPPR): the 32-bit field located at offset 0A0H on the virtual-APIC page. + (0xA0 => pub PPR: ReadWrite), + (0xA4 => _reserved5), + /// Virtual end-of-interrupt register (VEOI): the 32-bit field located at offset 0B0H on the virtual-APIC page. + (0xB0 => pub EOI: WriteOnly), + (0xB4 => _reserved6), + /// Virtual Remote Read Register (RRD): the 32-bit field located at offset 0C0H on the virtual-APIC page. + (0xC0 => pub RRD: ReadOnly), + (0xC4 => _reserved7), + /// Virtual Logical Destination Register (LDR): the 32-bit field located at offset 0D0H on the virtual-APIC page. + (0xD0 => pub LDR: ReadWrite), + (0xD4 => _reserved8), + /// Virtual Destination Format Register (DFR): the 32-bit field located at offset 0E0H on the virtual-APIC page. + (0xE0 => pub DFR: DestinationFormatRegisterMmio), + (0xE4 => _reserved9), + /// Virtual Spurious Interrupt Vector Register (SVR): the 32-bit field located at offset 0F0H on the virtual-APIC page. + (0xF0 => pub SVR: SpuriousInterruptVectorRegisterMmio), + (0xF4 => _reserved10), + /// Virtual interrupt-service register (VISR): + /// the 256-bit value comprising eight non-contiguous 32-bit fields at offsets + /// 100H, 110H, 120H, 130H, 140H, 150H, 160H, and 170H on the virtual-APIC page. + (0x100 => pub ISR: [ReadWrite; 8]), + /// Virtual trigger-mode register (VTMR): + /// the 256-bit value comprising eight non-contiguous 32-bit fields at offsets + /// 180H, 190H, 1A0H, 1B0H, 1C0H, 1D0H, 1E0H, and 1F0H on the virtual-APIC page. + (0x180 => pub TMR: [ReadOnly; 8]), + /// Virtual interrupt-request register (VIRR): + /// the 256-bit value comprising eight non-contiguous 32-bit fields at offsets + /// 200H, 210H, 220H, 230H, 240H, 250H, 260H, and 270H on the virtual-APIC page. + /// Bit x of the VIRR is at bit position (x & 1FH) at offset (200H | ((x & E0H) » 1)). + /// The processor uses only the low 4 bytes of each of the 16-Byte fields at offsets 200H, 210H, 220H, 230H, 240H, 250H, 260H, and 270H. + (0x200 => pub IRR: [ReadOnly; 8]), + /// Virtual error-status register (VESR): the 32-bit field located at offset 280H on the virtual-APIC page. + (0x280 => pub ESR: ErrorStatusRegisterMmio), + (0x284 => _reserved11), + /// Virtual LVT Corrected Machine Check Interrupt (CMCI) Register + (0x2F0 => pub LVT_CMCI: LvtCmciRegisterMmio), + (0x2F4 => _reserved12), + /// Virtual Interrupt Command Register (ICR): the 64-bit field located at offset 300H on the virtual-APIC page. + (0x300 => pub ICR_LO: InterruptCommandRegisterLowMmio), + (0x304 => _reserved13), + (0x310 => pub ICR_HI: InterruptCommandRegisterHighMmio), + (0x314 => _reserved14), + /// Virtual LVT Timer Register: the 32-bit field located at offset 320H on the virtual-APIC page. + (0x320 => pub LVT_TIMER: LvtTimerRegisterMmio), + (0x324 => _reserved15), + /// Virtual LVT Thermal Sensor register: the 32-bit field located at offset 330H on the virtual-APIC page. + (0x330 => pub LVT_THERMAL: LvtThermalMonitorRegisterMmio), + (0x334 => _reserved16), + /// Virtual LVT Performance Monitoring Counters register: the 32-bit field located at offset 340H on the virtual-APIC page. + (0x340 => pub LVT_PMI: LvtPerformanceCounterRegisterMmio), + (0x344 => _reserved17), + /// Virtual LVT LINT0 register: the 32-bit field located at offset 350H on the virtual-APIC page. + (0x350 => pub LVT_LINT0: LvtLint0RegisterMmio), + (0x354 => _reserved18), + /// Virtual LVT LINT1 register: the 32-bit field located at offset 360H on the virtual-APIC page. + (0x360 => pub LVT_LINT1: LvtLint1RegisterMmio), + (0x364 => _reserved19), + /// Virtual LVT Error register: the 32-bit field located at offset 370H on the virtual-APIC page. + (0x370 => pub LVT_ERROR: LvtErrorRegisterMmio), + (0x374 => _reserved20), + /// Virtual Initial Count Register (for Timer): the 32-bit field located at offset 380H on the virtual-APIC page. + (0x380 => pub ICR_TIMER: ReadWrite), + (0x384 => _reserved21), + /// Virtual Current Count Register (for Timer): the 32-bit field located at offset 390H on the virtual-APIC page. + (0x390 => pub CCR_TIMER: ReadOnly), + (0x394 => _reserved22), + /// Virtual Divide Configuration Register (for Timer): the 32-bit field located at offset 3E0H on the virtual-APIC page. + (0x3E0 => pub DCR_TIMER: DivideConfigurationRegisterMmio), + (0x3E4 => _reserved23), + /// Virtual SELF IPI Register: the 32-bit field located at offset 3F0H on the virtual-APIC page. + /// Available only in x2APIC mode. + (0x3F0 => pub SELF_IPI: WriteOnly), + (0x3F4 => _reserved24), + (0x1000 => @END), + } +} diff --git a/components/x86_vlapic/src/regs/svr.rs b/components/x86_vlapic/src/regs/svr.rs new file mode 100644 index 000000000..2853fd4c6 --- /dev/null +++ b/components/x86_vlapic/src/regs/svr.rs @@ -0,0 +1,84 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use tock_registers::LocalRegisterCopy; +use tock_registers::register_bitfields; +use tock_registers::registers::ReadWrite; + +register_bitfields! { + u32, + pub SPURIOUS_INTERRUPT_VECTOR [ + /// Reserved2 + Reserved1 OFFSET(13) NUMBITS(19) [], + /// Suppress EOI Broadcasts + /// Determines whether an EOI for a level-triggered interrupt causes EOI messages to be broadcast to the I/O APICs (0) or not (1). + /// See Section 11.8.5. + /// The default value for this bit is 0, indicating that EOI broadcasts are performed. + /// This bit is reserved to 0 if the processor does not support EOI-broadcast suppression. + /// - 0: Disabled + /// - 1: Enabled + EOIBroadcastSuppression OFFSET(12) NUMBITS(1) [ + /// Disabled + Disabled = 0, + /// Enabled + Enabled = 1 + ], + Reserved0 OFFSET(10) NUMBITS(2) [], + /// Focus Processor Checking + /// Determines if focus processor checking is enabled (0) or disabled (1) when using the lowestpriority delivery mode. + /// In Pentium 4 and Intel Xeon processors, this bit is reserved and should be cleared to 0. + /// - 0: Enabled + /// - 1: Disabled + FocusProcessorChecking OFFSET(9) NUMBITS(1) [ + /// Enabled + Enabled = 0, + /// Disabled + Disabled = 1 + ], + /// APIC Software Enable/Disable + /// Allows software to temporarily enable (1) or disable (0) the local APIC + /// (see Section 11.4.3, “Enabling or Disabling the Local APIC”). + /// - 0: APIC Disabled + /// - 1: APIC Enabled + APICSoftwareEnableDisable OFFSET(8) NUMBITS(1) [ + /// APIC Disabled + Disabled = 0, + /// APIC Enabled + Enabled = 1 + ], + /// Spurious Vector. + /// Determines the vector number to be delivered to the processor when the local APIC generates a spurious vector. + /// - (Pentium 4 and Intel Xeon processors.) Bits 0 through 7 of the this field are programmable by software. + /// - (P6 family and Pentium processors). Bits 4 through 7 of the this field are programmable by software, and bits 0 through 3 are hardwired to logical ones. Software writes to bits 0 through 3 have no effect. + /// For the P6 family and Pentium processors, bits 0 through 3 are always 1. + SPURIOUS_VECTOR OFFSET(0) NUMBITS(8) [], + ] +} + +/// Spurious-Interrupt Vector Register using MMIO. +/// - Address: FEE0 00F0H +/// - Value after reset: 0000 00FFH +/// +/// A special situation may occur when a processor raises its task priority to be greater than or equal to the level of the interrupt for which the processor INTR signal is currently being asserted. +/// If at the time the INTA cycle is issued, the interrupt that was to be dispensed has become masked (programmed by software), the local APIC will deliver a spurious-interrupt vector. +/// Dispensing the spurious-interrupt vector does not affect the ISR, so the handler for this vector should return without an EOI. +pub type SpuriousInterruptVectorRegisterMmio = ReadWrite; + +/// A read-write copy of Spurious-Interrupt Vector Register (FEE0 00F0H). +/// +/// This behaves very similarly to a MMIO read-write register, but instead of doing a +/// volatile read to MMIO to get the value for each function call, a copy of the +/// register contents are stored locally in memory. +pub type SpuriousInterruptVectorRegisterLocal = + LocalRegisterCopy; diff --git a/components/x86_vlapic/src/regs/timer/dcr.rs b/components/x86_vlapic/src/regs/timer/dcr.rs new file mode 100644 index 000000000..a0687a6ea --- /dev/null +++ b/components/x86_vlapic/src/regs/timer/dcr.rs @@ -0,0 +1,57 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use tock_registers::LocalRegisterCopy; +use tock_registers::register_bitfields; +use tock_registers::registers::ReadWrite; + +register_bitfields! { + u32, + pub DCR_TIMER [ + /// Reserved + Reserved OFFSET(4) NUMBITS(28) [], + /// Divide Value (bits 0, 1, and 3) + /// 000: Divide by 2 + /// 001: Divide by 4 + /// 010: Divide by 8 + /// 011: Divide by 16 + /// 100: Divide by 32 + /// 101: Divide by 64 + /// 110: Divide by 128 + /// 111: Divide by 1 + DivideValue OFFSET(0) NUMBITS(4) [ + DivideBy2 = 0b0000, + DivideBy4 = 0b0001, + DivideBy8 = 0b0010, + DivideBy16 = 0b0011, + DivideBy32 = 0b1000, + DivideBy64 = 0b1001, + DivideBy128 = 0b1010, + DivideBy1 = 0b1011 + ] + ] +} + +/// Divide Configuration Register (FEE0 03E0H) using MMIO. +/// - Address: FEE0 03E0H +/// - Value after reset: 0H +pub type DivideConfigurationRegisterMmio = ReadWrite; + +/// A read-write copy of the Divide Configuration Register (FEE0 03E0H). +/// +/// This behaves very similarly to a MMIO read-write register, but instead of doing a +/// volatile read to MMIO to get the value for each function call, a copy of the +/// register contents are stored locally in memory. +#[allow(dead_code)] +pub type DivideConfigurationRegisterLocal = LocalRegisterCopy; diff --git a/components/x86_vlapic/src/regs/timer/mod.rs b/components/x86_vlapic/src/regs/timer/mod.rs new file mode 100644 index 000000000..69b5908b4 --- /dev/null +++ b/components/x86_vlapic/src/regs/timer/mod.rs @@ -0,0 +1,17 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod dcr; + +pub use dcr::*; diff --git a/components/x86_vlapic/src/timer.rs b/components/x86_vlapic/src/timer.rs new file mode 100644 index 000000000..d09127da0 --- /dev/null +++ b/components/x86_vlapic/src/timer.rs @@ -0,0 +1,409 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use alloc::boxed::Box; +use axerrno::{AxResult, ax_err}; +use axvisor_api::{ + time::{self, current_ticks, register_timer, ticks_to_nanos, ticks_to_time}, + vmm::{VCpuId, VMId, inject_interrupt}, +}; + +use crate::{ + consts::RESET_LVT_REG, + regs::lvt::{ + LVT_TIMER::{self, TimerMode::Value as TimerMode}, + LvtTimerRegisterLocal, + }, +}; + +/// A virtual local APIC timer. (SDM Vol. 3C, Section 11.5.4) +/// +/// This struct virtualizes the access to 4 registers in the Local APIC: +/// +/// - LVT Timer Register. (SDM Vol. 3A, Section 11.5.1, Figure 11-8, offset 0x320, MSR 0x832, Read/Write) +/// - Divide Configuration Register. (SDM Vol. 3A, Section 11.5.4, Figure 11-10, offset 0x3E0, MSR 0x83E, Read/Write) +/// - Initial Count Register. (SDM Vol. 3A, Section 11.5.4, Figure 11-11, offset 0x380, MSR 0x838, Read/Write) +/// - Current Count Register. (SDM Vol. 3A, Section 11.5.4, Figure 11-11, offset 0x390, MSR 0x839, Read Only) +/// +/// The timer works in the following way: +/// +/// - Timer is started by and only by writing to the Initial Count Register. +/// - The deadline is determined by the Initial Count Register and the Divide Configuration Register, at the time of the start. +/// - Any modification to the Divide Configuration Register or the LVT Timer Register will not affect the current timer. +/// - Any write to the Initial Count Register will restart the timer. +/// - The value of the LVT Timer is read, at the time the deadline is reached, to determine +/// - if an interrupt should be generated (not masked), +/// - if the timer should be restarted (periodic mode), and +/// - the interrupt vector number to be used. +/// - The delivery status field in the LVT Timer Register is not supported and always returns 0. +/// - The timer stops when: +/// - the deadline is reached, and the timer is in one-shot mode, or +/// - a 0 is written to the Initial Count Register. +pub struct ApicTimer { + // the raw value of writable registers + /// Local Vector Table Timer Register. These's another copy in [`VirtualApicRegs`](crate::VirtualApicRegs), but we + /// keep a separate copy here for easier access. + lvt_timer_register: LvtTimerRegisterLocal, + /// Initial Count Register. This is the value that determines when the timer will fire. + initial_count_register: u32, + /// Divide Configuration Register. This determines the frequency of the timer. + divide_configuration_register: u32, + + // internal states + divide_shift: u8, + last_start_ticks: u64, + deadline_ns: u64, + + // temporary fields untils we find a permanent place for apic and its timer + cancel_token: Option, + where_am_i: (VMId, VCpuId), // (vm_id, vcpu_id) +} + +impl ApicTimer { + pub(crate) const fn new(vm_id: VMId, vcpu_id: VCpuId) -> Self { + Self { + lvt_timer_register: LvtTimerRegisterLocal::new(RESET_LVT_REG), // masked, one-shot, vector 0 + initial_count_register: 0, // 0 (stopped) + divide_configuration_register: 0, // divide by 2 + + divide_shift: 1, // as `divide_configuration_register` is 0, the shift is 1 (divide by 2) + last_start_ticks: 0, + deadline_ns: 0, + cancel_token: None, + where_am_i: (vm_id, vcpu_id), + } + } + + // /// Check if an interrupt generated. if yes, update it's states. + // pub fn check_interrupt(&mut self) -> bool { + // if self.deadline_ns == 0 { + // false + // } else if H::current_time_nanos() >= self.deadline_ns { + // if self.is_periodic() { + // self.deadline_ns += self.interval_ns(); + // } else { + // self.deadline_ns = 0; + // } + // !self.is_masked() + // } else { + // false + // } + // } + + #[allow(dead_code)] + pub fn read_lvt(&self) -> u32 { + self.lvt_timer_register.get() + } + + pub fn write_lvt(&mut self, mut value: u32) -> AxResult { + // valid bits: 0-7, 12, 16-18 + const LVT_MASK: u32 = 0x0007_10FF; + + value &= LVT_MASK; + self.lvt_timer_register.set(value); + Ok(()) + } + + #[allow(dead_code)] + pub fn read_icr(&self) -> u32 { + self.initial_count_register + } + + pub fn write_icr(&mut self, value: u32) -> AxResult { + // stop the timer no matter whether it is started, and no matter the value + self.stop_timer()?; + self.initial_count_register = value; + + if value > 0 { + self.start_timer() + } else { + Ok(()) + } + } + + /// Read from the Divide Configuration Register. + #[allow(dead_code)] + pub fn read_dcr(&self) -> u32 { + self.divide_configuration_register + } + + /// Write to the Divide Configuration Register. + pub fn write_dcr(&mut self, mut value: u32) { + const DCR_MASK: u32 = 0b1011; + + value &= DCR_MASK; + let shift = match value { + 0b0000 => 1, // divide by 2 + 0b0001 => 2, // divide by 4 + 0b0010 => 3, // divide by 8 + 0b0011 => 4, // divide by 16 + 0b1000 => 5, // divide by 32 + 0b1001 => 6, // divide by 64 + 0b1010 => 7, // divide by 128 + 0b1011 => 0, // divide by 1 + _ => unreachable!( + "internal error: invalid divide configuration register value after mask" + ), + }; + + self.divide_configuration_register = value; + self.divide_shift = shift as u8; + } + + /// Current Count Register. + pub fn read_ccr(&self) -> u32 { + if !self.is_started() { + return 0; + } + let remaining_ns = self.deadline_ns.wrapping_sub(time::current_time_nanos()); + let remaining_ticks = time::nanos_to_ticks(remaining_ns); + (remaining_ticks >> self.divide_shift) as _ + } + + /// Get the timer mode. + pub fn timer_mode(&self) -> TimerMode { + self.lvt_timer_register + .read_as_enum(LVT_TIMER::TimerMode) + .unwrap() // just panic if the value is invalid + } + + /// Check whether the timer interrupt is masked. + #[allow(dead_code)] + pub fn is_masked(&self) -> bool { + self.lvt_timer_register.is_set(LVT_TIMER::Mask) + } + + /// The timer interrupt vector number. + pub fn vector(&self) -> u8 { + self.lvt_timer_register.read(LVT_TIMER::Vector) as u8 + } + + /// Check whether the timer is started. + pub fn is_started(&self) -> bool { + // these two conditions are equivalent actually, we check both for clarity and robustness + self.initial_count_register > 0 && self.cancel_token.is_some() + } + + /// Restart the timer. Will not start the timer if it is not started. + pub fn restart_timer(&mut self) -> AxResult { + if !self.is_started() { + Ok(()) + } else { + self.stop_timer()?; + self.start_timer() + } + } + + /// Start the timer. + pub fn start_timer(&mut self) -> AxResult { + if self.is_started() { + return ax_err!(BadState, "Timer already started"); + } + + let current_ticks = current_ticks(); + let deadline_ticks = + current_ticks + ((self.initial_count_register as u64) << self.divide_shift); + let (vm_id, vcpu_id) = self.where_am_i; + let vector = self.vector(); + + trace!( + "vlapic @ (vm {vm_id}, vcpu {vcpu_id}) starts timer @ tick {current_ticks:?}, deadline tick {deadline_ticks:?}" + ); + + self.last_start_ticks = current_ticks; + self.deadline_ns = ticks_to_nanos(deadline_ticks); + + self.cancel_token = Some(register_timer( + ticks_to_time(deadline_ticks), + Box::new(move |_| { + // TODO: read the LVT Timer Register here + trace!( + "vlapic @ (vm {vm_id}, vcpu {vcpu_id}) timer expired, inject interrupt {vector}" + ); + inject_interrupt(vm_id, vcpu_id, vector); + }), + )); + + Ok(()) + } + + pub fn stop_timer(&mut self) -> AxResult { + // TODO: maybe disable irq here? + if self.is_started() { + self.last_start_ticks = 0; + self.deadline_ns = 0; + + time::cancel_timer(self.cancel_token.take().unwrap()); + } else { + warn!("`stop_timer` called when timer is not started, bad operation tolerated"); + } + + Ok(()) + } + + /// Whether the timer mode is periodic. + pub fn is_periodic(&self) -> bool { + self.timer_mode() == TimerMode::Periodic + } + + // /// Set LVT Timer Register. + // pub fn set_lvt_timer(&mut self, bits: u32) -> RvmResult { + // let timer_mode = bits.get_bits(17..19); + // if timer_mode == TimerMode::TscDeadline as _ { + // return rvm_err!(Unsupported); // TSC deadline mode was not supported + // } else if timer_mode == 0b11 { + // return rvm_err!(InvalidParam); // reserved + // } + // self.lvt_timer_bits = bits; + // self.start_timer(); + // Ok(()) + // } + + // /// Set Initial Count Register. + // pub fn set_initial_count(&mut self, initial: u32) -> RvmResult { + // self.initial_count = initial; + // self.start_timer(); + // Ok(()) + // } + + // /// Set Divide Configuration Register. + // pub fn set_divide(&mut self, dcr: u32) -> RvmResult { + // let shift = (dcr & 0b11) | ((dcr & 0b1000) >> 1); + // self.divide_shift = (shift + 1) as u8 & 0b111; + // self.start_timer(); + // Ok(()) + // } + + // const fn interval_ns(&self) -> u64 { + // (self.initial_count as u64 * APIC_CYCLE_NANOS) << self.divide_shift + // } + + // fn start_timer(&mut self) { + // if self.initial_count != 0 { + // self.last_start_cycle = H::current_time_nanos(); + // self.deadline_ns = self.last_start_cycle + self.interval_ns(); + // } else { + // self.deadline_ns = 0; + // } + // } +} + +#[cfg(test)] +mod tests { + use crate::regs::lvt::LVT_TIMER::TimerMode::Value as TimerMode; + use crate::timer::ApicTimer; + use axvisor_api::vmm::{VCpuId, VMId}; + + #[test] + fn test_apic_timer_creation() { + let vm_id = VMId::from(1 as usize); + let vcpu_id = VCpuId::from(0 as usize); + let timer = ApicTimer::new(vm_id, vcpu_id); + // Initial state should be stopped + assert!(!timer.is_started()); + assert_eq!(timer.read_icr(), 0); + assert_eq!(timer.read_dcr(), 0); + // assert_eq!(timer.read_ccr(), 0); + assert!(timer.is_masked()); + assert_eq!(timer.timer_mode(), TimerMode::OneShot); + assert_eq!(timer.vector(), 0); + } + + #[test] + fn test_lvt_register_operations() { + let vm_id = VMId::from(1 as usize); + let vcpu_id = VCpuId::from(0 as usize); + let mut timer = ApicTimer::new(vm_id, vcpu_id); + + // Test LVT write with valid bits + assert!(timer.write_lvt(0x000710FF).is_ok()); + assert_eq!(timer.read_lvt() & 0x000710FF, 0x000710FF); + + // Test LVT write with invalid bits (should be masked) + assert!(timer.write_lvt(0xFFFFFFFF).is_ok()); + assert_eq!(timer.read_lvt() & !0x000710FF, 0); + + // Test vector number + assert!(timer.write_lvt(0x50).is_ok()); // vector 0x50 + assert_eq!(timer.vector(), 0x50); + } + + #[test] + fn test_divide_configuration_register() { + let vm_id = VMId::from(1 as usize); + let vcpu_id = VCpuId::from(0 as usize); + let mut timer = ApicTimer::new(vm_id, vcpu_id); + + // Test different divide values + timer.write_dcr(0b0000); // divide by 2 + assert_eq!(timer.read_dcr(), 0b0000); + + timer.write_dcr(0b0001); // divide by 4 + assert_eq!(timer.read_dcr(), 0b0001); + + timer.write_dcr(0b1011); // divide by 1 + assert_eq!(timer.read_dcr(), 0b1011); + + // Test invalid bits are masked + timer.write_dcr(0xFFFFFFFF); + assert_eq!(timer.read_dcr() & !0b1011, 0); + } + + #[test] + fn test_timer_mode() { + let vm_id = VMId::from(1 as usize); + let vcpu_id = VCpuId::from(0 as usize); + let mut timer = ApicTimer::new(vm_id, vcpu_id); + + // Default should be one-shot + assert_eq!(timer.timer_mode(), TimerMode::OneShot); + assert!(!timer.is_periodic()); + + // Set periodic mode (bit 17 = 1) + assert!(timer.write_lvt(0x20000).is_ok()); + assert_eq!(timer.timer_mode(), TimerMode::Periodic); + assert!(timer.is_periodic()); + } + + #[test] + fn test_timer_mask() { + let vm_id = VMId::from(1 as usize); + let vcpu_id = VCpuId::from(0 as usize); + let mut timer = ApicTimer::new(vm_id, vcpu_id); + + // Default should be masked + assert!(timer.is_masked()); + + // Unmask timer (bit 16 = 0) + assert!(timer.write_lvt(0x50).is_ok()); // vector 0x50, not masked + assert!(!timer.is_masked()); + + // Mask timer (bit 16 = 1) + assert!(timer.write_lvt(0x10050).is_ok()); // vector 0x50, masked + assert!(timer.is_masked()); + } + + #[test] + fn test_multiple_timers() { + let vm_id = VMId::from(1 as usize); + let timer1 = ApicTimer::new(vm_id, VCpuId::from(0 as usize)); + let timer2 = ApicTimer::new(vm_id, VCpuId::from(1 as usize)); + + // Both timers should be independent + assert!(!timer1.is_started()); + assert!(!timer2.is_started()); + assert_eq!(timer1.read_icr(), timer2.read_icr()); + assert_eq!(timer1.read_dcr(), timer2.read_dcr()); + } +} diff --git a/components/x86_vlapic/src/utils.rs b/components/x86_vlapic/src/utils.rs new file mode 100644 index 000000000..131065566 --- /dev/null +++ b/components/x86_vlapic/src/utils.rs @@ -0,0 +1,75 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Find the last (most significant) bit set in a 32-bit value. +/// +/// Bits are numbered starting at 0 (the least significant bit). +/// A return value of `INVALID_BIT_INDEX` indicates that the input value was zero, +/// and no bits are set. +/// +/// # Parameters +/// - `value`: A `u32` input value. +/// +/// # Returns +/// - Zero-based bit index of the most significant bit set, or `INVALID_BIT_INDEX` if `value` is zero. +pub fn fls32(value: u32) -> u16 { + const INVALID_BIT_INDEX: u16 = 0xFFFF; // Define invalid bit index for zero input + if value == 0 { + return INVALID_BIT_INDEX; + } + 31 - value.leading_zeros() as u16 +} + +#[cfg(test)] +mod tests { + use super::*; + + const INVALID_BIT_INDEX: u16 = 0xFFFF; + + #[test] + fn test_fls32() { + // Test case: input is 0, no bits set + assert_eq!(fls32(0x0), INVALID_BIT_INDEX); + + // Test case: input is 1 (0b00000001), bit 0 is set + assert_eq!(fls32(0x01), 0); + + // Test case: input is 128 (0b10000000), bit 7 is set + assert_eq!(fls32(0x80), 7); + + // Test case: input is 0x80000001, bit 31 is the most significant bit set + assert_eq!(fls32(0x80000001), 31); + + // Test case: input is 0xFFFFFFFF, bit 31 is the most significant bit set + assert_eq!(fls32(0xFFFFFFFF), 31); + + // Test case: input is 0x7FFFFFFF, bit 30 is the most significant bit set + assert_eq!(fls32(0x7FFFFFFF), 30); + } + + #[test] + fn test_fls32_edge_cases() { + // Test case: input is 0x00000010, bit 4 is set + assert_eq!(fls32(0x10), 4); + + // Test case: input is 0x00001000, bit 12 is set + assert_eq!(fls32(0x1000), 12); + + // Test case: input is the maximum value (0xFFFFFFFF), bit 31 is set + assert_eq!(fls32(u32::MAX), 31); + + // Test case: input is 0x8000_0000 (highest bit set), bit 31 is set + assert_eq!(fls32(0x8000_0000), 31); + } +} diff --git a/components/x86_vlapic/src/vlapic.rs b/components/x86_vlapic/src/vlapic.rs new file mode 100644 index 000000000..69b2a8fe1 --- /dev/null +++ b/components/x86_vlapic/src/vlapic.rs @@ -0,0 +1,923 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use core::ptr::NonNull; + +use axvisor_api::vmm::{VCpuId, VMId}; +use bit::BitIndex; +use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; + +use axaddrspace::{HostPhysAddr, device::AccessWidth}; +use axerrno::{AxError, AxResult, ax_err_type}; +use axvisor_api::{memory::PhysFrame, vmm}; + +use crate::consts::{ + APIC_LVT_DS, APIC_LVT_M, APIC_LVT_VECTOR, ApicRegOffset, LAPIC_TRIG_EDGE, + RESET_SPURIOUS_INTERRUPT_VECTOR, +}; +use crate::regs::{ + APIC_BASE, ApicBaseRegisterMsr, + DESTINATION_FORMAT::{self, Model::Value as APICDestinationFormat}, + ERROR_STATUS, ErrorStatusRegisterLocal, ErrorStatusRegisterValue, INTERRUPT_COMMAND_HIGH, + INTERRUPT_COMMAND_LOW::{ + self, DeliveryMode::Value as APICDeliveryMode, + DestinationShorthand::Value as APICDestination, + }, + InterruptCommandRegisterLowLocal, LocalAPICRegs, SPURIOUS_INTERRUPT_VECTOR, + SpuriousInterruptVectorRegisterLocal, + lvt::{ + LVT_CMCI, LVT_ERROR, LVT_LINT0, LVT_LINT1, LVT_PERFORMANCE_COUNTER, LVT_THERMAL_MONITOR, + LVT_TIMER, LocalVectorTable, + }, +}; +use crate::{timer::ApicTimer, utils::fls32}; + +pub use crate::regs::lvt::LVT_TIMER::TimerMode::Value as TimerMode; + +/// Virtual-APIC Registers. +pub struct VirtualApicRegs { + /// The virtual-APIC page is a 4-KByte region of memory + /// that the processor uses to virtualize certain accesses to APIC registers and to manage virtual interrupts. + /// The physical address of the virtual-APIC page is the virtual-APIC address, + /// a 64-bit VM-execution control field in the VMCS (see Section 25.6.8). + virtual_lapic: NonNull, + + /// Todo: distinguish between APIC ID and vCPU ID. + vapic_id: u32, + esr_pending: ErrorStatusRegisterLocal, + esr_firing: i32, + + virtual_timer: ApicTimer, + + /// Vector number for the highest priority bit that is set in the ISR + isrv: u32, + + apic_base: ApicBaseRegisterMsr, + + /// Copies of some registers in the virtual APIC page, + /// to be able to detect what changed (e.g. svr_last) + svr_last: SpuriousInterruptVectorRegisterLocal, + /// Copies of some registers in the virtual APIC page, + /// to maintain a coherent snapshot of the register (e.g. lvt_last) + lvt_last: LocalVectorTable, + apic_page: PhysFrame, +} + +impl VirtualApicRegs { + /// Create new virtual-APIC registers by allocating a 4-KByte page for the virtual-APIC page. + pub fn new(vm_id: VMId, vcpu_id: VCpuId) -> Self { + let apic_frame = PhysFrame::alloc_zero().expect("allocate virtual-APIC page failed"); + Self { + // virtual-APIC ID is the same as the VCPU ID. + vapic_id: vcpu_id as _, + esr_pending: ErrorStatusRegisterLocal::new(0), + esr_firing: 0, + virtual_lapic: NonNull::new(apic_frame.as_mut_ptr().cast()).unwrap(), + apic_page: apic_frame, + svr_last: SpuriousInterruptVectorRegisterLocal::new(RESET_SPURIOUS_INTERRUPT_VECTOR), + lvt_last: LocalVectorTable::default(), + isrv: 0, + apic_base: ApicBaseRegisterMsr::new(0), + virtual_timer: ApicTimer::new(vm_id, vcpu_id), + } + } + + const fn regs(&self) -> &LocalAPICRegs { + unsafe { self.virtual_lapic.as_ref() } + } + + /// Virtual-APIC address (64 bits). + /// This field contains the physical address of the 4-KByte virtual-APIC page. + /// The processor uses the virtual-APIC page to virtualize certain accesses to APIC registers and to manage virtual interrupts; + /// see Chapter 30. + pub fn virtual_apic_page_addr(&self) -> HostPhysAddr { + self.apic_page.start_paddr() + } + + /// Gets the APIC base MSR value. + #[allow(dead_code)] + pub fn apic_base(&self) -> u64 { + self.apic_base.get() + } + + /// Returns whether the x2APIC mode is enabled. + pub fn is_x2apic_enabled(&self) -> bool { + self.apic_base.is_set(APIC_BASE::XAPIC_ENABLED) + && self.apic_base.is_set(APIC_BASE::X2APIC_Enabled) + } + + /// Returns whether the xAPIC mode is enabled. + #[allow(dead_code)] + pub fn is_xapic_enabled(&self) -> bool { + self.apic_base.is_set(APIC_BASE::XAPIC_ENABLED) + && !self.apic_base.is_set(APIC_BASE::X2APIC_Enabled) + } + + /// Returns the current timer mode. + pub fn timer_mode(&self) -> AxResult { + self.regs() + .LVT_TIMER + .read_as_enum(LVT_TIMER::TimerMode) + .ok_or_else(|| ax_err_type!(InvalidData, "Failed to read timer mode from LVT_TIMER")) + } + + /// 30.1.4 EOI Virtualization + /// IF any bits set in VISR + /// THEN SVI := highest index of bit set in VISR + /// ELSE SVI := 0; + /// FI; + fn find_isrv(&self) -> u32 { + let mut isrv = 0; + /* i ranges effectively from 7 to 1 */ + for i in (1..8).rev() { + let val = self.regs().ISR[i].get() as u32; + if val != 0 { + isrv = ((i as u32) << 5) | fls32(val) as u32; + break; + } + } + + isrv + } + + fn update_ppr(&mut self) { + let isrv = self.isrv; + let tpr = self.regs().TPR.get(); + // IF VTPR[7:4] ≥ SVI[7:4] + let ppr = if prio(tpr) >= prio(isrv) { + // THEN VPPR := VTPR & FFH; + tpr + } else { + // ELSE VPPR := SVI & F0H; + isrv & 0xf0 + }; + self.regs().PPR.set(ppr as _); + } + + /// Process the EOI operation triggered by a write to the EOI register. + /// 11.8.5 Signaling Interrupt Servicing Completion + /// 30.1.4 EOI Virtualization + fn process_eoi(&mut self) { + let vector = self.isrv; + + if vector == 0 { + return; + } + + let (idx, bitpos) = extract_index_and_bitpos_u32(vector); + + // Upon receiving an EOI, the APIC clears the highest priority bit in the ISR + // and dispatches the next highest priority interrupt to the processor. + + // VISR[Vector] := 0; (see Section 30.1.1 for definition of VISR) + let mut isr = self.regs().ISR[idx].get(); + isr &= !(1 << bitpos); + self.regs().ISR[idx].set(isr); + + // IF any bits set in VISR + // THEN SVI := highest index of bit set in VISR + // ELSE SVI := 0; + self.isrv = self.find_isrv(); + + // perform PPR virtualiation (see Section 30.1.3); + self.update_ppr(); + + // The trigger mode register (TMR) indicates the trigger mode of the interrupt (see Figure 11-20). + // Upon acceptance of an interrupt into the IRR, the corresponding TMR bit is cleared for edge-triggered interrupts and set for leveltriggered interrupts. + // If a TMR bit is set when an EOI cycle for its corresponding interrupt vector is generated, an EOI message is sent to all I/O APICs. + // (see 11.8.4 Interrupt Acceptance for Fixed Interrupts) + if (self.regs().TMR[idx].get() as u32).bit(bitpos) { + // Send EOI to all I/O APICs + /* + * Per Intel SDM 10.8.5, Software can inhibit the broadcast of + * EOI by setting bit 12 of the Spurious Interrupt Vector + * Register of the LAPIC. + * TODO: Check if the bit 12 "Suppress EOI Broadcasts" is set. + */ + unimplemented!("vioapic_broadcast_eoi(vlapic2vcpu(vlapic)->vm, vector);") + } + + debug!("Gratuitous EOI vector: {vector:#010X}"); + + unimplemented!("vcpu_make_request(vlapic2vcpu(vlapic), ACRN_REQUEST_EVENT);") + } + + /// Post an interrupt to the vcpu running on 'hostcpu'. + /// This will use a hardware assist if available (e.g. Posted Interrupt) + /// or fall back to sending an 'ipinum' to interrupt the 'hostcpu'. + fn set_err(&mut self, mask: ErrorStatusRegisterValue) { + self.esr_pending.modify(mask); + + self.esr_firing = 1; + if self.esr_firing == 0 { + self.esr_firing = 1; + let _lvt = self.regs().LVT_ERROR.get(); + // if ((lvt & APIC_LVT_M) == 0U) { + // vec = lvt & APIC_LVT_VECTOR; + // if (vec >= 16U) { + // vlapic_accept_intr(vlapic, vec, LAPIC_TRIG_EDGE); + // } + // } + unimplemented!("vlapic_accept_intr(vlapic, vec, LAPIC_TRIG_EDGE)"); + // self.esr_firing = 0; + } + } + + fn is_dest_field_matched(&self, dest: u32) -> AxResult { + let mut ret = false; + + let ldr = self.regs().LDR.get(); + + if self.is_x2apic_enabled() { + return Ok(true); + } else { + match self + .regs() + .DFR + .read_as_enum::(DESTINATION_FORMAT::Model) + .ok_or(AxError::InvalidData)? + { + APICDestinationFormat::Flat => { + /* + * In the "Flat Model" the MDA is interpreted as an 8-bit wide + * bitmask. This model is available in the xAPIC mode only. + */ + let logical_id = ldr >> 24; + let dest_logical_id = dest & 0xff; + if logical_id & dest_logical_id != 0 { + ret = true; + } + } + APICDestinationFormat::Cluster => { + /* + * In the "Cluster Model" the MDA is used to identify a + * specific cluster and a set of APICs in that cluster. + */ + let logical_id = (ldr >> 24) & 0xf; + let cluster_id = ldr >> 28; + let dest_logical_id = dest & 0xf; + let dest_cluster_id = (dest >> 4) & 0xf; + if (cluster_id == dest_cluster_id) && ((logical_id & dest_logical_id) != 0) { + ret = true; + } + } + } + } + Ok(ret) + } + + /// This function populates 'dmask' with the set of vcpus that match the + /// addressing specified by the (dest, phys, lowprio) tuple. + fn calculate_dest_no_shorthand( + &self, + is_broadcast: bool, + dest: u32, + is_phys: bool, + lowprio: bool, + ) -> AxResult { + let mut dmask = 0; + + if is_broadcast { + // Broadcast in both logical and physical modes. + dmask = vmm::current_vm_active_vcpus() as u64; + } else if is_phys { + // Physical mode: "dest" is local APIC ID. + // Todo: distinguish between APIC ID and vCPU ID. + dmask = 1 << dest; + } else if lowprio { + // lowprio is not supported. + // Refer to 11.6.2.4 Lowest Priority Delivery Mode. + unimplemented!("lowprio"); + } else { + // Logical mode: "dest" is message destination addr + // to be compared with the logical APIC ID in LDR. + + let vcpu_mask = vmm::active_vcpus(vmm::current_vm_id()).unwrap(); + for i in 0..vmm::current_vm_vcpu_num() { + if vcpu_mask & (1 << i) != 0 { + if !self.is_dest_field_matched(dest)? { + continue; + } + dmask |= 1 << i; + } + } + } + + Ok(dmask) + } + + fn calculate_dest( + &self, + shorthand: APICDestination, + is_broadcast: bool, + dest: u32, + is_phys: bool, + lowprio: bool, + ) -> AxResult { + let mut dmask = 0; + match shorthand { + APICDestination::NoShorthand => { + dmask = self.calculate_dest_no_shorthand(is_broadcast, dest, is_phys, lowprio)?; + } + APICDestination::SELF => { + dmask.set_bit(self.vapic_id as usize, true); + } + APICDestination::AllIncludingSelf => { + dmask = vmm::current_vm_active_vcpus() as u64; + } + APICDestination::AllExcludingSelf => { + dmask = vmm::current_vm_active_vcpus() as u64; + dmask &= !(1 << self.vapic_id); + } + } + + Ok(dmask) + } + + fn handle_self_ipi(&mut self) { + unimplemented!("x2apic handle_self_ipi"); + } + + fn set_intr(&mut self, vcpu_id: u32, vector: u32, level: bool) { + unimplemented!( + "set_intr, vcpu_id: {}, vector: {}, level: {}", + vcpu_id, + vector, + level + ); + } + + fn inject_nmi(&mut self, vcpu_id: u32) { + unimplemented!("inject_nmi vcpu_id: {}", vcpu_id); + } + + fn process_init_sipi( + &mut self, + vcpu_id: u32, + mode: APICDeliveryMode, + icr_low: InterruptCommandRegisterLowLocal, + ) { + unimplemented!( + "process_init_sipi, vcpu_id: {}, mode: {:?} icr_low: {:#010X}", + vcpu_id, + mode, + icr_low.get() + ); + } + + /// Figure 11-13. Logical Destination Register (LDR) + fn write_ldr(&mut self) { + const LDR_RESERVED: u32 = 0x00ffffff; + + let mut ldr = self.regs().LDR.get(); + let apic_id = ldr >> 24; + ldr &= !LDR_RESERVED; + + self.regs().LDR.set(ldr); + debug!("[VLAPIC] apic_id={apic_id:#010X} write LDR register to {ldr:#010X}"); + } + + fn write_dfr(&mut self) { + use crate::regs::DESTINATION_FORMAT; + + const APIC_DFR_RESERVED: u32 = 0x0fff_ffff; + const APIC_DFR_MODEL_MASK: u32 = 0xf000_0000; + + let mut dfr = self.regs().DFR.get(); + dfr &= APIC_DFR_MODEL_MASK; + dfr |= APIC_DFR_RESERVED; + self.regs().DFR.set(dfr); + + debug!("[VLAPIC] write DFR register to {dfr:#010X}"); + + match self.regs().DFR.read_as_enum(DESTINATION_FORMAT::Model) { + Some(DESTINATION_FORMAT::Model::Value::Flat) => { + debug!("[VLAPIC] DFR in Flat Model"); + } + Some(DESTINATION_FORMAT::Model::Value::Cluster) => { + debug!("[VLAPIC] DFR in Cluster Model"); + } + None => { + debug!("[VLAPIC] DFR in Unknown Model {dfr:#010X}"); + } + } + } + + /// Figure 11-14. Spurious-Interrupt Vector Register (SVR) + /// Handle writes to the SVR register. + fn write_svr(&mut self) -> AxResult { + let new = self.regs().SVR.extract(); + let old = self.svr_last; + + self.svr_last = new; + + if old.is_set(SPURIOUS_INTERRUPT_VECTOR::APICSoftwareEnableDisable) + && !new.is_set(SPURIOUS_INTERRUPT_VECTOR::APICSoftwareEnableDisable) + { + debug!("[VLAPIC] vlapic [{}] is software-disabled", self.vapic_id); + // The apic is now disabled so stop the apic timer + // and mask all the LVT entries. + self.virtual_timer.stop_timer()?; + self.mask_lvts()?; + warn!("VM wire mode should be changed to INTR here, unimplemented"); + } else if !old.is_set(SPURIOUS_INTERRUPT_VECTOR::APICSoftwareEnableDisable) + && new.is_set(SPURIOUS_INTERRUPT_VECTOR::APICSoftwareEnableDisable) + { + debug!("[VLAPIC] vlapic [{}] is software-enabled", self.vapic_id); + + // The apic is now enabled so restart the apic timer + // if it is configured in periodic mode. + if self.virtual_timer.is_periodic() { + debug!("Restarting the apic timer"); + self.virtual_timer.restart_timer()?; + } + } + + Ok(()) + } + + fn write_esr(&mut self) { + let esr = self.regs().ESR.get(); + debug!("[VLAPIC] write ESR register to {esr:#010X}"); + self.regs().ESR.set(self.esr_pending.get()); + self.esr_pending.set(0); + } + + fn write_icr(&mut self) -> AxResult { + self.regs() + .ICR_LO + .modify(INTERRUPT_COMMAND_LOW::DeliveryStatus::Idle); + + let icr_low = self.regs().ICR_LO.extract(); + + let (dest, is_broadcast) = if self.is_x2apic_enabled() { + use crate::consts::x2apic::X2APIC_BROADCAST_DEST_ID; + let dest = self.regs().ICR_HI.get(); + (dest, dest == X2APIC_BROADCAST_DEST_ID) + } else { + use crate::consts::xapic::XAPIC_BROADCAST_DEST_ID; + let dest = self.regs().ICR_HI.read(INTERRUPT_COMMAND_HIGH::Destination); + (dest, dest == XAPIC_BROADCAST_DEST_ID) + }; + + let vec = icr_low.read(INTERRUPT_COMMAND_LOW::Vector); + let mode = icr_low + .read_as_enum::(INTERRUPT_COMMAND_LOW::DeliveryMode) + .ok_or(AxError::InvalidData)?; + let is_phys = icr_low.is_set(INTERRUPT_COMMAND_LOW::DestinationMode); + let shorthand = icr_low + .read_as_enum::(INTERRUPT_COMMAND_LOW::DestinationShorthand) + .ok_or(AxError::InvalidData)?; + + if mode == APICDeliveryMode::Fixed && vec < 16 { + self.set_err(ERROR_STATUS::SendIllegalVector::SET); + debug!("[VLAPIC] Ignoring invalid IPI {vec:#010X}"); + } else if (shorthand == APICDestination::SELF + || shorthand == APICDestination::AllIncludingSelf) + && (mode == APICDeliveryMode::NMI + || mode == APICDeliveryMode::INIT + || mode == APICDeliveryMode::StartUp) + { + debug!("[VLAPIC] Invalid ICR value {vec:#010X}"); + } else { + debug!( + "icrlow {:#010X} icrhi {:#010X} triggered ipi {:#010X}", + icr_low.get(), + self.regs().ICR_HI.get(), + vec + ); + let dmask = self.calculate_dest(shorthand, is_broadcast, dest, is_phys, false)?; + + // TODO: we need to get the specific vcpu number somehow. + for i in 0..vmm::current_vm_vcpu_num() as u32 { + if dmask & (1 << i) != 0 { + match mode { + APICDeliveryMode::Fixed => { + self.set_intr(i, vec, LAPIC_TRIG_EDGE); + debug!("[VLAPIC] sending IPI {vec} to vcpu {i}"); + } + APICDeliveryMode::NMI => { + self.inject_nmi(i); + debug!("[VLAPIC] sending NMI to vcpu {i}"); + } + APICDeliveryMode::INIT | APICDeliveryMode::StartUp => { + self.process_init_sipi(i, mode, icr_low); + } + APICDeliveryMode::SMI => { + warn!("[VLPAIC] SMI IPI do not support"); + } + _ => { + error!("Unhandled icrlo write with mode {mode:?}\n"); + } + } + } + } + } + + Ok(()) + } + + fn extract_lvt_val(&self, offset: ApicRegOffset) -> u32 { + match offset { + ApicRegOffset::LvtCMCI => self.regs().LVT_CMCI.get(), + ApicRegOffset::LvtTimer => self.regs().LVT_TIMER.get(), + ApicRegOffset::LvtThermal => self.regs().LVT_THERMAL.get(), + ApicRegOffset::LvtPmc => self.regs().LVT_PMI.get(), + ApicRegOffset::LvtLint0 => self.regs().LVT_LINT0.get(), + ApicRegOffset::LvtLint1 => self.regs().LVT_LINT1.get(), + ApicRegOffset::LvtErr => self.regs().LVT_ERROR.get(), + _ => { + warn!("[VLAPIC] read unsupported APIC register: {offset:?}"); + 0 + } + } + } + + fn write_lvt(&mut self, offset: ApicRegOffset) -> AxResult { + let mut val = self.extract_lvt_val(offset); + + if self + .regs() + .SVR + .is_set(SPURIOUS_INTERRUPT_VECTOR::APICSoftwareEnableDisable) + { + val |= APIC_LVT_M; + } + + // Mask::Masked, Delivery Status:SendPending, Vector::SET(0xff) + let mut mask = APIC_LVT_M | APIC_LVT_DS | APIC_LVT_VECTOR; + + match offset { + ApicRegOffset::LvtTimer => { + mask |= LVT_TIMER::TimerMode::SET.mask(); + val &= mask; + self.regs().LVT_TIMER.set(val); // Duplicated, which one should be removed? + self.lvt_last.lvt_timer.set(val); + + self.virtual_timer.write_lvt(val)?; + } + ApicRegOffset::LvtErr => { + val &= mask; + self.regs().LVT_ERROR.set(val); + self.lvt_last.lvt_err.set(val); + } + ApicRegOffset::LvtLint0 => { + mask |= LVT_LINT0::TriggerMode::SET.mask(); + mask |= LVT_LINT0::RemoteIRR::SET.mask(); + mask |= LVT_LINT0::InterruptInputPinPolarity::SET.mask(); + mask |= LVT_LINT0::DeliveryMode::SET.mask(); + val &= mask; + + // vlapic mask/unmask LINT0 for ExtINT? + if (val & LVT_LINT0::DeliveryMode::SET.mask()) + == LVT_LINT0::DeliveryMode::ExtINT.mask() + { + let last = self.lvt_last.lvt_lint0; + if last.is_set(LVT_LINT0::Mask) && val & LVT_LINT0::Mask::SET.mask() == 0 { + // mask -> unmask: may from every vlapic in the vm + warn!("vpic wire mode change to LAPIC, unimplemented"); + } else if !last.is_set(LVT_LINT0::Mask) + && val & LVT_LINT0::Mask::SET.mask() != 0 + { + // unmask -> mask: only from the vlapic LINT0-ExtINT enabled + warn!("vpic wire mode change to NULL, unimplemented"); + } else { + // APIC_LVT_M unchanged. No action required. + } + } + + self.regs().LVT_LINT0.set(val); + self.lvt_last.lvt_lint0.set(val); + } + ApicRegOffset::LvtLint1 => { + mask |= LVT_LINT1::TriggerMode::SET.mask(); + mask |= LVT_LINT1::RemoteIRR::SET.mask(); + mask |= LVT_LINT1::InterruptInputPinPolarity::SET.mask(); + mask |= LVT_LINT1::DeliveryMode::SET.mask(); + val &= mask; + + self.regs().LVT_LINT1.set(val); + self.lvt_last.lvt_lint1.set(val); + } + ApicRegOffset::LvtCMCI => { + mask |= LVT_CMCI::DeliveryMode::SET.mask(); + val &= mask; + self.regs().LVT_CMCI.set(val); + self.lvt_last.lvt_cmci.set(val); + } + ApicRegOffset::LvtPmc => { + mask |= LVT_PERFORMANCE_COUNTER::DeliveryMode::SET.mask(); + val &= mask; + self.regs().LVT_PMI.set(val); + self.lvt_last.lvt_perf_count.set(val); + } + ApicRegOffset::LvtThermal => { + mask |= LVT_THERMAL_MONITOR::DeliveryMode::SET.mask(); + val &= mask; + self.regs().LVT_THERMAL.set(val); + self.lvt_last.lvt_thermal.set(val); + } + _ => { + warn!("[VLAPIC] write unsupported APIC register: {offset:?}"); + return Err(AxError::InvalidInput); + } + } + Ok(()) + } + + fn mask_lvts(&mut self) -> AxResult { + self.regs().LVT_CMCI.modify(LVT_CMCI::Mask::SET); + self.write_lvt(ApicRegOffset::LvtCMCI)?; + + self.regs().LVT_TIMER.modify(LVT_TIMER::Mask::SET); + self.write_lvt(ApicRegOffset::LvtTimer)?; + + self.regs() + .LVT_THERMAL + .modify(LVT_THERMAL_MONITOR::Mask::SET); + self.write_lvt(ApicRegOffset::LvtThermal)?; + + self.regs() + .LVT_PMI + .modify(LVT_PERFORMANCE_COUNTER::Mask::SET); + self.write_lvt(ApicRegOffset::LvtPmc)?; + + self.regs().LVT_LINT0.modify(LVT_LINT0::Mask::SET); + self.write_lvt(ApicRegOffset::LvtLint0)?; + + self.regs().LVT_LINT1.modify(LVT_LINT1::Mask::SET); + self.write_lvt(ApicRegOffset::LvtLint1)?; + + self.regs().LVT_ERROR.modify(LVT_ERROR::Mask::SET); + self.write_lvt(ApicRegOffset::LvtErr)?; + + Ok(()) + } + + fn write_icrtmr(&mut self) -> AxResult { + self.virtual_timer.write_icr(self.regs().ICR_TIMER.get()) + } + + fn write_dcr(&mut self) -> AxResult { + self.virtual_timer.write_dcr(self.regs().DCR_TIMER.get()); + Ok(()) + } +} + +fn extract_index_u32(vector: u32) -> usize { + vector as usize >> 5 +} + +fn extract_index_and_bitpos_u32(vector: u32) -> (usize, usize) { + (extract_index_u32(vector), vector as usize & 0x1F) +} + +/// Figure 11-18. Task-Priority Register (TPR) +/// [7:4]: Task-Priority Class +/// [3:0]: Task-Priority Sub-Class +fn prio(x: u32) -> u32 { + (x >> 4) & 0xf +} + +impl VirtualApicRegs { + pub fn handle_read(&self, offset: ApicRegOffset, width: AccessWidth) -> AxResult { + let mut value: usize = 0; + match offset { + ApicRegOffset::ID => { + value = self.regs().ID.get() as _; + } + ApicRegOffset::Version => { + value = self.regs().VERSION.get() as _; + } + ApicRegOffset::TPR => { + value = self.regs().TPR.get() as _; + } + ApicRegOffset::PPR => { + value = self.regs().PPR.get() as _; + } + ApicRegOffset::EOI => { + // value = self.regs().EOI.get() as _; + warn!("[VLAPIC] read EOI register: {value:#010X}"); + } + ApicRegOffset::LDR => { + value = self.regs().LDR.get() as _; + } + ApicRegOffset::DFR => { + value = self.regs().DFR.get() as _; + } + ApicRegOffset::SIVR => { + value = self.regs().SVR.get() as _; + } + ApicRegOffset::ISR(index) => { + value = self.regs().ISR[index.as_usize()].get() as _; + } + ApicRegOffset::TMR(index) => { + value = self.regs().TMR[index.as_usize()].get() as _; + } + ApicRegOffset::IRR(index) => { + value = self.regs().IRR[index.as_usize()].get() as _; + } + ApicRegOffset::ESR => { + value = self.regs().ESR.get() as _; + } + ApicRegOffset::ICRLow => { + value = self.regs().ICR_LO.get() as _; + if self.is_x2apic_enabled() && width == AccessWidth::Qword { + let icr_hi = self.regs().ICR_HI.get() as usize; + value |= icr_hi << 32; + debug!("[VLAPIC] read ICR register: {value:#018X}"); + } else if self.is_x2apic_enabled() ^ (width == AccessWidth::Qword) { + warn!( + "[VLAPIC] Illegal read attempt of ICR register at width {:?} with X2APIC {}", + width, + if self.is_x2apic_enabled() { + "enabled" + } else { + "disabled" + } + ); + return Err(AxError::InvalidInput); + } + } + ApicRegOffset::ICRHi => { + value = self.regs().ICR_HI.get() as _; + } + // Local Vector Table registers. + ApicRegOffset::LvtCMCI => { + value = self.lvt_last.lvt_cmci.get() as _; + } + ApicRegOffset::LvtTimer => { + value = self.lvt_last.lvt_timer.get() as _; + } + ApicRegOffset::LvtThermal => { + value = self.lvt_last.lvt_thermal.get() as _; + } + ApicRegOffset::LvtPmc => { + value = self.lvt_last.lvt_perf_count.get() as _; + } + ApicRegOffset::LvtLint0 => { + value = self.lvt_last.lvt_lint0.get() as _; + } + ApicRegOffset::LvtLint1 => { + value = self.lvt_last.lvt_lint1.get() as _; + } + ApicRegOffset::LvtErr => { + value = self.lvt_last.lvt_err.get() as _; + } + // Timer registers. + ApicRegOffset::TimerInitCount => { + match self.timer_mode() { + Ok(TimerMode::OneShot) | Ok(TimerMode::Periodic) => { + value = self.regs().ICR_TIMER.get() as _; + } + Ok(TimerMode::TSCDeadline) => { + /* if TSCDEADLINE mode always return 0*/ + value = 0; + } + _ => { + warn!("[VLAPIC] read TimerInitCount register: invalid timer mode"); + } + } + debug!("[VLAPIC] read TimerInitCount register: {value:#010X}"); + } + ApicRegOffset::TimerCurCount => { + value = self.virtual_timer.read_ccr() as _; + } + ApicRegOffset::TimerDivConf => { + value = self.regs().DCR_TIMER.get() as _; + } + _ => { + warn!("[VLAPIC] read unknown APIC register: {offset:?}"); + } + } + debug!("[VLAPIC] read {offset} register: {value:#010X}"); + Ok(value) + } + + pub fn handle_write( + &mut self, + offset: ApicRegOffset, + val: usize, + width: AccessWidth, + ) -> AxResult { + let data32 = val as u32; + + match offset { + ApicRegOffset::ID => { + // Force APIC ID to be read-only. + // self.regs().ID.set(val as _); + } + ApicRegOffset::EOI => { + self.process_eoi(); + } + ApicRegOffset::LDR => { + self.regs().LDR.set(data32); + self.write_ldr(); + } + ApicRegOffset::DFR => { + self.regs().DFR.set(data32); + self.write_dfr(); + } + ApicRegOffset::SIVR => { + self.regs().SVR.set(data32); + self.write_svr()?; + } + ApicRegOffset::ESR => { + self.regs().ESR.set(data32); + self.write_esr(); + } + ApicRegOffset::ICRLow => { + if self.is_x2apic_enabled() && width == AccessWidth::Qword { + debug!("[VLAPIC] write ICR register: {val:#018X} in X2APIC mode"); + self.regs().ICR_HI.set((val >> 32) as u32); + } else if self.is_x2apic_enabled() ^ (width == AccessWidth::Qword) { + warn!( + "[VLAPIC] Illegal read attempt of ICR register at width {:?} with X2APIC {}", + width, + if self.is_x2apic_enabled() { + "enabled" + } else { + "disabled" + } + ); + return Err(AxError::InvalidInput); + } + self.regs().ICR_LO.set(data32); + self.write_icr()?; + } + // Local Vector Table registers. + ApicRegOffset::LvtCMCI => { + self.regs().LVT_CMCI.set(data32); + self.write_lvt(offset)?; + } + ApicRegOffset::LvtTimer => { + self.regs().LVT_TIMER.set(data32); + self.write_lvt(offset)?; + } + ApicRegOffset::LvtThermal => { + self.regs().LVT_THERMAL.set(data32); + self.write_lvt(offset)?; + } + ApicRegOffset::LvtPmc => { + self.regs().LVT_PMI.set(data32); + self.write_lvt(offset)?; + } + ApicRegOffset::LvtLint0 => { + self.regs().LVT_LINT0.set(data32); + self.write_lvt(offset)?; + } + ApicRegOffset::LvtLint1 => { + self.regs().LVT_LINT1.set(data32); + self.write_lvt(offset)?; + } + ApicRegOffset::LvtErr => { + self.regs().LVT_ERROR.set(data32); + self.write_lvt(offset)?; + } + // Timer registers. + ApicRegOffset::TimerInitCount => { + // if TSCDEADLINE mode ignore icr_timer + if self.timer_mode()? == TimerMode::TSCDeadline { + warn!( + "[VLAPIC] write TimerInitCount register: ignore icr_timer in TSCDEADLINE mode" + ); + return Ok(()); + } + self.regs().ICR_TIMER.set(data32); + self.write_icrtmr()?; + } + ApicRegOffset::TimerDivConf => { + self.regs().DCR_TIMER.set(data32); + self.write_dcr()?; + } + ApicRegOffset::SelfIPI => { + if self.is_x2apic_enabled() { + self.regs().SELF_IPI.set(data32); + self.handle_self_ipi(); + } else { + warn!("[VLAPIC] write SelfIPI register: unsupported in xAPIC mode"); + return Err(AxError::InvalidInput); + } + } + _ => { + warn!("[VLAPIC] write unsupported APIC register: {offset:?}"); + return Err(AxError::InvalidInput); + } + } + + debug!("[VLAPIC] write {offset} register: {val:#010X}"); + + Ok(()) + } +} diff --git a/os/arceos/Cargo.toml b/os/arceos/Cargo.toml index 33ed3f775..ac13f588d 100644 --- a/os/arceos/Cargo.toml +++ b/os/arceos/Cargo.toml @@ -125,7 +125,7 @@ bindgen = "0.72" buddy-slab-allocator = "0.2" cfg-if = "1.0" chrono = { version = "0.4", default-features = false } -crate_interface = "0.1" +crate_interface = "0.3" ctor_bare = "0.2" event-listener = { version = "5.4.0", default-features = false } kernel_guard = "0.1" diff --git a/os/axvisor/.cargo/config.toml b/os/axvisor/.cargo/config.toml index 0da14d5b2..4bdaa6e66 100644 --- a/os/axvisor/.cargo/config.toml +++ b/os/axvisor/.cargo/config.toml @@ -2,9 +2,6 @@ # 使用系统 git 拉取依赖,可利用已配置的 git 凭证(如 gh auth、credential helper) git-fetch-with-cli = true -[target.'cfg(target_os = "none")'] -runner = "cargo osrun" - [target.x86_64-unknown-none] rustflags = [ "-Clink-args=-no-pie", @@ -12,5 +9,24 @@ rustflags = [ "-Clink-args=-Tlink.x", ] +# Comment out because it's specified by runner in tgoskits +# [target.aarch64-unknown-none-softfloat] +# rustflags = [ +# "-Crelocation-model=pic", +# "-Clink-args=-pie", +# "-Clink-args=-znostart-stop-gc", +# "-Clink-args=-Taxplat.x", +# ] + +[target.riscv64gc-unknown-none-elf] +rustflags = [ + "-Clink-args=-no-pie", + "-Clink-args=-znostart-stop-gc", + "-Clink-args=-Tlink.x", +] + +[target.'cfg(target_os = "none")'] +runner = "cargo osrun" + [alias] xtask = "run --bin xtask --" diff --git a/os/axvisor/.github/workflows/push.yml b/os/axvisor/.github/workflows/push.yml new file mode 100644 index 000000000..a18de4a04 --- /dev/null +++ b/os/axvisor/.github/workflows/push.yml @@ -0,0 +1,147 @@ +# ═══════════════════════════════════════════════════════════════════════════════ +# 组件仓库 GitHub Actions 配置模板 +# ═══════════════════════════════════════════════════════════════════════════════ +# +# 此文件用于子仓库,当子仓库有更新时通知主仓库进行 subtree pull 同步。 +# +# 【使用步骤】 +# ───────────────────────────────────────────────────────────────────────────── +# 1. 将此文件复制到子仓库的 .github/workflows/ 目录: +# cp scripts/push.yml <子仓库>/.github/workflows/push.yml +# +# 2. 在子仓库中配置 Secret: +# GitHub 仓库 → Settings → Secrets → Actions → New repository secret +# 名称: PARENT_REPO_TOKEN +# 值: 具有主仓库 repo 权限的 Personal Access Token +# +# 3. 修改下方 env 块中的一个变量(标注了「需要修改」的行): +# PARENT_REPO - 主仓库路径,例如 rcore-os/tgoskits +# (subtree 目录由主仓库自动从 git 历史中推断,无需手动指定) +# +# 【Token 权限要求】 +# ───────────────────────────────────────────────────────────────────────────── +# PARENT_REPO_TOKEN 需要 Classic Personal Access Token,权限包括: +# - repo (Full control of private repositories) +# 或 +# - Fine-grained token: Contents (Read and Write) +# +# 【触发条件】 +# ───────────────────────────────────────────────────────────────────────────── +# - 自动触发:推送到 dev 或 main 分支时 +# - 手动触发:Actions → Notify Parent Repository → Run workflow +# +# 【工作流程】 +# ───────────────────────────────────────────────────────────────────────────── +# 子仓库 push → 触发此工作流 → 调用主仓库 API → 主仓库 subtree pull +# +# 【注意事项】 +# ───────────────────────────────────────────────────────────────────────────── +# - 主仓库需要配置接收 repository_dispatch 事件的同步工作流 +# - 如果不需要子仓库到主仓库的同步,可以不使用此文件 +# +# ═══════════════════════════════════════════════════════════════════════════════ + +name: Notify Parent Repository + +# 当有新的推送时触发 +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Get repository info + id: repo + env: + GH_REPO_NAME: ${{ github.event.repository.name }} + GH_REF_NAME: ${{ github.ref_name }} + GH_SERVER_URL: ${{ github.server_url }} + GH_REPOSITORY: ${{ github.repository }} + run: | + # 直接使用 GitHub Actions 内置变量,通过 env 传入避免 shell 注入 + COMPONENT="$GH_REPO_NAME" + BRANCH="$GH_REF_NAME" + # 构造标准 HTTPS URL,供主仓库按 URL 精确匹配 repos.list + REPO_URL="${GH_SERVER_URL}/${GH_REPOSITORY}" + + echo "component=${COMPONENT}" >> $GITHUB_OUTPUT + echo "branch=${BRANCH}" >> $GITHUB_OUTPUT + echo "repo_url=${REPO_URL}" >> $GITHUB_OUTPUT + + echo "Component: ${COMPONENT}" + echo "Branch: ${BRANCH}" + echo "Repo URL: ${REPO_URL}" + + - name: Notify parent repository + env: + # ── 需要修改 ────────────────────────────────────────────────────────── + PARENT_REPO: "rcore-os/tgoskits" # 主仓库路径 + # ── 无需修改 ────────────────────────────────────────────────────────── + DISPATCH_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} + # 将用户可控内容通过 env 传入,避免直接插值到 shell 脚本 + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + GIT_ACTOR: ${{ github.actor }} + GIT_SHA: ${{ github.sha }} + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "Notifying parent repository about update in ${COMPONENT}:${BRANCH}" + + # 使用 jq 安全构建 JSON,避免 commit message 中任何特殊字符导致注入 + PAYLOAD=$(jq -n \ + --arg component "$COMPONENT" \ + --arg branch "$BRANCH" \ + --arg repo_url "$REPO_URL" \ + --arg commit "$GIT_SHA" \ + --arg message "$COMMIT_MESSAGE" \ + --arg author "$GIT_ACTOR" \ + '{ + event_type: "subtree-update", + client_payload: { + component: $component, + branch: $branch, + repo_url: $repo_url, + commit: $commit, + message: $message, + author: $author + } + }') + + curl --fail --show-error -X POST \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${DISPATCH_TOKEN}" \ + https://api.github.com/repos/${PARENT_REPO}/dispatches \ + -d "$PAYLOAD" + + echo "Notification sent successfully" + + - name: Create summary + env: + STEP_COMPONENT: ${{ steps.repo.outputs.component }} + STEP_BRANCH: ${{ steps.repo.outputs.branch }} + STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} + GIT_SHA: ${{ github.sha }} + GIT_ACTOR: ${{ github.actor }} + run: | + COMPONENT="$STEP_COMPONENT" + BRANCH="$STEP_BRANCH" + REPO_URL="$STEP_REPO_URL" + + echo "## Notification Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Component**: ${COMPONENT}" >> $GITHUB_STEP_SUMMARY + echo "- **Branch**: ${BRANCH}" >> $GITHUB_STEP_SUMMARY + echo "- **Repo URL**: ${REPO_URL}" >> $GITHUB_STEP_SUMMARY + echo "- **Commit**: \`${GIT_SHA}\`" >> $GITHUB_STEP_SUMMARY + echo "- **Author**: ${GIT_ACTOR}" >> $GITHUB_STEP_SUMMARY + echo "- **Status**: ✅ Notification sent" >> $GITHUB_STEP_SUMMARY diff --git a/os/axvisor/.github/workflows/qemu-aarch64.toml b/os/axvisor/.github/workflows/qemu-aarch64.toml index 4ba97898e..7b147d465 100644 --- a/os/axvisor/.github/workflows/qemu-aarch64.toml +++ b/os/axvisor/.github/workflows/qemu-aarch64.toml @@ -8,6 +8,8 @@ args = [ "4", "-device", "virtio-blk-device,drive=disk0", + # "-drive", + # "id=disk0,if=none,format=raw,file=${workspaceFolder}/tmp/rootfs.img", "-append", "root=/dev/vda rw init=/init", "-m", diff --git a/os/axvisor/.github/workflows/qemu-riscv64.toml b/os/axvisor/.github/workflows/qemu-riscv64.toml new file mode 100644 index 000000000..b228b6f1b --- /dev/null +++ b/os/axvisor/.github/workflows/qemu-riscv64.toml @@ -0,0 +1,24 @@ +args = [ + "-nographic", + "-cpu", + "rv64", + "-machine", + "virt", + "-bios", + "default", + "-smp", + "4", + "-device", + "virtio-blk-device,drive=disk0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspaceFolder}/tmp/rootfs.img", + "-m", + "8g", +] +fail_regex = [] +success_regex = [ + "Hello, world!", + "test pass!", +] +to_bin = true +uefi = false \ No newline at end of file diff --git a/os/axvisor/.github/workflows/qemu-x86_64.toml b/os/axvisor/.github/workflows/qemu-x86_64.toml index bf0753753..d558ef66a 100644 --- a/os/axvisor/.github/workflows/qemu-x86_64.toml +++ b/os/axvisor/.github/workflows/qemu-x86_64.toml @@ -7,8 +7,8 @@ args = [ "q35", "-device", "virtio-blk-pci,drive=disk0", - # "-drive", - # "id=disk0,if=none,format=raw,file=${workspaceFolder}/tmp/rootfs.img", + "-drive", + "id=disk0,if=none,format=raw,file=${workspaceFolder}/tmp/rootfs.img", "-nographic", "-accel", "kvm", diff --git a/os/axvisor/Cargo.lock b/os/axvisor/Cargo.lock index 6d5015bc5..4c69006f5 100644 --- a/os/axvisor/Cargo.lock +++ b/os/axvisor/Cargo.lock @@ -193,7 +193,7 @@ dependencies = [ "axdriver", "axerrno 0.2.2", "axfeat", - "axfs", + "axfs 0.2.0", "axhal", "axio", "axlog", @@ -208,6 +208,8 @@ version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e30c6a0ffd23095c69f48afd996eb51156b2511b52a01bdbb0b418fdfd1d458c" dependencies = [ + "aarch64-cpu 10.0.0", + "bitflags 2.10.0", "aarch64-cpu 11.2.0", "bitflags 2.11.0", "enum_dispatch", @@ -251,14 +253,14 @@ dependencies = [ [[package]] name = "arm_vcpu" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8581cf4d84a33f95aa43d39c0a25cabeeaddd65c97a790a0830e37da6e5d871" +checksum = "979230a9de461189f361c5a41a01006b4e943dcf9ebbac1c1444fb6c29fdbae2" dependencies = [ - "aarch64-cpu 10.0.0", + "aarch64-cpu 11.2.0", "axaddrspace", "axdevice_base", - "axerrno 0.1.2", + "axerrno 0.2.2", "axvcpu", "axvisor_api", "log", @@ -269,20 +271,20 @@ dependencies = [ [[package]] name = "arm_vgic" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e2c4d90852cad20bbe1e5ee6e6d1b05468b98787a8344ffea58537eb54f375" +checksum = "49c287b9ceddf2a9f36662df520a85e2fca93a194d303d98d50727c4b19647fa" dependencies = [ - "aarch64-cpu 10.0.0", + "aarch64-cpu 11.2.0", "aarch64_sysreg", "axaddrspace", "axdevice_base", - "axerrno 0.1.2", + "axerrno 0.2.2", "axvisor_api", "bitmaps", "log", "memory_addr", - "spin 0.9.8", + "spin 0.10.0", "tock-registers 0.10.1", ] @@ -345,11 +347,11 @@ dependencies = [ [[package]] name = "axaddrspace" -version = "0.1.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91962ea80ef137c2986b6802d664900e73aa7a014272c13dd9172cc948d5c94e" +checksum = "8b2dffa605d03eb184604fb7fcc15f2e641cf21b7a17b8f71e23b22f5c04b7be" dependencies = [ - "axerrno 0.1.2", + "axerrno 0.2.2", "bit_field", "bitflags 2.11.0", "cfg-if", @@ -358,8 +360,8 @@ dependencies = [ "memory_addr", "memory_set", "numeric-enum-macro", - "page_table_entry", - "page_table_multiarch", + "page_table_entry 0.6.1", + "page_table_multiarch 0.6.1", "x86", ] @@ -439,7 +441,7 @@ version = "0.3.0-preview.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "361edfc761188b19fb3d906b0b155942a6290068ee88d42f3b1f0ce31dcd099e" dependencies = [ - "aarch64-cpu 11.2.0", + "aarch64-cpu 10.0.0", "axbacktrace", "cfg-if", "lazyinit", @@ -447,10 +449,10 @@ dependencies = [ "log", "loongArch64", "memory_addr", - "page_table_entry", - "page_table_multiarch", + "page_table_entry 0.5.7", + "page_table_multiarch 0.5.7", "percpu", - "riscv 0.16.0", + "riscv 0.14.0", "static_assertions", "tock-registers 0.10.1", "x86", @@ -459,33 +461,32 @@ dependencies = [ [[package]] name = "axdevice" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473e2bedf3a04bede7ab6e05909d56f8aed538c56507ce172aaaf0d14dc3be36" +checksum = "55bcbe9825eecc55421eb205511a0d359f0c83141a4df53f9d3c2824b515fbba" dependencies = [ "arm_vgic", "axaddrspace", "axdevice_base", - "axerrno 0.1.2", + "axerrno 0.2.2", "axvmconfig", "cfg-if", "log", "memory_addr", "range-alloc-arceos", "riscv_vplic", - "spin 0.9.8", + "spin 0.10.0", ] [[package]] name = "axdevice_base" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2707beb1e7bb6557406d77f3968a9ed7ec3d0476691022eafa550c266ec5c9" +checksum = "3f466dbb1dcf24f0ad76f737b9b65425a1bdaef30daeff938c9d69d808eb1805" dependencies = [ "axaddrspace", - "axerrno 0.1.2", + "axerrno 0.2.2", "axvmconfig", - "memory_addr", "serde", ] @@ -506,6 +507,7 @@ dependencies = [ "axplat-dyn", "cfg-if", "crate_interface 0.1.4", + "dma-api 0.5.2", "log", "smallvec", ] @@ -587,7 +589,7 @@ dependencies = [ "axbacktrace", "axconfig", "axdriver", - "axfs", + "axfs 0.2.0", "axhal", "axlog", "axruntime", @@ -610,6 +612,7 @@ dependencies = [ "axfs_vfs", "axio", "cap_access", + "fatfs 0.4.0 (git+https://github.com/Josen-B/rust-fatfs.git?rev=41122ef)", "lazyinit", "log", "rsext4", @@ -617,14 +620,24 @@ dependencies = [ ] [[package]] -name = "axfs_devfs" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b87ae981272ca8d5d8f106a4452c63f4b5ac36e17ee8f848ee1b250538b9f8" +name = "axfs" +version = "0.2.0" +source = "git+https://github.com/arceos-org/arceos.git?tag=dev-251216#16096568f5ae6ad2d687eff2927a4cb69cef8133" dependencies = [ + "axdriver", + "axdriver_block 0.1.2 (git+https://github.com/arceos-org/axdriver_crates.git?tag=dev-v01)", + "axerrno 0.1.2", + "axfs_devfs", + "axfs_ramfs", "axfs_vfs", + "axio", + "axsync", + "cap_access", + "cfg-if", + "fatfs 0.4.0 (git+https://github.com/rafalh/rust-fatfs?rev=4eccb50)", + "lazyinit", "log", - "spin 0.9.8", + "scope-local", ] [[package]] @@ -662,7 +675,7 @@ dependencies = [ "axplat-aarch64-qemu-virt", "axplat-dyn", "axplat-loongarch64-qemu-virt", - "axplat-riscv64-qemu-virt", + "axplat-riscv64-qemu-virt 0.3.0 (git+https://github.com/arceos-org/axplat_crates.git?tag=dev-v03)", "axplat-x86-pc", "cfg-if", "fdt-parser", @@ -671,7 +684,7 @@ dependencies = [ "linkme", "log", "memory_addr", - "page_table_multiarch", + "page_table_multiarch 0.5.7", "percpu", "spin 0.10.0", ] @@ -745,21 +758,49 @@ dependencies = [ "axplat-macros", "bitflags 2.11.0", "const-str", - "crate_interface 0.3.0", + "crate_interface 0.1.4", "handler_table", "kspin", "memory_addr", "percpu", ] +[[package]] +name = "axplat-aarch64-dyn" +version = "0.4.0" +source = "git+https://github.com/arceos-hypervisor/axplat-aarch64-dyn.git?tag=v0.4.0#05d5acd43d925807496255a9b9e1aa2d272bb591" +dependencies = [ + "aarch64-cpu 10.0.0", + "aarch64-cpu-ext", + "any-uart", + "arm-gic-driver", + "axconfig-macros", + "axcpu", + "axplat", + "fdt-parser", + "heapless 0.8.0", + "lazyinit", + "log", + "memory_addr", + "page_table_entry 0.5.7", + "paste", + "percpu", + "rdif-intc", + "rdrive", + "serde", + "somehal", + "spin 0.10.0", + "toml 0.8.23", +] + [[package]] name = "axplat-aarch64-peripherals" version = "0.3.1-pre.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a744097da129e66068e4fff6758726c98bf329a7182dcc65712ac80daed581ed" dependencies = [ - "aarch64-cpu 11.2.0", - "arm-gic-driver 0.16.4", + "aarch64-cpu 10.0.0", + "arm-gic-driver", "arm_pl011", "arm_pl031", "axcpu", @@ -768,6 +809,7 @@ dependencies = [ "kspin", "lazyinit", "log", + "page_table_entry 0.5.7", "spin 0.10.0", ] @@ -782,7 +824,7 @@ dependencies = [ "axplat", "axplat-aarch64-peripherals", "log", - "page_table_entry", + "page_table_entry 0.5.7", ] [[package]] @@ -826,7 +868,7 @@ dependencies = [ "lazyinit", "log", "loongArch64", - "page_table_entry", + "page_table_entry 0.5.7", "uart_16550", ] @@ -843,17 +885,35 @@ dependencies = [ [[package]] name = "axplat-riscv64-qemu-virt" -version = "0.3.1-pre.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f91aff22afadd24807e34fb94fe4d0d2c8c5b86fb89dd6ff87a8093f812518" +version = "0.3.0" dependencies = [ "axconfig-macros", "axcpu", "axplat", + "axvisor_api", + "crate_interface 0.3.0", "kspin", "lazyinit", "log", - "riscv 0.16.0", + "riscv 0.14.0", + "riscv_goldfish", + "riscv_plic", + "sbi-rt", + "uart_16550", +] + +[[package]] +name = "axplat-riscv64-qemu-virt" +version = "0.3.0" +source = "git+https://github.com/arceos-org/axplat_crates.git?tag=dev-v03#0df0713b1c20eafaeebdc6b0e194b2985e857949" +dependencies = [ + "axconfig-macros", + "axcpu", + "axplat", + "kspin", + "lazyinit", + "log", + "riscv 0.14.0", "riscv_plic", "sbi-rt", "uart_16550", @@ -928,12 +988,16 @@ dependencies = [ "axbacktrace", "axconfig", "axdriver", - "axfs", + "axerrno 0.2.2", + "axfs 0.1.0", "axhal", "axklib", "axlog", "axmm", "axplat", + "axplat-aarch64-dyn", + "axplat-riscv64-qemu-virt 0.3.0", + "axplat-x86-qemu-q35", "axtask", "cfg-if", "chrono", @@ -1060,12 +1124,12 @@ dependencies = [ [[package]] name = "axvcpu" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70880a87fffe8087719ad2e7aa09da482db88a65d123dc9170297b7e2a162fb5" +checksum = "506c3ab0ce353e76d3279927b88a43ee0effd4368ce86e8bc72a0ee9430e0709" dependencies = [ "axaddrspace", - "axerrno 0.1.2", + "axerrno 0.2.2", "axvisor_api", "memory_addr", "percpu", @@ -1082,8 +1146,11 @@ dependencies = [ "axconfig", "axdevice", "axdevice_base", + "axdriver", "axerrno 0.2.2", "axhvc", + "axplat-riscv64-qemu-virt 0.3.0", + "axruntime", "axklib", "axplat-x86-qemu-q35", "axstd", @@ -1099,7 +1166,8 @@ dependencies = [ "clap", "colored", "cpumask", - "crate_interface 0.1.4", + "crate_interface 0.3.0", + "driver", "extern-trait", "fdt-parser", "flate2", @@ -1111,10 +1179,8 @@ dependencies = [ "lazyinit", "log", "memory_addr", - "ostool", - "page_table_entry", - "page_table_multiarch", - "pcie 0.5.0", + "page_table_entry 0.6.1", + "page_table_multiarch 0.6.1", "percpu", "phytium-mci", "prettyplease", @@ -1124,19 +1190,10 @@ dependencies = [ "rdif-clk", "rdif-intc", "rdrive", - "regex", - "reqwest 0.13.2", - "rk3568_clk", - "rk3588-clk", - "rockchip-pm", - "schemars", - "sdmmc", - "serde", - "serde_json", - "sha2", - "spin 0.9.8", - "syn 2.0.117", - "tar", + "riscv_vcpu", + "riscv_vplic", + "spin 0.10.0", + "syn 2.0.111", "timer_list", "tokio", "toml", @@ -1144,21 +1201,22 @@ dependencies = [ [[package]] name = "axvisor_api" -version = "0.1.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa7233b2a1338dc06a80e2779b572b4df02007ea128ef7b235b66fc3eeac0ca6" +checksum = "7959b2dd8afbac2a0cda189986df6ceb753a92e985c28ce2c279cf432cbe2dfc" dependencies = [ "axaddrspace", "axvisor_api_proc", - "crate_interface 0.1.4", + "cpumask", + "crate_interface 0.3.0", "memory_addr", ] [[package]] name = "axvisor_api_proc" -version = "0.1.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a64eb4410ae8357ac8c01c2fb201e57d7aeeb5436ed4d0f5774bfa11cc5902" +checksum = "f5351791fec23f5545518f88d4d8a134c564085a6f37ed6953bbd4f35d4f32c7" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1168,9 +1226,9 @@ dependencies = [ [[package]] name = "axvm" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76c13dc01a73107817fa04f8f2cad019eaee992541713d7064fd2da855502c6d" +checksum = "22f6406a71e7af560b7d544fb697f3071e045d304a20d8525502b5a906dbbaab" dependencies = [ "arm_vcpu", "arm_vgic", @@ -1179,16 +1237,17 @@ dependencies = [ "axdevice_base", "axerrno 0.2.2", "axvcpu", + "axvisor_api", "axvmconfig", "cfg-if", "cpumask", "log", "memory_addr", - "page_table_entry", - "page_table_multiarch", + "page_table_entry 0.6.1", + "page_table_multiarch 0.6.1", "percpu", "riscv_vcpu", - "spin 0.9.8", + "spin 0.10.0", "x86_vcpu", ] @@ -2085,6 +2144,23 @@ dependencies = [ "litrs", ] +[[package]] +name = "driver" +version = "0.1.0" +dependencies = [ + "axklib", + "log", + "phytium-mci", + "rdif-block", + "rdif-clk", + "rdrive", + "rk3568_clk", + "rk3588-clk", + "rockchip-pm", + "sdmmc", + "spin 0.10.0", +] + [[package]] name = "dunce" version = "1.0.5" @@ -2290,6 +2366,15 @@ dependencies = [ "log", ] +[[package]] +name = "fatfs" +version = "0.4.0" +source = "git+https://github.com/rafalh/rust-fatfs?rev=4eccb50#4eccb50d011146fbed20e133d33b22f3c27292e7" +dependencies = [ + "bitflags 2.10.0", + "log", +] + [[package]] name = "fdt-parser" version = "0.4.18" @@ -3606,10 +3691,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42c5b75d5d9bdbee44c827b0dd2766fa3d478a76b9c6735419228089d1b24536" dependencies = [ "arrayvec", - "axerrno 0.1.2", "log", "memory_addr", - "page_table_entry", + "page_table_entry 0.6.1", "riscv 0.16.0", "x86", ] @@ -3731,6 +3815,51 @@ dependencies = [ "tock-registers 0.9.0", ] +[[package]] +name = "pie-boot-if" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d979b0d1208dd8a57c5adb7d3c4e07bf15cbea3840123e864f6bfcb655c5e05" +dependencies = [ + "heapless 0.8.0", +] + +[[package]] +name = "pie-boot-loader-aarch64" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de8836eb8759cd65e70c73dc0f519345d8a734284e8e4cfc5889a6e445af9f09" +dependencies = [ + "aarch64-cpu 10.0.0", + "aarch64-cpu-ext", + "any-uart", + "bitflags 2.10.0", + "fdt-parser", + "kasm-aarch64", + "kdef-pgtable", + "log", + "num-align", + "page-table-generic", + "pie-boot-if", + "prettyplease", + "quote", + "spin 0.10.0", + "syn 2.0.111", + "thiserror 2.0.17", +] + +[[package]] +name = "pie-boot-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513f5ca7603771d7524bfb7e6ba8dded83d2e8ac02d46f15815e0c1144bca566" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -4382,7 +4511,6 @@ dependencies = [ "critical-section", "embedded-hal", "paste", - "riscv-macros 0.4.0", "riscv-types", ] @@ -4392,19 +4520,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b59d645e392e041ad18f5e529ed13242d8405c66bb192f59703ea2137017d0" -[[package]] -name = "riscv-h" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cffa652689d01c5f7033abe105e69f4d57ac85bf7e17da688bab10e4b9d3a2d8" -dependencies = [ - "bare-metal", - "bit_field", - "bitflags 2.11.0", - "log", - "riscv 0.14.0", -] - [[package]] name = "riscv-h" version = "0.2.0" @@ -4452,6 +4567,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3f2ad9f15a07f4a0e1677124f9120ce7e83ab7e1ca7186af0ca9da529b62e80" +[[package]] +name = "riscv_goldfish" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07aac72f95e774476db82916d79f2d303191310393830573c1ab5c821b21660a" + [[package]] name = "riscv_plic" version = "0.2.0" @@ -4463,29 +4584,45 @@ dependencies = [ [[package]] name = "riscv_vcpu" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6122b26f3d5920206f22b871fd13d79f5b23e570c7860f345d8736528dacc4c" +checksum = "8f7d192465c7a3e2f674c56d3e49cfeb00f84d0616137cbecb8aacb250e40c53" dependencies = [ "axaddrspace", - "axerrno 0.1.2", + "axerrno 0.2.2", "axvcpu", "axvisor_api", "bit_field", "bitflags 2.11.0", "cfg-if", - "crate_interface 0.1.4", + "crate_interface 0.3.0", "log", "memoffset", "memory_addr", - "page_table_entry", + "page_table_entry 0.6.1", "riscv 0.14.0", "riscv-decode", "riscv-h 0.2.0", "rustsbi", "sbi-rt", "sbi-spec", - "tock-registers 0.9.0", + "tock-registers 0.10.1", +] + +[[package]] +name = "riscv_vplic" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec84bba7d814f072afdd11a61c64ad55bedaeb17937a78bb6b9cec44b63a70fb" +dependencies = [ + "axaddrspace", + "axdevice_base", + "axerrno 0.2.2", + "axvisor_api", + "bitmaps", + "log", + "riscv-h", + "spin 0.10.0", ] [[package]] @@ -4830,6 +4967,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "scope-local" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a7d5ed5013e6436fcd78f2bcd3892a6286ef9ce6c9b61504d4c4a08d6a40eab" +dependencies = [ + "percpu", + "spin 0.10.0", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -5182,10 +5329,15 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df2e441db2f693b803ee5fdf85b89bcc1830c83ae483406d4f0bb74cf90473a6" dependencies = [ - "aarch64-cpu 11.2.0", - "anyhow", - "arm-gic-driver 0.17.0", - "kernutil", + "aarch64-cpu 10.0.0", + "aarch64-cpu-ext", + "any-uart", + "bindeps-simple", + "fdt-parser", + "futures", + "heapless 0.8.0", + "kasm-aarch64", + "kdef-pgtable", "log", "mmio-api", "page-table-generic", @@ -6575,26 +6727,26 @@ dependencies = [ [[package]] name = "x86_vcpu" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135b71adc527803929792ea1af4a2cd7c264c9cd3dd0096b03f9bc959505f73a" +checksum = "906f6ead2156dde21b712f0eb654b620d1c2652e62b6b0731a7807a565454692" dependencies = [ "axaddrspace", "axdevice_base", - "axerrno 0.1.2", + "axerrno 0.2.2", "axvcpu", "axvisor_api", "bit_field", "bitflags 2.11.0", "cfg-if", - "crate_interface 0.1.4", + "crate_interface 0.3.0", "log", "memory_addr", "numeric-enum-macro", - "page_table_entry", + "page_table_entry 0.6.1", "paste", "raw-cpuid 11.6.0", - "spin 0.9.8", + "spin 0.10.0", "x86", "x86_64", "x86_vlapic", @@ -6602,13 +6754,13 @@ dependencies = [ [[package]] name = "x86_vlapic" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809257bd2252fc337f3b494ae9230e4600bfa4e7d3f2478a0dd794e16849040d" +checksum = "73364dacf1435c64e3395567405311456c3087f7bb4852904332e0fa1379eb65" dependencies = [ "axaddrspace", "axdevice_base", - "axerrno 0.1.2", + "axerrno 0.2.2", "axvisor_api", "bit", "log", diff --git a/os/axvisor/Cargo.toml b/os/axvisor/Cargo.toml index 57e5b1c7b..d64ee8d49 100644 --- a/os/axvisor/Cargo.toml +++ b/os/axvisor/Cargo.toml @@ -34,58 +34,55 @@ path = "xtask/src/main.rs" [features] default = [] -ept-level-4 = ["axaddrspace/4-level-ept"] +ept-level-4 = [] fs = ["axstd/fs"] dyn-plat = ["axstd/plat-dyn"] # Driver features (from former driver crate) -rk3568-clk = ["dep:rk3568_clk"] rk3588-clk = ["dep:rk3588-clk"] sdmmc = ["dep:sdmmc"] rockchip-pm = ["dep:rockchip-pm"] phytium-blk = ["dep:phytium-mci"] [target.'cfg(not(any(windows, unix)))'.dependencies] -bitflags = "2.9.1" +bitflags = "2.2" cfg-if = "1.0" cpumask = "0.1.0" kernel_guard = "0.1" kspin = "0.1" -lazy_static = { version = "1.5", default-features = false, features = [ - "spin_no_std", -] } +lazy_static = {version = "1.5", default-features = false, features = ["spin_no_std"]} lazyinit = "0.2" log = "0.4" -spin = "0.9" +spin = "0.10" timer_list = "0.1.0" hashbrown = "0.14" # System dependent modules provided by ArceOS. axstd = { version = "=0.3.0-preview.3", features = [ - "alloc-level-1", - "paging", - "irq", - "multitask", - "task-ext", - "smp", - "hv", -] } + "alloc-level-1", + "paging", + "irq", + "multitask", + "task-ext", + "smp", + "hv", +]} # System dependent modules provided by ArceOS-Hypervisor (bare-metal only) -axaddrspace = "0.1.5" +axaddrspace = "0.3.0" axhvc = "0.2.0" # axruntime = { version = "=0.3.0-preview.1", features = ["alloc", "irq", "paging", "smp", "multitask"] } -axvcpu = "0.2.2" -axvm = "0.2.1" -axdevice = "0.2.1" -axdevice_base = "0.2.1" -axvisor_api = "0.1.0" +axvcpu = "0.3.0" +axvm = "0.3.0" +axdevice = "0.2.2" +axdevice_base = "0.2.2" +axvisor_api = "0.3.0" # System independent crates provided by ArceOS axerrno = "0.2.2" axklib = "0.3.0" # axcpu = { version = "0.3.0-preview.8", features = ["arm-el2"] } -byte-unit = { version = "5", default-features = false, features = ["byte"] } -crate_interface = "0.1.4" +byte-unit = {version = "5", default-features = false, features = ["byte"]} +crate_interface = "0.3" extern-trait = "0.4" fdt-parser = "0.4" memory_addr = "0.4.1" @@ -101,19 +98,10 @@ rd-block = "0.1" pcie = "0.5.0" # Optional driver dependencies -rk3568_clk = { version = "0.1", optional = true } -sdmmc = { version = "0.1", default-features = false, features = [ - "pio", -], optional = true } +sdmmc = { version = "0.1", default-features = false, features = ["pio"], optional = true } rk3588-clk = { version = "0.1", optional = true } rockchip-pm = { version = "0.4", optional = true } -phytium-mci = { version = "0.1", default-features = false, features = [ - "pio", -], optional = true } - -[target.'cfg(target_arch = "aarch64")'.dependencies] -aarch64-cpu-ext = "0.1" -arm-gic-driver = { version = "0.17", features = ["rdif"] } +phytium-mci = { version = "0.1", default-features = false, features = ["pio"], optional = true } [target.'cfg(target_arch = "x86_64")'.dependencies] axplat-x86-qemu-q35 = { version = "0.2.0", default-features = false, features = [ @@ -121,33 +109,39 @@ axplat-x86-qemu-q35 = { version = "0.2.0", default-features = false, features = "irq", "smp", ] } -axconfig = { version = "0.3.0-preview.3", features = ["plat-dyn"] } +axconfig = { version = "=0.3.0-preview.3", features = ["plat-dyn"] } + +[target.'cfg(target_arch = "aarch64")'.dependencies] +aarch64-cpu-ext = "0.1" +arm-gic-driver = {version = "0.17", features = ["rdif"]} + +[target.'cfg(target_arch = "riscv64")'.dependencies] +axplat-riscv64-qemu-virt = "0.3.1-pre.6" # xtask dependencies (only used on host platforms) [target.'cfg(any(windows, unix))'.dependencies] -axbuild = { version = "0.3.0-preview.3" } anyhow = { version = "1.0" } cargo_metadata = { version = "0.23" } chrono = { version = "0.4", features = ["serde"] } -clap = { version = "4.4", features = ["derive"] } +clap = {version = "4.4", features = ["derive"] } colored = { version = "3" } flate2 = { version = "1.0" } jkconfig = { version = "0.1" } ostool = { version = "0.8.4" } regex = { version = "1.12" } reqwest = { version = "0.13" } -schemars = { version = "1", features = ["derive"] } -serde = { version = "1.0", features = ["derive"] } +schemars = {version = "1", features = ["derive"] } +serde = {version = "1.0", features = ["derive"] } serde_json = { version = "1" } sha2 = { version = "0.10" } tar = { version = "0.4" } -tokio = { version = "1", features = ["full"] } +tokio = {version = "1", features = ["full"] } toml = { version = "0.9" } -axvmconfig = { version = "0.2", features = ["std"] } +axvmconfig = { version = "0.2.2", features = ["std"] } [build-dependencies] -axconfig = "0.3.0-preview.3" +axconfig = "=0.3.0-preview.3" prettyplease = "0.2" quote = "1.0" syn = "2.0" diff --git a/os/axvisor/build.rs b/os/axvisor/build.rs index bd5125d0a..ff8acb01e 100644 --- a/os/axvisor/build.rs +++ b/os/axvisor/build.rs @@ -260,6 +260,8 @@ fn main() -> anyhow::Result<()> { "aarch64-generic".to_string() } else if arch == "x86_64" { "x86-qemu-q35".to_string() + } else if arch == "riscv64" { + "riscv64-qemu-virt".to_string() } else { "dummy".to_string() }; diff --git a/os/axvisor/configs/board/orangepi-5-plus.toml b/os/axvisor/configs/board/orangepi-5-plus.toml index 21559ce51..465a3b29b 100644 --- a/os/axvisor/configs/board/orangepi-5-plus.toml +++ b/os/axvisor/configs/board/orangepi-5-plus.toml @@ -1,4 +1,7 @@ -cargo_args = [] +cargo_args = [ + "--config", + 'target.aarch64-unknown-none-softfloat.rustflags = ["-Crelocation-model=pic", "-Clink-args=-pie", "-Clink-args=-znostart-stop-gc", "-Clink-args=-Taxplat.x"]', +] features = [ # "ept-level-4", "dyn-plat", diff --git a/os/axvisor/configs/board/phytiumpi.toml b/os/axvisor/configs/board/phytiumpi.toml index be1fa2e66..d55d74892 100644 --- a/os/axvisor/configs/board/phytiumpi.toml +++ b/os/axvisor/configs/board/phytiumpi.toml @@ -1,4 +1,7 @@ -cargo_args = [] +cargo_args = [ + "--config", + 'target.aarch64-unknown-none-softfloat.rustflags = ["-Crelocation-model=pic", "-Clink-args=-pie", "-Clink-args=-znostart-stop-gc", "-Clink-args=-Taxplat.x"]', +] features = [ # "ept-level-4", "dyn-plat", @@ -10,4 +13,4 @@ features = [ log = "Info" target = "aarch64-unknown-none-softfloat" to_bin = true -vm_configs = [] +vm_configs = [] \ No newline at end of file diff --git a/os/axvisor/configs/board/qemu-aarch64.toml b/os/axvisor/configs/board/qemu-aarch64.toml index 2ec3fc39e..67f4aca43 100644 --- a/os/axvisor/configs/board/qemu-aarch64.toml +++ b/os/axvisor/configs/board/qemu-aarch64.toml @@ -2,10 +2,6 @@ cargo_args = [ # "--config", # 'target.aarch64-unknown-none-softfloat.rustflags = ["-Crelocation-model=pic", "-Clink-args=-pie", "-Clink-args=-znostart-stop-gc", "-Clink-args=-Taxplat.x"]', ] -# cargo_args = [ -# "--config", -# 'target.aarch64-unknown-none-softfloat.rustflags = ["-Clink-args=-Taxplat.x"]', -# ] features = [ "ept-level-4", "axstd/bus-mmio", diff --git a/os/axvisor/configs/board/qemu-riscv64.toml b/os/axvisor/configs/board/qemu-riscv64.toml new file mode 100644 index 000000000..62a82d795 --- /dev/null +++ b/os/axvisor/configs/board/qemu-riscv64.toml @@ -0,0 +1,12 @@ +cargo_args = [] +features = [ + "axstd/myplat", + "axstd/bus-mmio", + "ept-level-4", + # "fs", +] +log = "Info" +target = "riscv64gc-unknown-none-elf" +to_bin = true +vm_configs = [] +smp = 4 diff --git a/os/axvisor/configs/board/roc-rk3568-pc.toml b/os/axvisor/configs/board/roc-rk3568-pc.toml index 93833a314..9a40e5558 100644 --- a/os/axvisor/configs/board/roc-rk3568-pc.toml +++ b/os/axvisor/configs/board/roc-rk3568-pc.toml @@ -1,6 +1,16 @@ -cargo_args = [] -features = ["dyn-plat", "axstd/bus-mmio", "fs", "sdmmc", "rk3568-clk"] +cargo_args = [ + "--config", + 'target.aarch64-unknown-none-softfloat.rustflags = ["-Crelocation-model=pic", "-Clink-args=-pie", "-Clink-args=-znostart-stop-gc", "-Clink-args=-Taxplat.x"]', +] +features = [ + # "ept-level-4", + "dyn-plat", + "axstd/bus-mmio", + "fs", + "sdmmc", + "rk3568-clk", +] log = "Info" target = "aarch64-unknown-none-softfloat" to_bin = true -vm_configs = [] +vm_configs = [] \ No newline at end of file diff --git a/os/axvisor/configs/vms/arceos-riscv64-qemu-smp1.toml b/os/axvisor/configs/vms/arceos-riscv64-qemu-smp1.toml new file mode 100644 index 000000000..ff4a5ca1f --- /dev/null +++ b/os/axvisor/configs/vms/arceos-riscv64-qemu-smp1.toml @@ -0,0 +1,72 @@ +# Vm base info configs +# +[base] +# Guest vm id. +id = 1 +# Guest vm name. +name = "arceos-qemu" +# Virtualization type. +vm_type = 1 +# The number of virtual CPUs. +cpu_num = 1 +# Guest vm physical cpu ids. +phys_cpu_ids = [0] + +# +# Vm kernel configs +# +[kernel] +# The entry point of the kernel image. +entry_point = 0x8020_0000 +# The location of image: "memory" | "fs". +# load from memory. +image_location = "memory" +# The file path of the kernel image. +kernel_path = "/path/tmp/images/helloworld_riscv64-qemu-virt.bin" +# The load address of the kernel image. +kernel_load_addr = 0x8020_0000 +# The file path of the device tree blob (DTB). +#dtb_path = "path/aarch64-qemu-gicv3.dtb" +# The load address of the device tree blob (DTB). +dtb_load_addr = 0x8220_0000 + +## The file path of the ramdisk image. +# ramdisk_path = "" +## The load address of the ramdisk image. +# ramdisk_load_addr = 0 +## The path of the disk image. +# disk_path = "disk.img" + +# Memory regions with format (`base_paddr`, `size`, `flags`, `map_type`). +# For `map_type`, 0 means `MAP_ALLOC`, 1 means `MAP_IDENTICAL`. +memory_regions = [ + [0x8000_0000, 0x800_0000, 0x7, 0], # System RAM 1G MAP_ALLOC +] + +# +# Device specifications +# +[devices] +# Pass-through devices. +passthrough_devices = [ +] + +# Passthrough addresses. +# Base-GPA Length. +passthrough_addresses = [ + #[0x28041000, 0x100_0000] +] + +# Devices that are not desired to be passed through to the guest +excluded_devices = [ +] + +# Emu_devices. +# Name Base-Ipa Ipa_len Alloc-Irq Emu-Type EmuConfig. +emu_devices = [ + # ["gppt-gicd", 0x0800_0000, 0x1_0000, 0, 0x21, []], + # ["gppt-gicr", 0x080a_0000, 0x2_0000, 0, 0x20, [1, 0x2_0000, 0]], # 1 vcpu, stride 0x20000, starts with pcpu 0 + # ["gppt-gits", 0x0808_0000, 0x2_0000, 0, 0x22, [0x0808_0000]], # host_gits_base +] + +interrupt_mode = "passthrough" diff --git a/os/axvisor/configs/vms/linux-riscv64-qemu-smp1.dts b/os/axvisor/configs/vms/linux-riscv64-qemu-smp1.dts new file mode 100644 index 000000000..7a09d0979 --- /dev/null +++ b/os/axvisor/configs/vms/linux-riscv64-qemu-smp1.dts @@ -0,0 +1,132 @@ +/dts-v1/; + +/ { + #address-cells = <0x02>; + #size-cells = <0x02>; + compatible = "riscv-virtio"; + model = "riscv-virtio,qemu"; + + memory@90000000 { + device_type = "memory"; + reg = <0x00 0x90000000 0x00 0x40000000>; + }; + + cpus { + #address-cells = <0x01>; + #size-cells = <0x00>; + timebase-frequency = <0x989680>; + + cpu@0 { + phandle = <0x07>; + device_type = "cpu"; + reg = <0x00>; + status = "okay"; + compatible = "riscv"; + riscv,cbop-block-size = <0x40>; + riscv,cboz-block-size = <0x40>; + riscv,cbom-block-size = <0x40>; + riscv,isa-extensions = "i\0m\0a\0f\0d\0c"; + riscv,isa-base = "rv64i"; + riscv,isa = "rv64imafdc"; + mmu-type = "riscv,sv39"; + + interrupt-controller { + #interrupt-cells = <0x01>; + interrupt-controller; + compatible = "riscv,cpu-intc"; + phandle = <0x08>; + }; + }; + }; + + aliases { + serial0 = "/soc/serial@10000000"; + }; + + chosen { + bootargs = "earlycon=sbi console=ttyS0,115200 init=/init root=/dev/vda rw"; + stdout-path = "/soc/serial@10000000"; + }; + + soc { + #address-cells = <0x02>; + #size-cells = <0x02>; + compatible = "simple-bus"; + ranges; + + serial@10000000 { + interrupts = <0x0a>; + interrupt-parent = <0x09>; + clock-frequency = "\08@"; + reg = <0x00 0x10000000 0x00 0x100>; + compatible = "ns16550a"; + }; + + plic@c000000 { + phandle = <0x09>; + riscv,ndev = <0x5f>; + reg = <0x00 0xc000000 0x00 0x600000>; + interrupts-extended = <0x08 0x0b 0x08 0x09>; + interrupt-controller; + compatible = "sifive,plic-1.0.0\0riscv,plic0"; + #address-cells = <0x00>; + #interrupt-cells = <0x01>; + }; + + virtio_mmio@10008000 { + interrupts = <0x08>; + interrupt-parent = <0x09>; + reg = <0x00 0x10008000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10007000 { + interrupts = <0x07>; + interrupt-parent = <0x09>; + reg = <0x00 0x10007000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10006000 { + interrupts = <0x06>; + interrupt-parent = <0x09>; + reg = <0x00 0x10006000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10005000 { + interrupts = <0x05>; + interrupt-parent = <0x09>; + reg = <0x00 0x10005000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10004000 { + interrupts = <0x04>; + interrupt-parent = <0x09>; + reg = <0x00 0x10004000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10003000 { + interrupts = <0x03>; + interrupt-parent = <0x09>; + reg = <0x00 0x10003000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10002000 { + interrupts = <0x02>; + interrupt-parent = <0x09>; + reg = <0x00 0x10002000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10001000 { + interrupts = <0x01>; + interrupt-parent = <0x09>; + reg = <0x00 0x10001000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + }; +}; diff --git a/os/axvisor/configs/vms/linux-riscv64-qemu-smp1.toml b/os/axvisor/configs/vms/linux-riscv64-qemu-smp1.toml new file mode 100644 index 000000000..7aeacebbf --- /dev/null +++ b/os/axvisor/configs/vms/linux-riscv64-qemu-smp1.toml @@ -0,0 +1,67 @@ +# Vm base info configs +# +[base] +# Guest vm id. +id = 1 +# Guest vm name. +name = "linux-qemu" +# Virtualization type. +vm_type = 1 +# The number of virtual CPUs. +cpu_num = 1 +# Guest vm physical cpu sets. +phys_cpu_ids = [0] + +# +# Vm kernel configs +# +[kernel] +# The entry point of the kernel image. +entry_point = 0x9000_0000 +# The location of image: "memory" | "fs". +# load from memory. +image_location = "memory" +# The file path of the kernel image. +# kernel_path = "linux-6.6.62.bin" +kernel_path = "/path/tmp/images/qemu_riscv64_linux/qemu-riscv64" +# The load address of the kernel image. +kernel_load_addr = 0x9000_0000 +# The file path of the device tree blob (DTB). +dtb_path = "/path/tmp/configs/linux-riscv64-qemu-smp1.dtb" +# The load address of the device tree blob (DTB). +dtb_load_addr = 0x9300_0000 + +# Memory regions with format (`base_paddr`, `size`, `flags`, `map_type`). +# For `map_type`, 0 means `MAP_ALLOC`, 1 means `MAP_IDENTICAL`. +memory_regions = [ + [0x9000_0000, 0x4000_0000, 0x7, 2], # System RAM 1G MAP_IDENTICAL +] + +# +# Device specifications +# +[devices] +# Pass-through devices. +# Name Base-Ipa Base-Pa Length Alloc-Irq. +passthrough_devices = [ + # ["/soc/serial@10000000", 0x1000_0000, 0x1000_0000, 0x1000, 33], +] + +# Passthrough addresses. +# Base-GPA Length. +passthrough_addresses = [ + [0x1000_0000, 0x1000], # uart + [0x1000_1000, 0x8000], # virtio-mmio +] + +# Devices that are not desired to be passed through to the guest +excluded_devices = [ +] + +# Emu_devices. +# Name Base-Ipa Ipa_len Alloc-Irq Emu-Type EmuConfig. +emu_devices = [ + ["plic", 0x0c00_0000, 0x60_0000, 0, 0x30, [2]], # [context_num] +] + +interrupt_mode = "passthrough" diff --git a/os/axvisor/configs/vms/nimbos-aarch64-qemu-smp1.toml b/os/axvisor/configs/vms/nimbos-aarch64-qemu-smp1.toml index 17dac0d43..e280c206b 100644 --- a/os/axvisor/configs/vms/nimbos-aarch64-qemu-smp1.toml +++ b/os/axvisor/configs/vms/nimbos-aarch64-qemu-smp1.toml @@ -73,6 +73,7 @@ passthrough_devices = [ # Base-GPA Length. passthrough_addresses = [ #[0x28041000, 0x100_0000] + [0x0800_0000, 0x100_0000] ] # Devices that are not desired to be passed through to the guest diff --git a/os/axvisor/platform/riscv64-qemu-virt/Cargo.toml b/os/axvisor/platform/riscv64-qemu-virt/Cargo.toml new file mode 100644 index 000000000..869095bea --- /dev/null +++ b/os/axvisor/platform/riscv64-qemu-virt/Cargo.toml @@ -0,0 +1,33 @@ +[package] +edition = "2024" +name = "axplat-riscv64-qemu-virt" +version = "0.3.0" + +[features] +default = [ + "irq", + "smp", +] +fp-simd = ["axcpu/fp-simd"] +irq = ["axplat/irq"] +rtc = ["riscv_goldfish"] +smp = ["axplat/smp"] + +[dependencies] +kspin = "0.1" +lazyinit = "0.2" +log = "0.4" +riscv = "0.14.0" +riscv_goldfish = { version = "0.1", optional = true } +riscv_plic = "0.2" +sbi-rt = { version = "0.0.3", features = ["legacy"] } +uart_16550 = "0.4.0" + +axconfig-macros = "0.2" +axcpu = { version = "0.3.0-preview.8", features = ["arm-el2"] } +axplat = "0.3.1-pre.6" +axvisor_api = "0.3" +crate_interface = "0.3" + +[package.metadata.docs.rs] +targets = ["riscv64gc-unknown-none-elf"] diff --git a/os/axvisor/platform/riscv64-qemu-virt/axconfig.toml b/os/axvisor/platform/riscv64-qemu-virt/axconfig.toml new file mode 100644 index 000000000..581a4a741 --- /dev/null +++ b/os/axvisor/platform/riscv64-qemu-virt/axconfig.toml @@ -0,0 +1,105 @@ +# Architecture identifier. +arch = "riscv64" # str +# Platform identifier. +platform = "riscv64-qemu-virt" # str +# Platform package. +package = "axplat-riscv64-qemu-virt" # str + +# +# Platform configs +# +[plat] +# Number of CPUs. +cpu-num = 4 # uint +# Base address of the whole physical memory. +phys-memory-base = 0x8000_0000 # uint +# Size of the whole physical memory. (8GB) +phys-memory-size = 0x1_0000_0000 # uint +# Base physical address of the kernel image. +kernel-base-paddr = 0x8020_0000 # uint +# Base virtual address of the kernel image. +kernel-base-vaddr = "0xffff_ffc0_8020_0000" # uint +# Linear mapping offset, for quick conversions between physical and virtual +# addresses. +phys-virt-offset = "0xffff_ffc0_0000_0000" # uint +# Offset of bus address and phys address. some boards, the bus address is +# different from the physical address. +phys-bus-offset = 0 # uint +# Kernel address space base. +kernel-aspace-base = "0xffff_ffc0_0000_0000" # uint +# Kernel address space size. +kernel-aspace-size = "0x0000_003f_ffff_f000" # uint +# Stack size on bootstrapping. (256K) +boot-stack-size = 0x40000 # uint + +# +# Device specifications +# +[devices] +# MMIO ranges with format (`base_paddr`, `size`). +mmio-ranges = [ + [0x0010_1000, 0x1000], # RTC + [0x0c00_0000, 0x21_0000], # PLIC + [0x1000_0000, 0x1000], # UART + [0x1000_1000, 0x8000], # VirtIO + [0x3000_0000, 0x1000_0000], # PCI config space + [0x4000_0000, 0x4000_0000], # PCI memory ranges (ranges 1: 32-bit MMIO space) + [0x2_7fe0_0000, 0x1000_0000] +] # [(uint, uint)] +# VirtIO MMIO ranges with format (`base_paddr`, `size`). +virtio-mmio-ranges = [ + [0x1000_1000, 0x1000], + [0x1000_2000, 0x1000], + [0x1000_3000, 0x1000], + [0x1000_4000, 0x1000], + [0x1000_5000, 0x1000], + [0x1000_6000, 0x1000], + [0x1000_7000, 0x1000], + [0x1000_8000, 0x1000], +] # [(uint, uint)] +# Base physical address of the PCIe ECAM space. +pci-ecam-base = 0x3000_0000 # uint +# End PCI bus number (`bus-range` property in device tree). +pci-bus-end = 0xff # uint +# PCI device memory ranges (`ranges` property in device tree). +pci-ranges = [ + [0x0300_0000, 0x1_0000], # PIO space + [0x4000_0000, 0x4000_0000], # 32-bit MMIO space + [0x4_0000_0000, 0x4_0000_0000], # 64-bit MMIO space +] # [(uint, uint)] + +# Timer interrupt frequency in Hz. +timer-frequency = 10_000_000 # uint +# Timer interrupt num. +timer-irq = "0x8000_0000_0000_0005" # uint +# IPI interrupt num +ipi-irq = "0x8000_0000_0000_0001" # uint + +# rtc@101000 { +# interrupts = <0x0b>; +# interrupt-parent = <0x03>; +# reg = <0x00 0x101000 0x00 0x1000>; +# compatible = "google,goldfish-rtc"; +# }; +# RTC (goldfish) Address +rtc-paddr = 0x10_1000 # uint + +# plic@c000000 { +# phandle = <0x03>; +# riscv,ndev = <0x5f>; +# reg = <0x00 0xc000000 0x00 0x600000>; +# interrupts-extended = <0x02 0x0b 0x02 0x09>; +# interrupt-controller; +# compatible = "sifive,plic-1.0.0\0riscv,plic0"; +# }; +plic-paddr = 0x0c00_0000 # uint + +# serial@10000000 { +# interrupts = <0x0a>; +# interrupt-parent = <0x03>; +# clock-frequency = "\08@"; +# reg = <0x00 0x10000000 0x00 0x100>; +# compatible = "ns16550a"; +# }; +uart-paddr = 0x1000_0000 # uint +uart-irq = 0x0a # uint \ No newline at end of file diff --git a/os/axvisor/platform/riscv64-qemu-virt/build.rs b/os/axvisor/platform/riscv64-qemu-virt/build.rs new file mode 100644 index 000000000..80a4c120e --- /dev/null +++ b/os/axvisor/platform/riscv64-qemu-virt/build.rs @@ -0,0 +1,23 @@ +fn main() { + println!("cargo:rerun-if-env-changed=AXVISOR_SMP"); + println!("cargo:rerun-if-changed=linker.lds.S"); + + let mut smp = 1; + if let Ok(s) = std::env::var("AXVISOR_SMP") { + smp = s.parse::().unwrap_or(1); + } + + let ld_content = include_str!("linker.lds.S"); + let ld_content = ld_content.replace("%ARCH%", "riscv"); + let ld_content = ld_content.replace( + "%KERNEL_BASE%", + &format!("{:#x}", 0xffff_ffc0_8020_0000usize), + ); + let ld_content = ld_content.replace("%SMP%", &format!("{smp}",)); + + // target///build/axvisor-xxxx/out + let out_dir = std::env::var("OUT_DIR").unwrap(); + let out_path = std::path::Path::new(&out_dir).join("link.x"); + println!("cargo:rustc-link-search={out_dir}"); + std::fs::write(out_path, ld_content).unwrap(); +} diff --git a/os/axvisor/platform/riscv64-qemu-virt/linker.lds.S b/os/axvisor/platform/riscv64-qemu-virt/linker.lds.S new file mode 100644 index 000000000..439284c71 --- /dev/null +++ b/os/axvisor/platform/riscv64-qemu-virt/linker.lds.S @@ -0,0 +1,99 @@ +OUTPUT_ARCH(%ARCH%) + +BASE_ADDRESS = %KERNEL_BASE%; +SMP = %SMP%; + +ENTRY(_start) +SECTIONS +{ + . = BASE_ADDRESS; + _skernel = .; + + .text : ALIGN(4K) { + _stext = .; + *(.text.boot) + *(.text .text.*) + . = ALIGN(4K); + _etext = .; + } + + .rodata : ALIGN(4K) { + _srodata = .; + *(.rodata .rodata.*) + *(.srodata .srodata.*) + *(.sdata2 .sdata2.*) + . = ALIGN(4K); + _erodata = .; + } + + .data : ALIGN(4K) { + _sdata = .; + *(.data.boot_page_table) + . = ALIGN(4K); + __sdriver_register = .; + KEEP(*(.driver.register*)) + __edriver_register = .; + + *(.data .data.*) + *(.sdata .sdata.*) + *(.got .got.*) + } + + .tdata : ALIGN(0x10) { + _stdata = .; + *(.tdata .tdata.*) + _etdata = .; + } + + .tbss : ALIGN(0x10) { + _stbss = .; + *(.tbss .tbss.*) + *(.tcommon) + _etbss = .; + } + + . = ALIGN(4K); + _percpu_start = .; + _percpu_end = _percpu_start + SIZEOF(.percpu); + .percpu 0x0 : AT(_percpu_start) { + _percpu_load_start = .; + *(.percpu .percpu.*) + _percpu_load_end = .; + . = _percpu_load_start + ALIGN(64) * SMP; + } + . = _percpu_end; + + . = ALIGN(4K); + _edata = .; + + .bss : ALIGN(4K) { + boot_stack = .; + *(.bss.stack) + . = ALIGN(4K); + boot_stack_top = .; + + _sbss = .; + *(.bss .bss.*) + *(.sbss .sbss.*) + *(COMMON) + . = ALIGN(4K); + _ebss = .; + } + + _ekernel = .; + + /DISCARD/ : { + *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) + } +} + +SECTIONS { + linkme_IRQ : { *(linkme_IRQ) } + linkm2_IRQ : { *(linkm2_IRQ) } + linkme_PAGE_FAULT : { *(linkme_PAGE_FAULT) } + linkm2_PAGE_FAULT : { *(linkm2_PAGE_FAULT) } + linkme_SYSCALL : { *(linkme_SYSCALL) } + linkm2_SYSCALL : { *(linkm2_SYSCALL) } + axns_resource : { *(axns_resource) } +} +INSERT AFTER .tbss; diff --git a/os/axvisor/platform/riscv64-qemu-virt/src/boot.rs b/os/axvisor/platform/riscv64-qemu-virt/src/boot.rs new file mode 100644 index 000000000..e3a690a82 --- /dev/null +++ b/os/axvisor/platform/riscv64-qemu-virt/src/boot.rs @@ -0,0 +1,98 @@ +use crate::config::plat::{BOOT_STACK_SIZE, PHYS_VIRT_OFFSET}; +use axplat::mem::{Aligned4K, pa}; + +#[unsafe(link_section = ".bss.stack")] +static mut BOOT_STACK: [u8; BOOT_STACK_SIZE] = [0; BOOT_STACK_SIZE]; + +#[unsafe(link_section = ".data")] +static mut BOOT_PT_SV39: Aligned4K<[u64; 512]> = Aligned4K::new([0; 512]); + +#[allow(clippy::identity_op)] // (0x0 << 10) here makes sense because it's an address +unsafe fn init_boot_page_table() { + unsafe { + // 0x0000_0000..0x4000_0000, VRWX_GAD, 1G block + BOOT_PT_SV39[0] = (0x0 << 10) | 0xef; + // 0x8000_0000..0xc000_0000, VRWX_GAD, 4G block + BOOT_PT_SV39[2] = (0x80000 << 10) | 0xef; + BOOT_PT_SV39[3] = (0xC0000 << 10) | 0xef; + BOOT_PT_SV39[4] = (0x100000 << 10) | 0xef; + BOOT_PT_SV39[5] = (0x140000 << 10) | 0xef; + // 0xffff_ffc0_0000_0000..0xffff_ffc0_4000_0000, VRWX_GAD, 1G block + BOOT_PT_SV39[0x100] = (0x0 << 10) | 0xef; + // 0xffff_ffc0_8000_0000..0xffff_ffc0_c000_0000, VRWX_GAD, 1G block + BOOT_PT_SV39[0x102] = (0x80000 << 10) | 0xef; + BOOT_PT_SV39[0x103] = (0xC0000 << 10) | 0xef; + BOOT_PT_SV39[0x104] = (0x100000 << 10) | 0xef; + BOOT_PT_SV39[0x105] = (0x140000 << 10) | 0xef; + } +} + +unsafe fn init_mmu() { + unsafe { + axcpu::asm::write_kernel_page_table(pa!(&raw const BOOT_PT_SV39 as usize)); + axcpu::asm::flush_tlb(None); + } +} + +/// The earliest entry point for the primary CPU. +#[unsafe(naked)] +#[unsafe(no_mangle)] +#[unsafe(link_section = ".text.boot")] +unsafe extern "C" fn _start() -> ! { + // PC = 0x8020_0000 + // a0 = hartid + // a1 = dtb + core::arch::naked_asm!(" + mv s0, a0 // save hartid + mv s1, a1 // save DTB pointer + la sp, {boot_stack} + li t0, {boot_stack_size} + add sp, sp, t0 // setup boot stack + + call {init_boot_page_table} + call {init_mmu} // setup boot page table and enabel MMU + + li s2, {phys_virt_offset} // fix up virtual high address + add sp, sp, s2 + + mv a0, s0 + mv a1, s1 + la a2, {entry} + add a2, a2, s2 + jalr a2 // call_main(cpu_id, dtb) + j .", + phys_virt_offset = const PHYS_VIRT_OFFSET, + boot_stack_size = const BOOT_STACK_SIZE, + boot_stack = sym BOOT_STACK, + init_boot_page_table = sym init_boot_page_table, + init_mmu = sym init_mmu, + entry = sym axplat::call_main, + ) +} + +/// The earliest entry point for secondary CPUs. +#[cfg(feature = "smp")] +#[unsafe(naked)] +pub(crate) unsafe extern fn _start_secondary() -> ! { + // a0 = hartid + // a1 = SP + core::arch::naked_asm!(" + mv s0, a0 // save hartid + mv sp, a1 // set SP + + call {init_mmu} // setup boot page table and enabel MMU + + li s1, {phys_virt_offset} // fix up virtual high address + add a1, a1, s1 + add sp, sp, s1 + + mv a0, s0 + la a1, {entry} + add a1, a1, s1 + jalr a1 // call_secondary_main(cpu_id) + j .", + phys_virt_offset = const PHYS_VIRT_OFFSET, + init_mmu = sym init_mmu, + entry = sym axplat::call_secondary_main, + ) +} diff --git a/os/axvisor/platform/riscv64-qemu-virt/src/console.rs b/os/axvisor/platform/riscv64-qemu-virt/src/console.rs new file mode 100644 index 000000000..037f5cc6a --- /dev/null +++ b/os/axvisor/platform/riscv64-qemu-virt/src/console.rs @@ -0,0 +1,54 @@ +use axplat::console::ConsoleIf; +use kspin::SpinNoIrq; +use lazyinit::LazyInit; +use uart_16550::MmioSerialPort; + +use crate::config::{devices::UART_PADDR, plat::PHYS_VIRT_OFFSET}; + +static UART: LazyInit> = LazyInit::new(); + +pub(crate) fn init_early() { + UART.init_once({ + let mut uart = unsafe { MmioSerialPort::new(UART_PADDR + PHYS_VIRT_OFFSET) }; + uart.init(); + SpinNoIrq::new(uart) + }); +} + +struct ConsoleIfImpl; + +#[impl_plat_interface] +impl ConsoleIf for ConsoleIfImpl { + /// Writes bytes to the console from input u8 slice. + fn write_bytes(bytes: &[u8]) { + for &c in bytes { + let mut uart = UART.lock(); + match c { + b'\n' => { + uart.send_raw(b'\r'); + uart.send_raw(b'\n'); + } + c => uart.send_raw(c), + } + } + } + + /// Reads bytes from the console into the given mutable slice. + /// Returns the number of bytes read. + fn read_bytes(bytes: &mut [u8]) -> usize { + let mut uart = UART.lock(); + for (i, byte) in bytes.iter_mut().enumerate() { + match uart.try_receive() { + Ok(c) => *byte = c, + Err(_) => return i, + } + } + bytes.len() + } + + /// Returns the IRQ number for the console, if applicable. + #[cfg(feature = "irq")] + fn irq_num() -> Option { + Some(crate::config::devices::UART_IRQ) + } +} diff --git a/os/axvisor/platform/riscv64-qemu-virt/src/init.rs b/os/axvisor/platform/riscv64-qemu-virt/src/init.rs new file mode 100644 index 000000000..f90db4e90 --- /dev/null +++ b/os/axvisor/platform/riscv64-qemu-virt/src/init.rs @@ -0,0 +1,40 @@ +use axplat::init::InitIf; + +struct InitIfImpl; + +#[impl_plat_interface] +impl InitIf for InitIfImpl { + /// This function should be called immediately after the kernel has booted, + /// and performed earliest platform configuration and initialization (e.g., + /// early console, clocking). + fn init_early(_cpu_id: usize, _mbi: usize) { + axcpu::init::init_trap(); + crate::console::init_early(); + crate::time::init_early(); + } + + /// Initializes the platform at the early stage for secondary cores. + #[cfg(feature = "smp")] + fn init_early_secondary(_cpu_id: usize) { + axcpu::init::init_trap(); + } + + /// Initializes the platform at the later stage for the primary core. + /// + /// This function should be called after the kernel has done part of its + /// initialization (e.g, logging, memory management), and finalized the rest of + /// platform configuration and initialization. + fn init_later(_cpu_id: usize, _arg: usize) { + #[cfg(feature = "irq")] + crate::irq::init_percpu(); + crate::time::init_percpu(); + } + + /// Initializes the platform at the later stage for secondary cores. + #[cfg(feature = "smp")] + fn init_later_secondary(_cpu_id: usize) { + #[cfg(feature = "irq")] + crate::irq::init_percpu(); + crate::time::init_percpu(); + } +} diff --git a/os/axvisor/platform/riscv64-qemu-virt/src/irq.rs b/os/axvisor/platform/riscv64-qemu-virt/src/irq.rs new file mode 100644 index 000000000..6e06037dc --- /dev/null +++ b/os/axvisor/platform/riscv64-qemu-virt/src/irq.rs @@ -0,0 +1,264 @@ +use core::{ + num::NonZeroU32, + ptr::NonNull, + sync::atomic::{AtomicPtr, Ordering}, +}; + +use axplat::{ + irq::{HandlerTable, IpiTarget, IrqHandler, IrqIf}, + percpu::this_cpu_id, +}; +use kspin::SpinNoIrq; +use riscv::register::sie; +use riscv_plic::Plic; +use sbi_rt::HartMask; + +use crate::config::{devices::PLIC_PADDR, plat::PHYS_VIRT_OFFSET}; + +/// Use call_interface with the trait path as known to crate_interface +/// Interface for injecting virtual interrupts to guest VMs. +/// This trait is defined and implemented in the kernel (axvisor). +#[crate_interface::def_interface] +pub trait InjectIrqIf { + fn inject_virtual_interrupt(irq: usize); +} + +/// `Interrupt` bit in `scause` +pub(super) const INTC_IRQ_BASE: usize = 1 << (usize::BITS - 1); + +/// Supervisor software interrupt in `scause` +#[allow(unused)] +pub(super) const S_SOFT: usize = INTC_IRQ_BASE + 1; + +/// Supervisor timer interrupt in `scause` +pub(super) const S_TIMER: usize = INTC_IRQ_BASE + 5; + +/// Supervisor external interrupt in `scause` +pub(super) const S_EXT: usize = INTC_IRQ_BASE + 9; + +static TIMER_HANDLER: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut()); + +static IPI_HANDLER: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut()); + +/// The maximum number of IRQs. +pub const MAX_IRQ_COUNT: usize = 1024; + +static IRQ_HANDLER_TABLE: HandlerTable = HandlerTable::new(); + +static PLIC: SpinNoIrq = SpinNoIrq::new(unsafe { + Plic::new(NonNull::new((PHYS_VIRT_OFFSET + PLIC_PADDR) as *mut _).unwrap()) +}); + +fn this_context() -> usize { + let hart_id = this_cpu_id(); + hart_id * 2 + 1 // supervisor context +} + +pub(super) fn init_percpu() { + // enable soft interrupts, timer interrupts, and external interrupts + unsafe { + sie::set_ssoft(); + sie::set_stimer(); + sie::set_sext(); + } + PLIC.lock().init_by_context(this_context()); +} + +macro_rules! with_cause { + ($cause: expr, @S_TIMER => $timer_op: expr, @S_SOFT => $ipi_op: expr, @S_EXT => $ext_op: expr, @EX_IRQ => $plic_op: expr $(,)?) => { + match $cause { + S_TIMER => $timer_op, + S_SOFT => $ipi_op, + S_EXT => $ext_op, + other => { + if other & INTC_IRQ_BASE == 0 { + // Device-side interrupts read from PLIC + $plic_op + } else { + // Other CPU-side interrupts + panic!("Unknown IRQ cause: {other}"); + } + } + } + }; +} + +struct IrqIfImpl; + +#[impl_plat_interface] +impl IrqIf for IrqIfImpl { + /// Enables or disables the given IRQ. + fn set_enable(irq: usize, enabled: bool) { + with_cause!( + irq, + @S_TIMER => { + unsafe { + if enabled { + sie::set_stimer(); + } else { + sie::clear_stimer(); + } + } + }, + @S_SOFT => {}, + @S_EXT => {}, + @EX_IRQ => { + let Some(irq) = NonZeroU32::new(irq as _) else { + return; + }; + trace!("PLIC set enable: {irq} {enabled}"); + let mut plic = PLIC.lock(); + if enabled { + plic.set_priority(irq, 6); + plic.enable(irq, this_context()); + } else { + plic.disable(irq, this_context()); + } + } + ); + } + + /// Registers an IRQ handler for the given IRQ. + /// + /// It also enables the IRQ if the registration succeeds. It returns `false` if + /// the registration failed. + /// + /// The `irq` parameter has the following semantics + /// 1. If its highest bit is 1, it means it is an interrupt on the CPU side. Its + /// value comes from `scause`, where [`S_SOFT`] represents software interrupt + /// and [`S_TIMER`] represents timer interrupt. If its value is [`S_EXT`], it + /// means it is an external interrupt, and the real IRQ number needs to + /// be obtained from PLIC. + /// 2. If its highest bit is 0, it means it is an interrupt on the device side, + /// and its value is equal to the IRQ number provided by PLIC. + fn register(irq: usize, handler: IrqHandler) -> bool { + with_cause!( + irq, + @S_TIMER => TIMER_HANDLER.compare_exchange(core::ptr::null_mut(), handler as *mut _, Ordering::AcqRel, Ordering::Acquire).is_ok(), + @S_SOFT => IPI_HANDLER.compare_exchange(core::ptr::null_mut(), handler as *mut _, Ordering::AcqRel, Ordering::Acquire).is_ok(), + @S_EXT => { + warn!("External IRQ should be got from PLIC, not scause"); + false + }, + @EX_IRQ => { + if IRQ_HANDLER_TABLE.register_handler(irq, handler) { + Self::set_enable(irq, true); + true + } else { + warn!("register handler for External IRQ {irq} failed"); + false + } + } + ) + } + + /// Unregisters the IRQ handler for the given IRQ. + /// + /// It also disables the IRQ if the unregistration succeeds. It returns the + /// existing handler if it is registered, `None` otherwise. + fn unregister(irq: usize) -> Option { + with_cause!( + irq, + @S_TIMER => { + let handler = TIMER_HANDLER.swap(core::ptr::null_mut(), Ordering::AcqRel); + if !handler.is_null() { + Some(unsafe { core::mem::transmute::<*mut (), IrqHandler>(handler) }) + } else { + None + } + }, + @S_SOFT => { + let handler = IPI_HANDLER.swap(core::ptr::null_mut(), Ordering::AcqRel); + if !handler.is_null() { + Some(unsafe { core::mem::transmute::<*mut (), IrqHandler>(handler) }) + } else { + None + } + }, + @S_EXT => { + warn!("External IRQ should be got from PLIC, not scause"); + None + }, + @EX_IRQ => IRQ_HANDLER_TABLE.unregister_handler(irq).inspect(|_| Self::set_enable(irq, false)) + ) + } + + /// Handles the IRQ. + /// + /// It is called by the common interrupt handler. It should look up in the + /// IRQ handler table and calls the corresponding handler. If necessary, it + /// also acknowledges the interrupt controller after handling. + fn handle(irq: usize) -> Option { + with_cause!( + irq, + @S_TIMER => { + trace!("IRQ: timer"); + let handler = TIMER_HANDLER.load(Ordering::Acquire); + if !handler.is_null() { + // SAFETY: The handler is guaranteed to be a valid function pointer. + unsafe { core::mem::transmute::<*mut (), IrqHandler>(handler)() }; + } + Some(irq) + }, + @S_SOFT => { + trace!("IRQ: IPI"); + let handler = IPI_HANDLER.load(Ordering::Acquire); + if !handler.is_null() { + // SAFETY: The handler is guaranteed to be a valid function pointer. + unsafe { core::mem::transmute::<*mut (), IrqHandler>(handler)() }; + } + Some(irq) + }, + @S_EXT => { + // TODO: judge irq's ownership before handling (axvisor or any vm). + // Maybe later it will be done by registering all irqs IQR_HANDLER_TABLE. + + let mut plic = PLIC.lock(); + let Some(irq) = plic.claim(this_context()) else { + debug!("Spurious external IRQ"); + return None; + }; + drop(plic); + // Inject the virtual interrupt to the guest VM + crate_interface::call_interface!(InjectIrqIf::inject_virtual_interrupt, irq.get() as usize); + + // trace!("IRQ: external {irq}"); + // IRQ_HANDLER_TABLE.handle(irq.get() as usize); + // Only for irqs that belong to axvisor, complete the IRQ. + // plic.complete(this_context(), irq); + Some(irq.get() as usize) + }, + @EX_IRQ => { + unreachable!("Device-side IRQs should be handled by triggering the External Interrupt."); + } + ) + } + + /// Sends an inter-processor interrupt (IPI) to the specified target CPU or all CPUs. + fn send_ipi(_irq_num: usize, target: IpiTarget) { + match target { + IpiTarget::Current { cpu_id } => { + let res = sbi_rt::send_ipi(HartMask::from_mask_base(1 << cpu_id, 0)); + if res.is_err() { + warn!("send_ipi failed: {res:?}"); + } + } + IpiTarget::Other { cpu_id } => { + let res = sbi_rt::send_ipi(HartMask::from_mask_base(1 << cpu_id, 0)); + if res.is_err() { + warn!("send_ipi failed: {res:?}"); + } + } + IpiTarget::AllExceptCurrent { cpu_id, cpu_num } => { + for i in 0..cpu_num { + if i != cpu_id { + let res = sbi_rt::send_ipi(HartMask::from_mask_base(1 << i, 0)); + if res.is_err() { + warn!("send_ipi_all_others failed: {res:?}"); + } + } + } + } + } + } +} diff --git a/os/axvisor/platform/riscv64-qemu-virt/src/lib.rs b/os/axvisor/platform/riscv64-qemu-virt/src/lib.rs new file mode 100644 index 000000000..32d538179 --- /dev/null +++ b/os/axvisor/platform/riscv64-qemu-virt/src/lib.rs @@ -0,0 +1,39 @@ +#![no_std] + +#[macro_use] +extern crate log; +#[macro_use] +extern crate axplat; + +#[cfg(feature = "irq")] +pub mod irq; + +mod boot; +mod console; +mod init; +mod mem; +mod power; +mod time; + +pub mod config { + //! Platform configuration module. + //! + //! If the `AX_CONFIG_PATH` environment variable is set, it will load the configuration from the specified path. + //! Otherwise, it will fall back to the `axconfig.toml` file in the current directory and generate the default configuration. + //! + //! If the `PACKAGE` field in the configuration does not match the package name, it will panic with an error message. + axconfig_macros::include_configs!(path_env = "AX_CONFIG_PATH", fallback = "axconfig.toml"); + assert_str_eq!( + PACKAGE, + env!("CARGO_PKG_NAME"), + "`PACKAGE` field in the configuration does not match the Package name. Please check your configuration file." + ); +} + +pub const fn cpu_count() -> usize { + config::plat::CPU_NUM +} + +pub const fn plic_base() -> usize { + config::devices::PLIC_PADDR +} diff --git a/os/axvisor/platform/riscv64-qemu-virt/src/mem.rs b/os/axvisor/platform/riscv64-qemu-virt/src/mem.rs new file mode 100644 index 000000000..13000df04 --- /dev/null +++ b/os/axvisor/platform/riscv64-qemu-virt/src/mem.rs @@ -0,0 +1,58 @@ +use axplat::mem::{MemIf, PhysAddr, RawRange, VirtAddr, pa, va}; + +use crate::config::devices::MMIO_RANGES; +use crate::config::plat::{ + KERNEL_BASE_PADDR, PHYS_MEMORY_BASE, PHYS_MEMORY_SIZE, PHYS_VIRT_OFFSET, +}; + +struct MemIfImpl; + +#[impl_plat_interface] +impl MemIf for MemIfImpl { + /// Returns all physical memory (RAM) ranges on the platform. + /// + /// All memory ranges except reserved ranges (including the kernel loaded + /// range) are free for allocation. + fn phys_ram_ranges() -> &'static [RawRange] { + // TODO: paser dtb to get the available memory ranges + // We can't directly use `PHYS_MEMORY_BASE` here, because it may has been used by sbi. + &[( + KERNEL_BASE_PADDR, + PHYS_MEMORY_BASE + PHYS_MEMORY_SIZE - KERNEL_BASE_PADDR, + )] + } + + /// Returns all reserved physical memory ranges on the platform. + /// + /// Reserved memory can be contained in [`phys_ram_ranges`], they are not + /// allocatable but should be mapped to kernel's address space. + /// + /// Note that the ranges returned should not include the range where the + /// kernel is loaded. + fn reserved_phys_ram_ranges() -> &'static [RawRange] { + &[] + } + + /// Returns all device memory (MMIO) ranges on the platform. + fn mmio_ranges() -> &'static [RawRange] { + &MMIO_RANGES + } + + /// Translates a physical address to a virtual address. + fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { + va!(paddr.as_usize() + PHYS_VIRT_OFFSET) + } + + /// Translates a virtual address to a physical address. + fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr { + pa!(vaddr.as_usize() - PHYS_VIRT_OFFSET) + } + + /// Returns the kernel address space base virtual address and size. + fn kernel_aspace() -> (VirtAddr, usize) { + ( + va!(crate::config::plat::KERNEL_ASPACE_BASE), + crate::config::plat::KERNEL_ASPACE_SIZE, + ) + } +} diff --git a/os/axvisor/platform/riscv64-qemu-virt/src/power.rs b/os/axvisor/platform/riscv64-qemu-virt/src/power.rs new file mode 100644 index 000000000..db9e2e0c6 --- /dev/null +++ b/os/axvisor/platform/riscv64-qemu-virt/src/power.rs @@ -0,0 +1,32 @@ +use axplat::power::PowerIf; + +struct PowerImpl; + +#[impl_plat_interface] +impl PowerIf for PowerImpl { + /// Bootstraps the given CPU core with the given initial stack (in physical + /// address). + /// + /// Where `cpu_id` is the logical CPU ID (0, 1, ..., N-1, N is the number of + /// CPU cores on the platform). + #[cfg(feature = "smp")] + fn cpu_boot(cpu_id: usize, stack_top_paddr: usize) { + use axplat::mem::{va, virt_to_phys}; + if sbi_rt::probe_extension(sbi_rt::Hsm).is_unavailable() { + warn!("HSM SBI extension is not supported for current SEE."); + return; + } + let entry = virt_to_phys(va!(crate::boot::_start_secondary as *const () as usize)); + sbi_rt::hart_start(cpu_id, entry.as_usize(), stack_top_paddr); + } + + /// Shutdown the whole system. + fn system_off() -> ! { + info!("Shutting down..."); + sbi_rt::system_reset(sbi_rt::Shutdown, sbi_rt::NoReason); + warn!("It should shutdown!"); + loop { + axcpu::asm::halt(); + } + } +} diff --git a/os/axvisor/platform/riscv64-qemu-virt/src/time.rs b/os/axvisor/platform/riscv64-qemu-virt/src/time.rs new file mode 100644 index 000000000..5448f73ec --- /dev/null +++ b/os/axvisor/platform/riscv64-qemu-virt/src/time.rs @@ -0,0 +1,71 @@ +use riscv::register::time; + +use axplat::time::{NANOS_PER_SEC, TimeIf}; + +const NANOS_PER_TICK: u64 = NANOS_PER_SEC / crate::config::devices::TIMER_FREQUENCY as u64; +/// RTC wall time offset in nanoseconds at monotonic time base. +static mut RTC_EPOCHOFFSET_NANOS: u64 = 0; + +pub(super) fn init_early() { + #[cfg(feature = "rtc")] + use crate::config::{devices::RTC_PADDR, plat::PHYS_VIRT_OFFSET}; + + #[cfg(feature = "rtc")] + if RTC_PADDR != 0 { + use riscv_goldfish::Rtc; + + // Get the current time in microseconds since the epoch (1970-01-01) from the riscv RTC. + // Subtract the timer ticks to get the actual time when ArceOS was booted. + let epoch_time_nanos = + Rtc::new(RTC_PADDR + PHYS_VIRT_OFFSET).get_unix_timestamp() * 1_000_000_000; + + unsafe { + RTC_EPOCHOFFSET_NANOS = + epoch_time_nanos - TimeIfImpl::ticks_to_nanos(TimeIfImpl::current_ticks()); + } + } +} + +pub(super) fn init_percpu() { + #[cfg(feature = "irq")] + sbi_rt::set_timer(0); +} + +struct TimeIfImpl; + +#[impl_plat_interface] +impl TimeIf for TimeIfImpl { + /// Returns the current clock time in hardware ticks. + fn current_ticks() -> u64 { + time::read() as u64 + } + + /// Converts hardware ticks to nanoseconds. + fn ticks_to_nanos(ticks: u64) -> u64 { + ticks * NANOS_PER_TICK + } + + /// Converts nanoseconds to hardware ticks. + fn nanos_to_ticks(nanos: u64) -> u64 { + nanos / NANOS_PER_TICK + } + + /// Return epoch offset in nanoseconds (wall time offset to monotonic clock start). + fn epochoffset_nanos() -> u64 { + unsafe { RTC_EPOCHOFFSET_NANOS } + } + + /// Returns the IRQ number for the timer interrupt. + #[cfg(feature = "irq")] + fn irq_num() -> usize { + crate::config::devices::TIMER_IRQ + } + + /// Set a one-shot timer. + /// + /// A timer interrupt will be triggered at the specified monotonic time deadline (in nanoseconds). + #[cfg(feature = "irq")] + fn set_oneshot_timer(deadline_ns: u64) { + sbi_rt::set_timer(Self::nanos_to_ticks(deadline_ns)); + } +} diff --git a/os/axvisor/scripts/ostool/qemu-riscv64.toml b/os/axvisor/scripts/ostool/qemu-riscv64.toml new file mode 100644 index 000000000..5fcd64120 --- /dev/null +++ b/os/axvisor/scripts/ostool/qemu-riscv64.toml @@ -0,0 +1,23 @@ +args = [ + "-nographic", + "-cpu", + "rv64", + "-machine", + "virt", + "-bios", + "default", + "-smp", + "4", + "-device", + "virtio-blk-device,drive=disk0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspaceFolder}/tmp/rootfs.img", + "-append", + "root=/dev/vda rw init=/init", + "-m", + "4g", +] +fail_regex = [] +success_regex = [] +to_bin = true +uefi = false diff --git a/os/axvisor/src/hal/arch/aarch64/api.rs b/os/axvisor/src/hal/arch/aarch64/api.rs index d61652427..32ca2441d 100644 --- a/os/axvisor/src/hal/arch/aarch64/api.rs +++ b/os/axvisor/src/hal/arch/aarch64/api.rs @@ -12,17 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[axvisor_api::api_mod_impl(axvisor_api::arch)] -mod arch_api_impl { - use core::panic; +use axvisor_api::arch::ArchIf; +use std::os::arceos::modules::axhal::{self, mem::virt_to_phys}; - use axvisor_api::memory::virt_to_phys; +struct ArchImpl; - extern fn hardware_inject_virtual_interrupt(irq: axvisor_api::vmm::InterruptVector) { +#[axvisor_api::api_impl] +impl ArchIf for ArchImpl { + fn hardware_inject_virtual_interrupt(irq: axvisor_api::vmm::InterruptVector) { crate::hal::arch::inject_interrupt(irq as _); } - extern fn read_vgicd_typer() -> u32 { + fn read_vgicd_typer() -> u32 { let mut gic = rdrive::get_one::() .expect("Failed to get GIC driver") .lock() @@ -38,7 +39,7 @@ mod arch_api_impl { panic!("No GIC driver found"); } - extern fn read_vgicd_iidr() -> u32 { + fn read_vgicd_iidr() -> u32 { // use axstd::os::arceos::modules::axhal::irq::MyVgic; // MyVgic::get_gicd().lock().get_iidr() let mut gic = rdrive::get_one::() @@ -57,7 +58,7 @@ mod arch_api_impl { panic!("No GIC driver found"); } - extern fn get_host_gicd_base() -> memory_addr::PhysAddr { + fn get_host_gicd_base() -> memory_addr::PhysAddr { let mut gic = rdrive::get_one::() .expect("Failed to get GIC driver") .lock() @@ -75,7 +76,7 @@ mod arch_api_impl { panic!("No GIC driver found"); } - extern fn get_host_gicr_base() -> memory_addr::PhysAddr { + fn get_host_gicr_base() -> memory_addr::PhysAddr { let mut gic = rdrive::get_one::() .expect("Failed to get GIC driver") .lock() @@ -86,4 +87,23 @@ mod arch_api_impl { } panic!("No GICv3 driver found"); } + + fn fetch_irq() -> u64 { + /// TODO: better implementation + let mut gic = rdrive::get_one::() + .expect("Failed to get GIC driver") + .lock() + .unwrap(); + if let Some(gic) = gic.typed_mut::() { + return u32::from(gic.cpu_interface().ack()) as _; + } + if let Some(gic) = gic.typed_mut::() { + return gic.cpu_interface().ack1().to_u32() as _; + } + panic!("No GIC driver found"); + } + + fn handle_irq() { + axhal::irq::irq_handler(0); + } } diff --git a/os/axvisor/src/hal/arch/riscv64/api.rs b/os/axvisor/src/hal/arch/riscv64/api.rs new file mode 100644 index 000000000..e45c5704b --- /dev/null +++ b/os/axvisor/src/hal/arch/riscv64/api.rs @@ -0,0 +1,8 @@ +struct VmInterruptIfImpl; + +#[crate_interface::impl_interface] +impl axplat_riscv64_qemu_virt::irq::InjectIrqIf for VmInterruptIfImpl { + fn inject_virtual_interrupt(irq: usize) { + crate::hal::arch::inject_interrupt(irq); + } +} diff --git a/os/axvisor/src/hal/arch/riscv64/cache.rs b/os/axvisor/src/hal/arch/riscv64/cache.rs new file mode 100644 index 000000000..1a905a9d0 --- /dev/null +++ b/os/axvisor/src/hal/arch/riscv64/cache.rs @@ -0,0 +1,5 @@ +use memory_addr::VirtAddr; + +use crate::hal::CacheOp; + +pub fn dcache_range(_op: CacheOp, _addr: VirtAddr, _size: usize) {} diff --git a/os/axvisor/src/hal/arch/riscv64/mod.rs b/os/axvisor/src/hal/arch/riscv64/mod.rs new file mode 100644 index 000000000..2c14c0155 --- /dev/null +++ b/os/axvisor/src/hal/arch/riscv64/mod.rs @@ -0,0 +1,31 @@ +mod api; +pub mod cache; + +use crate::vmm::vm_list::get_vm_by_id; +use axaddrspace::{GuestPhysAddr, device::AccessWidth}; +use axvisor_api::vmm::current_vm_id; + +pub fn hardware_check() { + // TODO: implement hardware checks for RISC-V64 + // check page table level like aarch64 +} + +pub fn inject_interrupt(irq_id: usize) { + debug!("injecting interrupt id: {}", irq_id); + + // Get the instance of the vplic, and then inject virtual interrupt. + let vplic = get_vm_by_id(current_vm_id()) + .unwrap() + .get_devices() + .find_mmio_dev(GuestPhysAddr::from_usize(axruntime::plic_base())) + .unwrap(); + + // Calulate the pending register offset and value. + let reg_offset = riscv_vplic::PLIC_PENDING_OFFSET + (irq_id / 32) * 4; + let addr = GuestPhysAddr::from_usize(axruntime::plic_base() + reg_offset); + let width = AccessWidth::Dword; + let val: u32 = 1 << (irq_id % 32); + + // Use a trick write to set the pending bit. + let _ = vplic.handle_write(addr, width, val as _); +} diff --git a/os/axvisor/src/hal/impl_host.rs b/os/axvisor/src/hal/impl_host.rs new file mode 100644 index 000000000..831160f98 --- /dev/null +++ b/os/axvisor/src/hal/impl_host.rs @@ -0,0 +1,10 @@ +use axvisor_api::host::HostIf; + +struct HostImpl; + +#[axvisor_api::api_impl] +impl HostIf for HostImpl { + fn get_host_cpu_num() -> usize { + std::os::arceos::modules::axhal::cpu_num() + } +} diff --git a/os/axvisor/src/hal/impl_memory.rs b/os/axvisor/src/hal/impl_memory.rs new file mode 100644 index 000000000..1479255f2 --- /dev/null +++ b/os/axvisor/src/hal/impl_memory.rs @@ -0,0 +1,53 @@ +use core::{alloc::Layout, ptr::NonNull}; + +use std::os::arceos; + +use axaddrspace::{AxMmHal, HostPhysAddr, HostVirtAddr}; +use axvisor_api::memory::MemoryIf; +use memory_addr::PAGE_SIZE_4K; + +use crate::hal::AxMmHalImpl; + +struct MemoryImpl; + +#[axvisor_api::api_impl] +impl MemoryIf for MemoryImpl { + fn alloc_frame() -> Option { + ::alloc_frame() + } + + fn alloc_contiguous_frames(num_frames: usize, frame_align_pow2: usize) -> Option { + arceos::modules::axalloc::global_allocator() + .alloc( + Layout::from_size_align( + num_frames * PAGE_SIZE_4K, + PAGE_SIZE_4K << frame_align_pow2, + ) + .unwrap(), + ) + // .alloc_pages(num_frames, PAGE_SIZE_4K << frame_align_pow2) + // .map(|vaddr| ::virt_to_phys(vaddr.into())) + .map(|vaddr| HostPhysAddr::from(vaddr.as_ptr() as usize)) + .ok() + } + + fn dealloc_frame(paddr: HostPhysAddr) { + ::dealloc_frame(paddr) + } + + fn dealloc_contiguous_frames(paddr: HostPhysAddr, num_frames: usize) { + // arceos::modules::axalloc::global_allocator().dealloc_pages(paddr.as_usize(), num_frames); + arceos::modules::axalloc::global_allocator().dealloc( + unsafe { NonNull::new_unchecked(paddr.as_usize() as _) }, + Layout::from_size_align(num_frames * PAGE_SIZE_4K, PAGE_SIZE_4K).unwrap(), + ); + } + + fn phys_to_virt(paddr: HostPhysAddr) -> HostVirtAddr { + ::phys_to_virt(paddr) + } + + fn virt_to_phys(vaddr: HostVirtAddr) -> HostPhysAddr { + ::virt_to_phys(vaddr) + } +} diff --git a/os/axvisor/src/hal/impl_time.rs b/os/axvisor/src/hal/impl_time.rs new file mode 100644 index 000000000..2c2758992 --- /dev/null +++ b/os/axvisor/src/hal/impl_time.rs @@ -0,0 +1,33 @@ +use std::os::arceos::modules::axhal; + +use axvisor_api::time::{CancelToken, Nanos, Ticks, TimeIf, TimeValue}; + +use crate::vmm; + +struct TimeImpl; + +#[axvisor_api::api_impl] +impl TimeIf for TimeImpl { + fn current_ticks() -> Ticks { + axhal::time::current_ticks() + } + + fn ticks_to_nanos(ticks: Ticks) -> Nanos { + axhal::time::ticks_to_nanos(ticks) + } + + fn nanos_to_ticks(nanos: Nanos) -> Ticks { + axhal::time::nanos_to_ticks(nanos) + } + + fn register_timer( + deadline: TimeValue, + handler: alloc::boxed::Box, + ) -> CancelToken { + vmm::timer::register_timer(deadline.as_nanos() as u64, handler) + } + + fn cancel_timer(token: CancelToken) { + vmm::timer::cancel_timer(token) + } +} diff --git a/os/axvisor/src/hal/impl_vmm.rs b/os/axvisor/src/hal/impl_vmm.rs new file mode 100644 index 000000000..44717cc47 --- /dev/null +++ b/os/axvisor/src/hal/impl_vmm.rs @@ -0,0 +1,74 @@ +use std::os::arceos::modules::{axhal, axtask}; + +use axaddrspace::{HostPhysAddr, HostVirtAddr}; +use axerrno::{AxResult, ax_err_type}; +use axvisor_api::vmm::{InterruptVector, VCpuId, VCpuSet, VMId, VmmIf}; + +use crate::{task::AsVCpuTask, vmm}; + +struct VmmImpl; + +fn virt_to_phys(vaddr: HostVirtAddr) -> HostPhysAddr { + axhal::mem::virt_to_phys(vaddr) +} + +fn current_time_nanos() -> u64 { + axhal::time::monotonic_time_nanos() +} + +fn current_vm_id() -> usize { + axtask::current().as_vcpu_task().vm().id() +} + +fn current_vcpu_id() -> usize { + axtask::current().as_vcpu_task().vcpu.id() +} + +fn current_pcpu_id() -> usize { + axhal::percpu::this_cpu_id() +} + +fn vcpu_resides_on(vm_id: usize, vcpu_id: usize) -> AxResult { + vmm::with_vcpu_task(vm_id, vcpu_id, |task| task.cpu_id() as usize) + .ok_or_else(|| ax_err_type!(NotFound)) +} + +fn inject_irq_to_vcpu(vm_id: usize, vcpu_id: usize, irq: usize) -> AxResult { + vmm::with_vm_and_vcpu_on_pcpu(vm_id, vcpu_id, move |_, vcpu| { + vcpu.inject_interrupt(irq).unwrap(); + }) +} + +#[axvisor_api::api_impl] +impl VmmIf for VmmImpl { + fn current_vm_id() -> usize { + axtask::current().as_vcpu_task().vm().id() + } + + fn current_vcpu_id() -> usize { + axtask::current().as_vcpu_task().vcpu.id() + } + + fn vcpu_num(vm_id: VMId) -> Option { + vmm::with_vm(vm_id, |vm| vm.vcpu_num()) + } + + fn active_vcpus(_vm_id: VMId) -> Option { + todo!("active_vcpus") + } + + fn inject_interrupt(vm_id: VMId, vcpu_id: VCpuId, vector: InterruptVector) { + let _ = vmm::with_vm_and_vcpu_on_pcpu(vm_id, vcpu_id, move |_, vcpu| { + vcpu.inject_interrupt(vector as usize).unwrap(); + }); + } + + fn inject_interrupt_to_cpus(_vm_id: VMId, _vcpu_set: VCpuSet, _vector: InterruptVector) { + todo!("inject_interrupt_to_cpus") + } + + fn notify_vcpu_timer_expired(_vm_id: VMId, _vcpu_id: VCpuId) { + todo!("notify_vcpu_timer_expired") + // vmm::timer::notify_timer_expired(vm_id, vcpu_id); + } +} diff --git a/os/axvisor/src/hal/mod.rs b/os/axvisor/src/hal/mod.rs index afc0070b4..183c7c794 100644 --- a/os/axvisor/src/hal/mod.rs +++ b/os/axvisor/src/hal/mod.rs @@ -12,25 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::os::arceos::{ - self, - modules::{axhal::percpu::this_cpu_id, axtask}, -}; - -use axerrno::{AxResult, ax_err_type}; -use memory_addr::PAGE_SIZE_4K; -use page_table_multiarch::PagingHandler; +use std::os::arceos::{self, modules::axhal::percpu::this_cpu_id}; use arceos::modules::axhal; use axaddrspace::{AxMmHal, HostPhysAddr, HostVirtAddr}; -use axvcpu::AxVCpuHal; -use axvm::{AxVMHal, AxVMPerCpu}; +use axvm::AxVMPerCpu; +use page_table_multiarch::PagingHandler; #[cfg_attr(target_arch = "aarch64", path = "arch/aarch64/mod.rs")] #[cfg_attr(target_arch = "x86_64", path = "arch/x86_64/mod.rs")] +#[cfg_attr(target_arch = "riscv64", path = "arch/riscv64/mod.rs")] pub mod arch; -use crate::{hal::arch::hardware_check, task::AsVCpuTask, vmm}; +use crate::{hal::arch::hardware_check, vmm}; #[allow(unused)] #[repr(C)] @@ -44,58 +38,20 @@ pub enum CacheOp { CleanAndInvalidate, } -/// Implementation for `AxVMHal` trait. -pub struct AxVMHalImpl; - -impl AxVMHal for AxVMHalImpl { - type PagingHandler = axhal::paging::PagingHandlerImpl; - - fn virt_to_phys(vaddr: HostVirtAddr) -> HostPhysAddr { - axhal::mem::virt_to_phys(vaddr) - } - - fn current_time_nanos() -> u64 { - axhal::time::monotonic_time_nanos() - } - - fn current_vm_id() -> usize { - axtask::current().as_vcpu_task().vm().id() - } - - fn current_vcpu_id() -> usize { - axtask::current().as_vcpu_task().vcpu.id() - } - - fn current_pcpu_id() -> usize { - axhal::percpu::this_cpu_id() - } - - fn vcpu_resides_on(vm_id: usize, vcpu_id: usize) -> AxResult { - vmm::with_vcpu_task(vm_id, vcpu_id, |task| task.cpu_id() as usize) - .ok_or_else(|| ax_err_type!(NotFound)) - } - - fn inject_irq_to_vcpu(vm_id: usize, vcpu_id: usize, irq: usize) -> AxResult { - vmm::with_vm_and_vcpu_on_pcpu(vm_id, vcpu_id, move |_, vcpu| { - vcpu.inject_interrupt(irq).unwrap(); - }) - } -} - pub struct AxMmHalImpl; impl AxMmHal for AxMmHalImpl { fn alloc_frame() -> Option { - ::PagingHandler::alloc_frame() + ::alloc_frame() } fn dealloc_frame(paddr: HostPhysAddr) { - ::PagingHandler::dealloc_frame(paddr) + ::dealloc_frame(paddr) } #[inline] fn phys_to_virt(paddr: HostPhysAddr) -> HostVirtAddr { - ::PagingHandler::phys_to_virt(paddr) + ::phys_to_virt(paddr) } fn virt_to_phys(vaddr: axaddrspace::HostVirtAddr) -> axaddrspace::HostPhysAddr { @@ -103,18 +59,18 @@ impl AxMmHal for AxMmHalImpl { } } -pub struct AxVCpuHalImpl; +// pub struct AxVCpuHalImpl; -impl AxVCpuHal for AxVCpuHalImpl { - type MmHal = AxMmHalImpl; +// impl AxVCpuHal for AxVCpuHalImpl { +// type MmHal = AxMmHalImpl; - fn irq_hanlder() { - axhal::irq::irq_handler(0); - } -} +// fn irq_hanlder() { +// axhal::irq::irq_handler(0); +// } +// } #[percpu::def_percpu] -static mut AXVM_PER_CPU: AxVMPerCpu = AxVMPerCpu::::new_uninit(); +static mut AXVM_PER_CPU: AxVMPerCpu = AxVMPerCpu::new_uninit(); /// Init hardware virtualization support in each core. pub(crate) fn enable_virtualization() { @@ -173,119 +129,7 @@ pub(crate) fn enable_virtualization() { info!("All cores have enabled hardware virtualization support."); } -#[axvisor_api::api_mod_impl(axvisor_api::memory)] -mod memory_api_impl { - use core::{alloc::Layout, ptr::NonNull}; - - use super::*; - - extern fn alloc_frame() -> Option { - ::alloc_frame() - } - - extern fn alloc_contiguous_frames( - num_frames: usize, - frame_align_pow2: usize, - ) -> Option { - arceos::modules::axalloc::global_allocator() - .alloc( - Layout::from_size_align( - num_frames * PAGE_SIZE_4K, - PAGE_SIZE_4K << frame_align_pow2, - ) - .unwrap(), - ) - // .alloc_pages(num_frames, PAGE_SIZE_4K << frame_align_pow2) - // .map(|vaddr| ::virt_to_phys(vaddr.into())) - .map(|vaddr| HostPhysAddr::from(vaddr.as_ptr() as usize)) - .ok() - } - - extern fn dealloc_frame(paddr: HostPhysAddr) { - ::dealloc_frame(paddr) - } - - extern fn dealloc_contiguous_frames(paddr: HostPhysAddr, num_frames: usize) { - // arceos::modules::axalloc::global_allocator().dealloc_pages(paddr.as_usize(), num_frames); - arceos::modules::axalloc::global_allocator().dealloc( - unsafe { NonNull::new_unchecked(paddr.as_usize() as _) }, - Layout::from_size_align(num_frames * PAGE_SIZE_4K, PAGE_SIZE_4K).unwrap(), - ); - } - - extern fn phys_to_virt(paddr: HostPhysAddr) -> HostVirtAddr { - ::phys_to_virt(paddr) - } - - extern fn virt_to_phys(vaddr: HostVirtAddr) -> HostPhysAddr { - ::virt_to_phys(vaddr) - } -} - -#[axvisor_api::api_mod_impl(axvisor_api::time)] -mod time_api_impl { - use super::*; - use axvisor_api::time::{CancelToken, Nanos, Ticks, TimeValue}; - - extern fn current_ticks() -> Ticks { - axhal::time::current_ticks() - } - - extern fn ticks_to_nanos(ticks: Ticks) -> Nanos { - axhal::time::ticks_to_nanos(ticks) - } - - extern fn nanos_to_ticks(nanos: Nanos) -> Ticks { - axhal::time::nanos_to_ticks(nanos) - } - - extern fn register_timer( - deadline: TimeValue, - handler: alloc::boxed::Box, - ) -> CancelToken { - vmm::timer::register_timer(deadline.as_nanos() as u64, handler) - } - - extern fn cancel_timer(token: CancelToken) { - vmm::timer::cancel_timer(token) - } -} - -#[axvisor_api::api_mod_impl(axvisor_api::vmm)] -mod vmm_api_impl { - use super::*; - use axvisor_api::vmm::{InterruptVector, VCpuId, VMId}; - - extern fn current_vm_id() -> usize { - ::current_vm_id() - } - - extern fn current_vcpu_id() -> usize { - ::current_vcpu_id() - } - - extern fn vcpu_num(vm_id: VMId) -> Option { - vmm::with_vm(vm_id, |vm| vm.vcpu_num()) - } - - extern fn active_vcpus(_vm_id: VMId) -> Option { - todo!("active_vcpus") - } - - extern fn inject_interrupt(vm_id: VMId, vcpu_id: VCpuId, vector: InterruptVector) { - ::inject_irq_to_vcpu(vm_id, vcpu_id, vector as usize).unwrap(); - } - - extern fn notify_vcpu_timer_expired(_vm_id: VMId, _vcpu_id: VCpuId) { - todo!("notify_vcpu_timer_expired") - // vmm::timer::notify_timer_expired(vm_id, vcpu_id); - } -} - -#[axvisor_api::api_mod_impl(axvisor_api::host)] -mod host_api_impl { - extern fn get_host_cpu_num() -> usize { - // std::os::arceos::modules::axconfig::plat::CPU_NUM - std::os::arceos::modules::axhal::cpu_num() - } -} +mod impl_host; +mod impl_memory; +mod impl_time; +mod impl_vmm; diff --git a/os/axvisor/src/vmm/images/mod.rs b/os/axvisor/src/vmm/images/mod.rs index 1ae83994e..82610474a 100644 --- a/os/axvisor/src/vmm/images/mod.rs +++ b/os/axvisor/src/vmm/images/mod.rs @@ -125,7 +125,13 @@ impl ImageLoader { self.vm.clone(), ); } else { - info!("dtb_load_gpa not provided"); + if let Some(buffer) = vm_imags.dtb { + #[cfg(target_arch = "riscv64")] + load_vm_image_from_memory(buffer, self.dtb_load_gpa.unwrap(), self.vm.clone()) + .expect("Failed to load DTB images"); + } else { + info!("dtb_load_gpa not provided"); + } } // Load BIOS image diff --git a/os/axvisor/src/vmm/mod.rs b/os/axvisor/src/vmm/mod.rs index cbbfb8d41..e9e64737e 100644 --- a/os/axvisor/src/vmm/mod.rs +++ b/os/axvisor/src/vmm/mod.rs @@ -32,18 +32,15 @@ use std::os::arceos::{ use axerrno::{AxResult, ax_err_type}; -use crate::{ - hal::{AxVCpuHalImpl, AxVMHalImpl}, - task::AsVCpuTask, -}; +use crate::task::AsVCpuTask; pub use timer::init_percpu as init_timer_percpu; /// The instantiated VM type. -pub type VM = axvm::AxVM; +pub type VM = axvm::AxVM; /// The instantiated VM ref type (by `Arc`). -pub type VMRef = axvm::AxVMRef; +pub type VMRef = axvm::AxVMRef; /// The instantiated VCpu ref type (by `Arc`). -pub type VCpuRef = axvm::AxVCpuRef; +pub type VCpuRef = axvm::AxVCpuRef; static VMM: AxWaitQueueHandle = AxWaitQueueHandle::new(); diff --git a/os/axvisor/src/vmm/vcpus.rs b/os/axvisor/src/vmm/vcpus.rs index b80bcc798..5a26355d6 100644 --- a/os/axvisor/src/vmm/vcpus.rs +++ b/os/axvisor/src/vmm/vcpus.rs @@ -323,16 +323,17 @@ fn vcpu_on(vm: VMRef, vcpu_id: usize, entry_point: GuestPhysAddr, arg: usize) { vcpu.set_entry(entry_point) .expect("vcpu_on: set_entry failed"); + #[cfg(not(target_arch = "riscv64"))] vcpu.set_gpr(0, arg); #[cfg(target_arch = "riscv64")] { - debug!( + info!( "vcpu_on: vcpu[{}] entry={:x} opaque={:x}", vcpu_id, entry_point, arg ); - vcpu.set_gpr(0, vcpu_id); - vcpu.set_gpr(1, arg); + vcpu.set_gpr(riscv_vcpu::GprIndex::A0 as usize, vcpu_id); + vcpu.set_gpr(riscv_vcpu::GprIndex::A1 as usize, arg); } let vcpu_task = alloc_vcpu_task(&vm, vcpu); @@ -521,7 +522,10 @@ fn vcpu_run() { }); vcpu_on(vm.clone(), target_vcpu_id, entry_point, arg as _); + #[cfg(not(target_arch = "riscv64"))] vcpu.set_gpr(0, 0); + #[cfg(target_arch = "riscv64")] + vcpu.set_gpr(riscv_vcpu::GprIndex::A0 as usize, 0); } AxVCpuExitReason::SystemDown => { warn!("VM[{vm_id}] run VCpu[{vcpu_id}] SystemDown"); diff --git a/os/axvisor/xtask/src/cargo.rs b/os/axvisor/xtask/src/cargo.rs index 432f8837d..55f8322a6 100644 --- a/os/axvisor/xtask/src/cargo.rs +++ b/os/axvisor/xtask/src/cargo.rs @@ -25,6 +25,8 @@ impl Context { Arch::Aarch64 } else if build_config.target.contains("x86_64") { Arch::X86_64 + } else if build_config.target.contains("riscv64") { + Arch::Riscv64 } else { return Err(anyhow::anyhow!( "Unsupported target architecture: {}", @@ -77,4 +79,5 @@ impl Context { enum Arch { Aarch64, X86_64, + Riscv64, } diff --git a/os/axvisor/xtask/src/image.rs b/os/axvisor/xtask/src/image.rs index 3c1b3e16d..6b83511a9 100644 --- a/os/axvisor/xtask/src/image.rs +++ b/os/axvisor/xtask/src/image.rs @@ -64,7 +64,7 @@ pub struct ImageArgs { pub command: ImageCommands, } -#[derive(Parser, Default)] +#[derive(Parser)] pub struct ImageConfigOverrides { /// The path to the local storage of images. Override the config file. #[arg(short('S'), long, global = true)] diff --git a/os/axvisor/xtask/src/tbuld.rs b/os/axvisor/xtask/src/tbuld.rs index ef6d26033..addc62e87 100644 --- a/os/axvisor/xtask/src/tbuld.rs +++ b/os/axvisor/xtask/src/tbuld.rs @@ -101,14 +101,6 @@ impl Context { "-Z", "build-std-features=compiler-builtins-mem", ); - ensure_cargo_arg_pair( - &mut cargo.args, - "--config", - &format!( - "target.{}.rustflags=[\"-Clink-arg=-Taxplat.x\"]", - cargo.target - ), - ); } cargo.args.extend(config.cargo_args); diff --git a/platform/x86-qemu-q35/src/apic.rs b/platform/x86-qemu-q35/src/apic.rs index ace4681f5..0bb543e4e 100644 --- a/platform/x86-qemu-q35/src/apic.rs +++ b/platform/x86-qemu-q35/src/apic.rs @@ -19,8 +19,10 @@ use core::mem::MaybeUninit; use axplat::mem::{PhysAddr, pa, phys_to_virt}; use kspin::SpinNoIrq; use lazyinit::LazyInit; -use x2apic::ioapic::IoApic; -use x2apic::lapic::{LocalApic, LocalApicBuilder, xapic_base}; +use x2apic::{ + ioapic::IoApic, + lapic::{LocalApic, LocalApicBuilder, xapic_base}, +}; use x86_64::instructions::port::Port; use self::vectors::*; diff --git a/platform/x86-qemu-q35/src/boot.rs b/platform/x86-qemu-q35/src/boot.rs index 9dca312fd..e2bb392b2 100644 --- a/platform/x86-qemu-q35/src/boot.rs +++ b/platform/x86-qemu-q35/src/boot.rs @@ -16,8 +16,10 @@ use core::arch::global_asm; -use x86_64::registers::control::{Cr0Flags, Cr4Flags}; -use x86_64::registers::model_specific::EferFlags; +use x86_64::registers::{ + control::{Cr0Flags, Cr4Flags}, + model_specific::EferFlags, +}; use crate::config::plat::{BOOT_STACK_SIZE, PHYS_VIRT_OFFSET}; diff --git a/platform/x86-qemu-q35/src/mp.rs b/platform/x86-qemu-q35/src/mp.rs index 4198f03dc..3777a84cf 100644 --- a/platform/x86-qemu-q35/src/mp.rs +++ b/platform/x86-qemu-q35/src/mp.rs @@ -14,8 +14,10 @@ //! Multi-processor booting. -use axplat::mem::{PAGE_SIZE_4K, PhysAddr, pa}; -use axplat::time::{Duration, busy_wait}; +use axplat::{ + mem::{PAGE_SIZE_4K, PhysAddr, pa}, + time::{Duration, busy_wait}, +}; const START_PAGE_IDX: u8 = 6; const START_PAGE_PADDR: PhysAddr = pa!(START_PAGE_IDX as usize * PAGE_SIZE_4K); diff --git a/platform/x86-qemu-q35/src/time.rs b/platform/x86-qemu-q35/src/time.rs index 8ac8ffeba..1450e6fac 100644 --- a/platform/x86-qemu-q35/src/time.rs +++ b/platform/x86-qemu-q35/src/time.rs @@ -17,10 +17,9 @@ //! Currently, the TSC is used as the clock source. use axplat::time::TimeIf; -use raw_cpuid::CpuId; - #[cfg(feature = "irq")] use int_ratio::Ratio; +use raw_cpuid::CpuId; #[cfg(feature = "irq")] const LAPIC_TICKS_PER_SEC: u64 = 1_000_000_000; // TODO: need to calibrate diff --git a/scripts/repo/repos.csv b/scripts/repo/repos.csv index a93f32de9..ea2d802c6 100644 --- a/scripts/repo/repos.csv +++ b/scripts/repo/repos.csv @@ -4,21 +4,22 @@ https://github.com/arceos-hypervisor/axvisor,,os/axvisor,OS,axvisor - ArceOS Hyp https://github.com/Starry-OS/StarryOS,,os/StarryOS,OS,StarryOS - 教学操作系统 https://github.com/arceos-hypervisor/aarch64_sysreg,,components/aarch64_sysreg,Hypervisor, https://github.com/arceos-hypervisor/axaddrspace,,components/axaddrspace,Hypervisor, -https://github.com/arceos-hypervisor/axdevice,v0.2.1,components/axdevice,Hypervisor, -https://github.com/arceos-hypervisor/axdevice_base,v0.2.1,components/axdevice_base,Hypervisor, +https://github.com/arceos-hypervisor/axdevice,,components/axdevice,Hypervisor, +https://github.com/arceos-hypervisor/axdevice_base,,components/axdevice_base,Hypervisor, https://github.com/arceos-hypervisor/axhvc,,components/axhvc,Hypervisor, https://github.com/arceos-hypervisor/axklib,,components/axklib,Hypervisor, -https://github.com/arceos-hypervisor/axvcpu,v0.2.2,components/axvcpu,Hypervisor, -https://github.com/arceos-hypervisor/axvisor_api,v0.1.0,components/axvisor_api,Hypervisor, -https://github.com/arceos-hypervisor/axvm,v0.2.3,components/axvm,Hypervisor, +https://github.com/arceos-hypervisor/axvcpu,,components/axvcpu,Hypervisor, +https://github.com/arceos-hypervisor/axvisor_api,,components/axvisor_api,Hypervisor, +https://github.com/arceos-hypervisor/axvm,,components/axvm,Hypervisor, https://github.com/arceos-hypervisor/axvmconfig,,components/axvmconfig,Hypervisor, https://github.com/arceos-hypervisor/range-alloc,,components/range-alloc-arceos,Hypervisor, -https://github.com/arceos-hypervisor/x86_vcpu,v0.2.2,components/x86_vcpu,Hypervisor, -https://github.com/arceos-hypervisor/arm_vcpu,v0.2.2,components/arm_vcpu,Hypervisor, -https://github.com/arceos-hypervisor/arm_vgic,v0.2.1,components/arm_vgic,Hypervisor, -https://github.com/arceos-hypervisor/riscv_vcpu,v0.2.2,components/riscv_vcpu,Hypervisor, +https://github.com/arceos-hypervisor/x86_vcpu,,components/x86_vcpu,Hypervisor, +https://github.com/arceos-hypervisor/x86_vlapic.git,,components/x86_vlapic,Hypervisor, +https://github.com/arceos-hypervisor/arm_vcpu,,components/arm_vcpu,Hypervisor, +https://github.com/arceos-hypervisor/arm_vgic,,components/arm_vgic,Hypervisor, +https://github.com/arceos-hypervisor/riscv_vcpu,,components/riscv_vcpu,Hypervisor, https://github.com/arceos-hypervisor/riscv-h,,components/riscv-h,Hypervisor, -https://github.com/arceos-hypervisor/riscv_vplic,v0.2.1,components/riscv_vplic,Hypervisor, +https://github.com/arceos-hypervisor/riscv_vplic,,components/riscv_vplic,Hypervisor, https://github.com/arceos-org/allocator,,components/axallocator,ArceOS, https://github.com/arceos-org/axconfig-gen,,components/axconfig-gen,ArceOS, https://github.com/arceos-org/axcpu,dev,components/axcpu,ArceOS, @@ -55,3 +56,4 @@ https://github.com/Starry-OS/starry-signal,,components/starry-signal,Starry, https://github.com/Starry-OS/starry-vm,,components/starry-vm,Starry, https://github.com/Starry-OS/smoltcp,,components/starry-smoltcp,Starry, https://github.com/arceos-org/axfs_crates,main,components/axfs_crates,ArceOS, +https://github.com/DeathWish5/fxmac_rs,,components/fxmac_rs,ArceOS,