From f99823f847c3bad8b774e1a56aeceffe12d60d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 28 Nov 2025 13:46:19 +0800 Subject: [PATCH 01/19] feat: add submodules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chore: update submodule commits for various modules feat: add axplat-aarch64-dyn submodule feat(axalloc): introduce a global memory allocator with buddy system - Added `axalloc` crate with a global allocator implementation. - Implemented `GlobalAllocator` using a buddy system allocator. - Provided functions for memory initialization, allocation, and deallocation. - Added `GlobalPage` struct for managing contiguous memory pages. feat(axvm-types): define address types and device access structures - Introduced `GuestVirtAddr` and `GuestPhysAddr` types for guest memory addressing. - Created traits for device address handling and defined `DeviceAddr` and `DeviceAddrRange`. - Implemented `Port` and `SysRegAddr` types for I/O operations and system registers. fix(vmm): update VM reference handling - Changed `push_vm` function to accept `axvm::Vm` directly and return an `Arc`. - Updated global VM list management to use `Arc` for thread-safe reference counting. chore: update submodules and dependencies - Updated submodules for `arm_vcpu`, `axvm`, and `axvmconfig`. - Added `memory_addr` dependency to `axruntime` and `axvm-types`. - Updated `Cargo.toml` files to reflect new dependencies and features. chore: update axplat-aarch64-dyn submodule to latest commit chore: update axplat-aarch64-dyn submodule to latest commit fix: update memory mapping type in build_vmconfig function chore: update dependencies to version hv-0.4.3 and sync submodule axvm chore: update dependencies and refactor task management in the kernel chore: update fdt-parser submodule to latest commit chore: update fdt-edit and fdt-raw packages to latest versions and adjust dependencies chore: update fdt-edit package to version 0.1.3 and adjust checksum chore: update Cargo.lock and axplat-aarch64-dyn submodule to latest commits Add initial documentation and diagram for refactoring process - Created a new markdown file `refactor.md` to outline the refactoring strategy. - Added a binary image `refactor.png` to visually represent the refactoring process. Add refactor documentation for AxVisor architecture evolution - Introduced a comprehensive analysis report detailing the transition from axvisor-dev3 to axvsior-dev2. - Documented key architectural changes, including modularization, multi-architecture support, and improved dependency management. - Highlighted the benefits of the refactor, such as enhanced maintainability, stability, and development efficiency. - Provided a detailed comparison of the old and new architecture, including directory structure and module responsibilities. - Included design principles, performance optimizations, and future development recommendations. Refactor AxVisor architecture: Transition from monolithic to modular design - Removed detailed execution summary and key improvements from refactor.md to streamline documentation. - Deleted outdated diagram (refactor.png) to eliminate redundancy. - Updated submodule reference for axvm to the latest commit for improved consistency. Refactor code structure for improved readability and maintainability 更新 Cargo.lock 和 Cargo.toml,移除 fdt-parser 版本限制并添加 ranges-ext 依赖 更新 fdt-edit 依赖版本至 0.1.4,移除 ept-level-4 特性,并更新 axvm 子模块 更新 axalloc 依赖版本至 0.2.0,修复依赖冲突并移除过时的 zerocopy 依赖 更新 GitHub Actions 工作流,升级 checkout 动作至 v6 并启用子模块递归更新;更新 axvm 子模块至最新提交 update ci 删除不再使用的 NimbOS 和 QEMU 设置工作流;移除多个配置文件中的 ept-level-4 特性 refactor: Enhance AxVm design and state management - Introduced a detailed design for AxVm, illustrating its internal structure and lifecycle management through state machines. - Defined the overall architecture, including the relationship between AxVisor and Vm instances. - Elaborated on the state management within VmData, detailing the three core states: InitData, VmRunData, and StoppedData. - Clarified the VCPU and threading model, emphasizing resource migration and lifecycle safety through Arc/Weak references. refactor: 清理未使用的导入和模块,优化代码结构 更新 fdt-edit 包至版本 0.1.5,移除 build_vmconfig 函数中的冗余设备配置,更新 axvm 子模块至最新提交 更新 axvm 子模块至最新提交 更新 axvm 子模块至最新提交 更新 linux-aarch64-qemu-smp1.toml 配置,移除冗余物理 CPU ID 和内核相关注释,更新 axvm 子模块至最新提交 更新 axvm 和 arm_vcpu 子模块至最新提交 更新 axvm 子模块至最新提交 更新 axvm 和 arm_vcpu 子模块至最新提交 更新 axvm 子模块至最新提交 更新 axvm 和 arm_vcpu 子模块至最新提交 更新 axvm 和 arm_vcpu 子模块至最新提交 更新 axvm 子模块至最新提交 更新 arceos 和 linux 虚拟机配置文件,注释掉物理 CPU ID 和内核相关参数 在虚拟机配置中添加内核中断模式设置为“passthrough” 调整 arceos 虚拟机配置,移动中断模式设置至设备规格部分,并更新内核路径和内存区域配置 更新 uboot.toml 成功正则表达式,添加 "Hello, world!";更新 arm_vcpu 和 axvm-types 子模块至最新提交 更新 arceos 和 linux 虚拟机配置,注释掉物理 CPU ID 和内核相关参数 更新 axvm 子模块至最新提交 更新 axvm 子模块至最新提交;修复内存映射类型为保留类型的配置;更新 axconfig 和 axruntime 的版本号至 0.1.0 更新依赖项,添加 axplat-x86-qemu-q35 和 x86_vcpu;修复多处理器启动页设置中的指针计算 更新 arm_vcpu 子模块至最新提交 更新 arm_vcpu 和 axvm 子模块至最新提交 更新依赖项,添加 raw-cpuid 和 axaddrspace;公开 load_images_fs 函数;更新子模块至最新提交 doc: update uml 更新版本号至 0.3.0,优化 load_images_fs 函数,简化内存映像加载逻辑 fmt chore: clean up Cargo.lock by removing unused packages and updating dependencies --- .cargo/config.toml | 25 + .devspace/state.json | 86 ++ .../setup-nimbos-guest-image/action.yml | 72 -- .../workflows/actions/setup-qemu/action.yml | 47 - .github/workflows/test-board.yml | 5 +- .github/workflows/test-qemu.yml | 5 +- .github/workflows/uboot.toml | 1 + .gitignore | 1 - .gitmodules | 36 + .rustfmt.toml | 2 +- Cargo.lock | 981 ++++-------------- Cargo.toml | 38 +- configs/board/orangepi-5-plus.toml | 1 - configs/board/phytiumpi.toml | 1 - configs/board/qemu-aarch64.toml | 1 - configs/board/qemu-x86_64.toml | 1 - configs/board/roc-rk3568-pc.toml | 1 - configs/vms/arceos-aarch64-e2000-smp1.toml | 12 +- configs/vms/arceos-aarch64-rk3568-smp1.toml | 8 +- configs/vms/linux-aarch64-e2000-smp1.toml | 8 +- configs/vms/linux-aarch64-qemu-smp1.toml | 11 +- configs/vms/linux-aarch64-rk3568-smp1.toml | 8 +- configs/vms/nimbos-x86_64-qemu-smp1.toml | 2 +- doc/old.drawio | 121 +++ doc/old.svg | 1 + doc/refactor-vm.dio | 175 ++++ doc/refactor-vm.svg | 1 + doc/refactor.dio | 165 +++ doc/refactor.md | 49 + doc/refactor.svg | 1 + kernel/Cargo.toml | 40 +- kernel/src/hal/arch/aarch64/api.rs | 75 -- kernel/src/hal/arch/aarch64/cache.rs | 17 - kernel/src/hal/arch/aarch64/mod.rs | 145 --- kernel/src/hal/arch/x86_64/cache.rs | 5 - kernel/src/hal/arch/x86_64/mod.rs | 4 - kernel/src/hal/mod.rs | 275 ----- kernel/src/main.rs | 15 +- kernel/src/task.rs | 42 +- kernel/src/vmm/config.rs | 238 ++--- kernel/src/vmm/images/mod.rs | 345 +----- kernel/src/vmm/mod.rs | 142 +-- kernel/src/vmm/timer.rs | 113 -- kernel/src/vmm/vm_list.rs | 11 +- modules/arm_vcpu | 1 + modules/arm_vgic | 1 + modules/axaddrspace | 1 + modules/axalloc/Cargo.toml | 22 + modules/axalloc/src/lib.rs | 227 ++++ modules/axalloc/src/page.rs | 107 ++ modules/axconfig/Cargo.toml | 2 +- modules/axdevice | 1 + modules/axdevice_base | 1 + modules/axruntime/Cargo.toml | 8 +- modules/axruntime/src/lib.rs | 10 +- modules/axvcpu | 1 + modules/axvisor_api | 1 + modules/axvm | 1 + modules/axvm-types/Cargo.toml | 10 + modules/axvm-types/src/addr.rs | 23 + modules/axvm-types/src/device/device_addr.rs | 97 ++ modules/axvm-types/src/device/mod.rs | 132 +++ modules/axvm-types/src/lib.rs | 5 + modules/axvm-types/src/mem.rs | 25 + modules/axvmconfig | 1 + modules/x86_vcpu | 1 + modules/x86_vlapic | 1 + platform/axplat-aarch64-dyn | 1 + platform/x86-qemu-q35/src/lib.rs | 6 +- platform/x86-qemu-q35/src/mp.rs | 4 +- xtask/Cargo.toml | 1 - xtask/src/cargo.rs | 2 +- xtask/src/devspace.rs | 5 + xtask/src/image.rs | 18 +- xtask/src/main.rs | 18 +- xtask/src/menuconfig.rs | 5 +- 76 files changed, 1830 insertions(+), 2242 deletions(-) create mode 100644 .devspace/state.json delete mode 100644 .github/workflows/actions/setup-nimbos-guest-image/action.yml delete mode 100644 .github/workflows/actions/setup-qemu/action.yml create mode 100644 doc/old.drawio create mode 100644 doc/old.svg create mode 100644 doc/refactor-vm.dio create mode 100644 doc/refactor-vm.svg create mode 100644 doc/refactor.dio create mode 100644 doc/refactor.md create mode 100644 doc/refactor.svg delete mode 100644 kernel/src/hal/arch/aarch64/api.rs delete mode 100644 kernel/src/hal/arch/aarch64/cache.rs delete mode 100644 kernel/src/hal/arch/aarch64/mod.rs delete mode 100644 kernel/src/hal/arch/x86_64/cache.rs delete mode 100644 kernel/src/hal/arch/x86_64/mod.rs delete mode 100644 kernel/src/hal/mod.rs delete mode 100644 kernel/src/vmm/timer.rs create mode 160000 modules/arm_vcpu create mode 160000 modules/arm_vgic create mode 160000 modules/axaddrspace create mode 100644 modules/axalloc/Cargo.toml create mode 100644 modules/axalloc/src/lib.rs create mode 100644 modules/axalloc/src/page.rs create mode 160000 modules/axdevice create mode 160000 modules/axdevice_base create mode 160000 modules/axvcpu create mode 160000 modules/axvisor_api create mode 160000 modules/axvm create mode 100644 modules/axvm-types/Cargo.toml create mode 100644 modules/axvm-types/src/addr.rs create mode 100644 modules/axvm-types/src/device/device_addr.rs create mode 100644 modules/axvm-types/src/device/mod.rs create mode 100644 modules/axvm-types/src/lib.rs create mode 100644 modules/axvm-types/src/mem.rs create mode 160000 modules/axvmconfig create mode 160000 modules/x86_vcpu create mode 160000 modules/x86_vlapic create mode 160000 platform/axplat-aarch64-dyn diff --git a/.cargo/config.toml b/.cargo/config.toml index 676b0e8a..851143c3 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -18,3 +18,28 @@ rustflags = [ [alias] xtask = "run --package xtask --" +# >>> devspace patches >>> +# Managed by `cargo xtask devspace` +[patch."crates-io"] +axaddrspace = { path = "modules/axaddrspace" } +axdevice_base = { path = "modules/axdevice_base" } +axvisor_api = { path = "modules/axvisor_api" } +x86_vcpu = { path = "modules/x86_vcpu" } +x86_vlapic = { path = "modules/x86_vlapic" } + +[patch."https://github.com/arceos-hypervisor/arm_vcpu"] +arm_vcpu = { path = "modules/arm_vcpu/" } + +[patch."https://github.com/arceos-hypervisor/arm_vgic"] +arm_vgic = { path = "modules/arm_vgic/" } + +[patch."https://github.com/arceos-hypervisor/axdevice"] +axdevice = { path = "modules/axdevice/" } + +[patch."https://github.com/arceos-hypervisor/axvcpu"] +axvcpu = { path = "modules/axvcpu/" } + +[patch."https://github.com/arceos-hypervisor/axvmconfig"] +axvmconfig = { path = "modules/axvmconfig/" } + +# <<< devspace patches <<< diff --git a/.devspace/state.json b/.devspace/state.json new file mode 100644 index 00000000..e861f5be --- /dev/null +++ b/.devspace/state.json @@ -0,0 +1,86 @@ +{ + "modules": { + "axvisor_api": { + "name": "axvisor_api", + "path": "modules/axvisor_api" + }, + "x86_vlapic": { + "name": "x86_vlapic", + "path": "modules/x86_vlapic" + }, + "axvmconfig": { + "name": "axvmconfig", + "path": "modules/axvmconfig" + }, + "arm_vcpu": { + "name": "arm_vcpu", + "path": "modules/arm_vcpu" + }, + "arm_vgic": { + "name": "arm_vgic", + "path": "modules/arm_vgic" + }, + "x86_vcpu": { + "name": "x86_vcpu", + "path": "modules/x86_vcpu" + }, + "axdevice_base": { + "name": "axdevice_base", + "path": "modules/axdevice_base" + }, + "axaddrspace": { + "name": "axaddrspace", + "path": "modules/axaddrspace" + }, + "axvcpu": { + "name": "axvcpu", + "path": "modules/axvcpu" + }, + "axdevice": { + "name": "axdevice", + "path": "modules/axdevice" + } + }, + "patches": [ + { + "source": "crates-io", + "crate_name": "axaddrspace" + }, + { + "source": "crates-io", + "crate_name": "axdevice_base" + }, + { + "source": "crates-io", + "crate_name": "axvisor_api" + }, + { + "source": "crates-io", + "crate_name": "x86_vcpu" + }, + { + "source": "crates-io", + "crate_name": "x86_vlapic" + }, + { + "source": "https://github.com/arceos-hypervisor/arm_vcpu", + "crate_name": "arm_vcpu" + }, + { + "source": "https://github.com/arceos-hypervisor/arm_vgic", + "crate_name": "arm_vgic" + }, + { + "source": "https://github.com/arceos-hypervisor/axdevice", + "crate_name": "axdevice" + }, + { + "source": "https://github.com/arceos-hypervisor/axvcpu", + "crate_name": "axvcpu" + }, + { + "source": "https://github.com/arceos-hypervisor/axvmconfig", + "crate_name": "axvmconfig" + } + ] +} \ No newline at end of file diff --git a/.github/workflows/actions/setup-nimbos-guest-image/action.yml b/.github/workflows/actions/setup-nimbos-guest-image/action.yml deleted file mode 100644 index dc27a868..00000000 --- a/.github/workflows/actions/setup-nimbos-guest-image/action.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Setup NimbOS Guest Image - -inputs: - nimbos-version: - description: 'NimbOS version to use' - required: true - type: string - nimbos-repo: - description: 'NimbOS repository to use' - required: false - type: string - default: 'arceos-hypervisor/nimbos' - arch: - description: 'Architecture to build for' - required: true - type: string - bios-version: - description: 'BIOS version to use (only for x86_64)' - required: false - type: string - default: 'latest' - bios-repo: - description: 'BIOS repository to use (only for x86_64)' - required: false - type: string - default: 'arceos-hypervisor/axvm-bios-x86' - disk-path: - description: 'Absolute path to the disk image (relative paths will NOT work)' - required: true - type: string - -runs: - using: "composite" - steps: - - name: Make temporary directory - shell: bash - run: | - sudo rm -rf temp - mkdir -p temp - - name: Download NimbOS - uses: dsaltares/fetch-gh-release-asset@1.1.2 - with: - file: ${{ inputs.arch }}_usertests.zip - repo: ${{ inputs.nimbos-repo }} - version: ${{ inputs.nimbos-version }} - target: temp/${{ inputs.arch }}.zip - - name: Unzip NimbOS - shell: bash - run: | - unzip temp/${{ inputs.arch }}.zip -d temp - rm temp/${{ inputs.arch }}.zip - mv temp/nimbos.bin temp/nimbos-${{ inputs.arch }}.bin - - name: Download BIOS - if: inputs.arch == 'x86_64' - uses: dsaltares/fetch-gh-release-asset@1.1.2 - with: - file: axvm-bios.bin - repo: ${{ inputs.bios-repo }} - version: ${{ inputs.bios-version }} - target: temp/axvm-bios.bin - - name: Create Image - shell: bash - run: | - ./axvisor.sh disk_img --image ${{ inputs.disk-path }} - sudo mkdir -p img - sudo chown root:root temp/* - sudo mount ${{ inputs.disk-path }} img - sudo mv temp/* img - sudo umount img - - name: Cleanup - shell: bash - run: rm -rf temp img diff --git a/.github/workflows/actions/setup-qemu/action.yml b/.github/workflows/actions/setup-qemu/action.yml deleted file mode 100644 index f1b287bc..00000000 --- a/.github/workflows/actions/setup-qemu/action.yml +++ /dev/null @@ -1,47 +0,0 @@ -# copied from arceos-org/arceos -name: Download and build QEMU - -inputs: - qemu-version: - description: 'QEMU version' - required: true - type: string - -runs: - using: "composite" - steps: - - name: Cache QEMU - id: cache-qemu - uses: actions/cache/restore@v3 - with: - path: qemu_build - key: qemu-${{ inputs.qemu-version }}-slirp-1 - - name: Download and build QEMU - if: steps.cache-qemu.outputs.cache-hit != 'true' - env: - QEMU_PATH: qemu-${{ inputs.qemu-version }} - PREFIX: ${{ github.workspace }}/qemu_build - shell: bash - run: | - sudo apt-get update && sudo apt-get install -y ninja-build libslirp-dev glib-2.0 - wget https://download.qemu.org/$QEMU_PATH.tar.xz && tar -xJf $QEMU_PATH.tar.xz - cd $QEMU_PATH \ - && ./configure --prefix=$PREFIX --target-list=x86_64-softmmu,riscv64-softmmu,aarch64-softmmu --enable-slirp \ - && make -j > /dev/null 2>&1 \ - && make install - - uses: actions/cache/save@v3 - if: steps.cache-qemu.outputs.cache-hit != 'true' - with: - path: qemu_build - key: qemu-${{ inputs.qemu-version }}-slirp-1 - - - name: Install QEMU - shell: bash - run: | - echo "$PWD/qemu_build/bin" >> $GITHUB_PATH - - name: Verify installation - shell: bash - run: | - qemu-system-x86_64 --version - qemu-system-aarch64 --version - qemu-system-riscv64 --version diff --git a/.github/workflows/test-board.yml b/.github/workflows/test-board.yml index f687650d..52e7d515 100644 --- a/.github/workflows/test-board.yml +++ b/.github/workflows/test-board.yml @@ -27,8 +27,9 @@ jobs: - ${{ matrix.board }} steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v6 + with: + submodules: "recursive" - name: Install dependencies run: cargo +stable install ostool --version ^0.8 diff --git a/.github/workflows/test-qemu.yml b/.github/workflows/test-qemu.yml index f4e510b3..136e43b9 100644 --- a/.github/workflows/test-qemu.yml +++ b/.github/workflows/test-qemu.yml @@ -31,8 +31,9 @@ jobs: - intel steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v6 + with: + submodules: "recursive" - name: Install dependencies run: cargo +stable install ostool --version ^0.8 diff --git a/.github/workflows/uboot.toml b/.github/workflows/uboot.toml index e82eacaf..29fbd54d 100644 --- a/.github/workflows/uboot.toml +++ b/.github/workflows/uboot.toml @@ -10,6 +10,7 @@ success_regex = [ "Welcome to AxVisor Shell!", "All tests passed!", "Hello World!", + "Hello, world!", "root@firefly:~#", "root@phytium-Ubuntu:~#", "Welcome to Phytium Buildroot", diff --git a/.gitignore b/.gitignore index 0dc4eff8..772fd371 100644 --- a/.gitignore +++ b/.gitignore @@ -30,7 +30,6 @@ __pycache__/ /crates/* !/crates/nop/ !/crates/nop/** -.devspace/ /Cargo.toml.bk diff --git a/.gitmodules b/.gitmodules index e69de29b..640aa4b1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,36 @@ +[submodule "modules/axvm"] + path = modules/axvm + url = https://github.com/arceos-hypervisor/axvm.git +[submodule "modules/axvcpu"] + path = modules/axvcpu + url = https://github.com/arceos-hypervisor/axvcpu.git +[submodule "modules/axdevice"] + path = modules/axdevice + url = https://github.com/arceos-hypervisor/axdevice.git +[submodule "modules/arm_vcpu"] + path = modules/arm_vcpu + url = https://github.com/arceos-hypervisor/arm_vcpu +[submodule "modules/arm_vgic"] + path = modules/arm_vgic + url = https://github.com/arceos-hypervisor/arm_vgic.git +[submodule "modules/axaddrspace"] + path = modules/axaddrspace + url = https://github.com/arceos-hypervisor/axaddrspace +[submodule "modules/axdevice_base"] + path = modules/axdevice_base + url = https://github.com/arceos-hypervisor/axdevice_base.git +[submodule "modules/axvisor_api"] + path = modules/axvisor_api + url = https://github.com/arceos-hypervisor/axvisor_api +[submodule "modules/x86_vcpu"] + path = modules/x86_vcpu + url = https://github.com/arceos-hypervisor/x86_vcpu +[submodule "modules/x86_vlapic"] + path = modules/x86_vlapic + url = https://github.com/arceos-hypervisor/x86_vlapic +[submodule "modules/axvmconfig"] + path = modules/axvmconfig + url = https://github.com/arceos-hypervisor/axvmconfig.git +[submodule "platform/axplat-aarch64-dyn"] + path = platform/axplat-aarch64-dyn + url = https://github.com/arceos-hypervisor/axplat-aarch64-dyn diff --git a/.rustfmt.toml b/.rustfmt.toml index 89ab2b7f..de5e7675 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1 +1 @@ -force_explicit_abi = false \ No newline at end of file +# force_explicit_abi = false \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 0b7e56a3..756f83ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,13 +11,22 @@ dependencies = [ "tock-registers 0.9.0", ] +[[package]] +name = "aarch64-cpu" +version = "11.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44171e22925ec72b63d86747bc3655c7849a5b8d865c980222128839f45ac034" +dependencies = [ + "tock-registers 0.10.1", +] + [[package]] name = "aarch64-cpu-ext" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52dad5cf7342926ce1c375ec680834e56dd3cdbe8b7adf8a6f99b2854cc52c17" dependencies = [ - "aarch64-cpu", + "aarch64-cpu 10.0.0", "tock-registers 0.10.1", ] @@ -27,17 +36,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a2c929f5025d9b8a0f549b187c4d3a39671f44015ff6ccddd0b134c874b3c1a" -[[package]] -name = "abi-singleton" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbdf894742ece5360a74aa8278c42e0f305aa9f7c35d43ebc9cceca105f7e434" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "addr2line" version = "0.25.1" @@ -112,15 +110,6 @@ dependencies = [ "libc", ] -[[package]] -name = "ansi_rgb" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a730095eb14ee842a0f1e68504b85c8d4a19b1ef2ac2a9b4debf0ed982f9b08a" -dependencies = [ - "rgb", -] - [[package]] name = "anstream" version = "0.6.21" @@ -198,7 +187,7 @@ name = "arceos_api" version = "0.2.0" source = "git+https://github.com/arceos-org/arceos.git?tag=dev-251216#16096568f5ae6ad2d687eff2927a4cb69cef8133" dependencies = [ - "axalloc", + "axalloc 0.2.0 (git+https://github.com/arceos-org/arceos.git?tag=dev-251216)", "axconfig", "axdriver", "axerrno 0.1.2", @@ -212,32 +201,18 @@ dependencies = [ "axtask", ] -[[package]] -name = "arm-gic-driver" -version = "0.14.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a886a953642fbf21eb5928c49a05f021fae007219ae8cb2dafbf403dfeba974a" -dependencies = [ - "aarch64-cpu", - "bitflags 2.10.0", - "enum_dispatch", - "log", - "rdif-intc 0.11.0", - "tock-registers 0.9.0", -] - [[package]] name = "arm-gic-driver" version = "0.15.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5487b0a69ebddf2f8affd1e0d32a875fc6213b3a15e3315f9d7beb34b174d457" dependencies = [ - "aarch64-cpu", + "aarch64-cpu 10.0.0", "bitflags 2.10.0", "enum_dispatch", "log", "paste", - "rdif-intc 0.12.1", + "rdif-intc", "tock-registers 0.9.0", ] @@ -262,14 +237,10 @@ dependencies = [ [[package]] name = "arm_vcpu" version = "0.1.1" -source = "git+https://github.com/arceos-hypervisor/arm_vcpu?branch=next#b24cc3635c049302ab8d58d3b54007bb5a053a96" dependencies = [ - "aarch64-cpu", - "axaddrspace", - "axdevice_base", + "aarch64-cpu 11.2.0", "axerrno 0.1.2", - "axvcpu", - "axvisor_api", + "axvm-types", "log", "numeric-enum-macro", "percpu", @@ -279,10 +250,8 @@ dependencies = [ [[package]] name = "arm_vgic" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f576b11b486e2ca12373c8205c4a06473a85cf7a664845e5961c47948910c3" dependencies = [ - "aarch64-cpu", + "aarch64-cpu 10.0.0", "aarch64_sysreg", "axaddrspace", "axdevice_base", @@ -290,7 +259,7 @@ dependencies = [ "axvisor_api", "bitmaps", "log", - "memory_addr 0.4.1", + "memory_addr", "spin 0.9.8", "tock-registers 0.10.1", ] @@ -307,6 +276,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0f477b951e452a0b6b4a10b53ccd569042d1d01729b519e02074a9c0958a063" +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + [[package]] name = "async-trait" version = "0.1.89" @@ -332,24 +307,36 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axaddrspace" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06b129114ab36be728ef11dd6540559c30deb6332378157d22bdc0aae6803a63" +version = "0.2.0" dependencies = [ + "assert_matches", "axerrno 0.1.2", + "axin", "bit_field", "bitflags 2.10.0", "cfg-if", + "lazy_static", "lazyinit", "log", - "memory_addr 0.4.1", + "memory_addr", "memory_set", "numeric-enum-macro", "page_table_entry", "page_table_multiarch", + "spin 0.10.0", "x86", ] +[[package]] +name = "axalloc" +version = "0.2.0" +dependencies = [ + "axerrno 0.1.2", + "buddy_system_allocator", + "kspin", + "log", +] + [[package]] name = "axalloc" version = "0.2.0" @@ -360,7 +347,7 @@ dependencies = [ "cfg-if", "kspin", "log", - "memory_addr 0.4.1", + "memory_addr", "strum 0.27.2", ] @@ -409,14 +396,14 @@ name = "axcpu" version = "0.3.0" source = "git+https://github.com/arceos-org/axcpu.git?tag=dev-v03#72ef3859952b7340bae261c9a50c32705e602017" dependencies = [ - "aarch64-cpu", + "aarch64-cpu 10.0.0", "axbacktrace", "cfg-if", "lazyinit", "linkme", "log", "loongArch64", - "memory_addr 0.4.1", + "memory_addr", "page_table_entry", "page_table_multiarch", "percpu", @@ -430,7 +417,6 @@ dependencies = [ [[package]] name = "axdevice" version = "0.1.0" -source = "git+https://github.com/arceos-hypervisor/axdevice.git#75d9db284fd4c9ee9607c2fd84967461eeaf5b07" dependencies = [ "arm_vgic", "axaddrspace", @@ -439,7 +425,7 @@ dependencies = [ "axvmconfig", "cfg-if", "log", - "memory_addr 0.4.1", + "memory_addr", "range-alloc", "spin 0.9.8", ] @@ -447,14 +433,12 @@ dependencies = [ [[package]] name = "axdevice_base" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c43baf33ed4790ffd3365c4ca027a1e3d1c2b6058f4605b67bca04cadf48d5" dependencies = [ "axaddrspace", "axerrno 0.1.2", "axvmconfig", "cfg-if", - "memory_addr 0.4.1", + "memory_addr", "serde", ] @@ -474,8 +458,8 @@ name = "axdriver" version = "0.2.0" source = "git+https://github.com/arceos-org/arceos.git?tag=dev-251216#16096568f5ae6ad2d687eff2927a4cb69cef8133" dependencies = [ - "arm-gic-driver 0.15.9", - "axalloc", + "arm-gic-driver", + "axalloc 0.2.0 (git+https://github.com/arceos-org/arceos.git?tag=dev-251216)", "axconfig", "axdriver_base 0.1.2 (git+https://github.com/arceos-org/axdriver_crates.git?tag=dev-v01)", "axdriver_block 0.1.2 (git+https://github.com/arceos-org/axdriver_crates.git?tag=dev-v01)", @@ -491,10 +475,10 @@ dependencies = [ "crate_interface", "dma-api 0.5.2", "log", - "memory_addr 0.4.1", - "rdif-block 0.6.2", - "rdif-intc 0.12.1", - "rdrive 0.18.11", + "memory_addr", + "rdif-block", + "rdif-intc", + "rdrive", "smallvec", "spin 0.10.0", ] @@ -589,7 +573,7 @@ name = "axfeat" version = "0.2.0" source = "git+https://github.com/arceos-org/arceos.git?tag=dev-251216#16096568f5ae6ad2d687eff2927a4cb69cef8133" dependencies = [ - "axalloc", + "axalloc 0.2.0 (git+https://github.com/arceos-org/arceos.git?tag=dev-251216)", "axbacktrace", "axdriver", "axfs", @@ -603,7 +587,7 @@ dependencies = [ [[package]] name = "axfs" -version = "0.1.0" +version = "0.3.0" dependencies = [ "axdriver", "axdriver_block 0.1.2 (git+https://github.com/arceos-org/axdriver_crates.git?tag=v0.1.2)", @@ -615,7 +599,7 @@ dependencies = [ "fatfs", "lazyinit", "log", - "rsext4 0.1.0 (git+https://github.com/Dirinkbottle/rsext4.git?tag=dev-251222)", + "rsext4", "spin 0.9.8", ] @@ -646,7 +630,7 @@ name = "axhal" version = "0.2.0" source = "git+https://github.com/arceos-org/arceos.git?tag=dev-251216#16096568f5ae6ad2d687eff2927a4cb69cef8133" dependencies = [ - "axalloc", + "axalloc 0.2.0 (git+https://github.com/arceos-org/arceos.git?tag=dev-251216)", "axconfig", "axcpu", "axplat", @@ -661,7 +645,7 @@ dependencies = [ "lazyinit", "linkme", "log", - "memory_addr 0.4.1", + "memory_addr", "page_table_multiarch", "percpu", ] @@ -675,12 +659,25 @@ dependencies = [ "numeric-enum-macro", ] +[[package]] +name = "axin" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db62cb7067e33d432df247b32ee450ae267cb16319c8c5de247381c3652a639" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "axio" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e797ff4cfd17460c7b8742222a2cadd72a2f4966f0057d36b5925fabf534f7" +checksum = "92a675c98dc5af5cca52cfdd1044ae960816909853cd13870737d55cb23f5d4e" dependencies = [ + "autocfg", "axerrno 0.1.2", ] @@ -703,7 +700,7 @@ version = "0.2.0" source = "git+https://github.com/arceos-hypervisor/axklib.git#7c0fc0588f978f7d75bb94f4e07477776ed37887" dependencies = [ "axerrno 0.1.2", - "memory_addr 0.4.1", + "memory_addr", "trait-ffi", ] @@ -723,14 +720,14 @@ name = "axmm" version = "0.2.0" source = "git+https://github.com/arceos-org/arceos.git?tag=dev-251216#16096568f5ae6ad2d687eff2927a4cb69cef8133" dependencies = [ - "axalloc", + "axalloc 0.2.0 (git+https://github.com/arceos-org/arceos.git?tag=dev-251216)", "axconfig", "axerrno 0.1.2", "axhal", "kspin", "lazyinit", "log", - "memory_addr 0.4.1", + "memory_addr", "memory_set", ] @@ -763,36 +760,35 @@ dependencies = [ "crate_interface", "handler_table", "kspin", - "memory_addr 0.4.1", + "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", + "aarch64-cpu 10.0.0", "aarch64-cpu-ext", "any-uart", - "arm-gic-driver 0.15.9", + "arm-gic-driver", "axconfig-macros", "axcpu", "axplat", "fdt-parser", - "heapless 0.8.0", + "heapless 0.9.2", "lazyinit", "log", - "memory_addr 0.4.1", + "memory_addr", "page_table_entry", "paste", "percpu", - "rdif-intc 0.12.1", - "rdrive 0.18.11", + "rdif-intc", + "rdrive", "serde", "somehal", "spin 0.10.0", - "toml 0.8.23", + "toml 0.9.10+spec-1.1.0", ] [[package]] @@ -800,8 +796,8 @@ name = "axplat-aarch64-peripherals" version = "0.3.0" source = "git+https://github.com/arceos-org/axplat_crates.git?tag=dev-v03#0df0713b1c20eafaeebdc6b0e194b2985e857949" dependencies = [ - "aarch64-cpu", - "arm-gic-driver 0.15.9", + "aarch64-cpu 10.0.0", + "arm-gic-driver", "arm_pl011", "arm_pl031", "axcpu", @@ -931,7 +927,7 @@ dependencies = [ name = "axruntime" version = "0.1.0" dependencies = [ - "axalloc", + "axalloc 0.2.0 (git+https://github.com/arceos-org/arceos.git?tag=dev-251216)", "axconfig", "axdisplay", "axdriver", @@ -951,7 +947,9 @@ dependencies = [ "crate_interface", "ctor_bare", "log", + "memory_addr", "percpu", + "percpu_macros", "somehal", ] @@ -1008,15 +1006,15 @@ dependencies = [ "kspin", "lazyinit", "log", - "memory_addr 0.4.1", + "memory_addr", "percpu", ] [[package]] name = "axum" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", "bytes", @@ -1067,78 +1065,58 @@ dependencies = [ [[package]] name = "axvcpu" version = "0.1.2" -source = "git+https://github.com/arceos-hypervisor/axvcpu.git?branch=next#343ec3ccf99a86fb9c67a7b0372e9b7a745f0640" dependencies = [ "axaddrspace", "axerrno 0.1.2", "axvisor_api", - "memory_addr 0.4.1", + "memory_addr", "percpu", ] [[package]] name = "axvisor" -version = "0.0.0" +version = "0.3.0" dependencies = [ - "aarch64-cpu-ext", "anyhow", - "arm-gic-driver 0.15.9", - "axaddrspace", "axconfig", - "axdevice", - "axdevice_base", "axerrno 0.2.2", "axhvc", - "axruntime", "axstd", - "axvcpu", - "axvisor_api", "axvm", + "axvmconfig", "bitflags 2.10.0", "byte-unit", "cfg-if", "cpumask", - "crate_interface", "driver", "extern-trait", - "fdt-parser", "kernel_guard", "kspin", "lazy_static", "lazyinit", "log", - "memory_addr 0.4.1", - "page_table_entry", - "page_table_multiarch", - "percpu", + "memory_addr", "prettyplease", "quote", - "rdif-intc 0.12.1", - "rdrive 0.18.11", "spin 0.9.8", "syn 2.0.111", "timer_list", "toml 0.9.10+spec-1.1.0", - "vm-fdt 0.3.0 (git+https://github.com/bullhh/vm-fdt.git)", ] [[package]] name = "axvisor_api" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa7233b2a1338dc06a80e2779b572b4df02007ea128ef7b235b66fc3eeac0ca6" dependencies = [ "axaddrspace", "axvisor_api_proc", "crate_interface", - "memory_addr 0.4.1", + "memory_addr", ] [[package]] name = "axvisor_api_proc" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a64eb4410ae8357ac8c01c2fb201e57d7aeeb5436ed4d0f5774bfa11cc5902" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1149,32 +1127,50 @@ dependencies = [ [[package]] name = "axvm" version = "0.1.0" -source = "git+https://github.com/arceos-hypervisor/axvm.git?branch=next#e161233e58c0ef0c6ec115ffa5b0d17dadd298be" dependencies = [ + "aarch64-cpu 11.2.0", + "aarch64-cpu-ext", + "anyhow", "arm_vcpu", - "arm_vgic", "axaddrspace", - "axdevice", - "axdevice_base", - "axerrno 0.2.2", - "axvcpu", + "axerrno 0.1.2", + "axhal", + "axplat-x86-qemu-q35", + "axruntime", + "axstd", + "axvm-types", "axvmconfig", + "bitmap-allocator", "cfg-if", "cpumask", + "fdt-edit", + "kspin", + "lazyinit", "log", - "memory_addr 0.4.1", + "memory_addr", "page_table_entry", "page_table_multiarch", "percpu", - "riscv_vcpu", - "spin 0.9.8", + "ranges-ext", + "raw-cpuid 11.6.0", + "spin 0.10.0", + "thiserror 2.0.17", + "timer_list", + "vm-allocator", "x86_vcpu", ] +[[package]] +name = "axvm-types" +version = "0.1.0" +dependencies = [ + "bitflags 2.10.0", + "memory_addr", +] + [[package]] name = "axvmconfig" version = "0.1.0" -source = "git+https://github.com/arceos-hypervisor/axvmconfig.git?branch=next#5a8b64a47510b17da71e54cabbdf8c999ba2e2c9" dependencies = [ "axerrno 0.1.2", "clap", @@ -1202,25 +1198,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "bare-metal" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" - -[[package]] -name = "bare-test" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7dfcf95987c500af4665d8a18adbc5e7d3177a2537964f48002b88e28fb055e" -dependencies = [ - "bare-test-macros", - "log", - "sparreal-kernel", - "sparreal-macros 0.9.3", - "sparreal-rt", -] - [[package]] name = "bare-test-macros" version = "0.2.0" @@ -1260,30 +1237,10 @@ dependencies = [ "cargo_metadata 0.20.0", "flate2", "rand 0.9.2", - "reqwest 0.12.26", + "reqwest 0.12.28", "tar", ] -[[package]] -name = "bindgen" -version = "0.71.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" -dependencies = [ - "bitflags 2.10.0", - "cexpr", - "clang-sys", - "itertools 0.13.0", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.111", -] - [[package]] name = "bit" version = "0.1.1" @@ -1533,23 +1490,14 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.49" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ "find-msvc-tools", "shlex", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" version = "1.0.4" @@ -1575,17 +1523,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" version = "4.5.53" @@ -1841,7 +1778,7 @@ dependencies = [ "document-features", "mio", "parking_lot", - "rustix 1.1.2", + "rustix 1.1.3", "signal-hook", "signal-hook-mio", "winapi", @@ -1866,16 +1803,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "cstr_core" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd98742e4fdca832d40cab219dc2e3048de17d873248f83f17df47c1bea70956" -dependencies = [ - "cty", - "memchr", -] - [[package]] name = "ctor_bare" version = "0.2.1" @@ -1896,12 +1823,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "cty" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" - [[package]] name = "cursive" version = "0.21.1" @@ -2074,18 +1995,18 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "convert_case 0.10.0", "proc-macro2", @@ -2159,14 +2080,14 @@ dependencies = [ [[package]] name = "driver" -version = "0.1.0" +version = "0.3.0" dependencies = [ "axklib", "log", - "phytium-mci 0.1.0 (git+https://github.com/YanQD/phytium-mci.git?rev=99c9ee5)", - "rdif-block 0.6.2", + "phytium-mci", + "rdif-block", "rdif-clk", - "rdrive 0.18.11", + "rdrive", "rk3568_clk", "rk3588-clk", "rockchip-pm", @@ -2388,12 +2309,34 @@ dependencies = [ "log", ] +[[package]] +name = "fdt-edit" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b0f564fda6b9389cec0b258a98483b974b6ed37cf4e771222fb49cabe1e260f" +dependencies = [ + "enum_dispatch", + "fdt-raw", + "log", +] + [[package]] name = "fdt-parser" version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f95f0bda5ff920492f6573294d8e3a99b75ee2e5ef93ab313fc6d517fa46785" +[[package]] +name = "fdt-raw" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7b19f67663e8368d5a07165a1c348b5a761afe5d130e982a0ed8859aca37c2" +dependencies = [ + "heapless 0.9.2", + "log", + "thiserror 2.0.17", +] + [[package]] name = "filetime" version = "0.2.26" @@ -2428,7 +2371,7 @@ dependencies = [ "serde", "sha1", "thiserror 2.0.17", - "vm-fdt 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vm-fdt", ] [[package]] @@ -2616,12 +2559,6 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - [[package]] name = "h2" version = "0.3.27" @@ -2684,16 +2621,6 @@ dependencies = [ "ahash 0.7.8", ] -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash 0.8.12", - "allocator-api2", -] - [[package]] name = "hashbrown" version = "0.15.5" @@ -3150,15 +3077,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "itertools" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -3170,9 +3088,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" [[package]] name = "jiff" @@ -3231,18 +3149,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "kasm-aarch64" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e484b7a4686e2750fae1b4c4b750e14f1522eb303288d9d2723a955c2a41b7d7" -dependencies = [ - "darling 0.20.11", - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "kasm-aarch64" version = "0.2.0" @@ -3337,16 +3243,6 @@ version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" -[[package]] -name = "libloading" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if", - "windows-link", -] - [[package]] name = "libredox" version = "0.1.11" @@ -3479,15 +3375,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "lwext4_rust" -version = "0.2.0" -dependencies = [ - "bindgen", - "log", - "printf-compat", -] - [[package]] name = "lzma-rs" version = "0.3.0" @@ -3537,21 +3424,6 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "memory_addr" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5438b8df0f13e16e1f46140de247695a95952a5a4479e47197a8711bf1063373" - [[package]] name = "memory_addr" version = "0.4.1" @@ -3565,7 +3437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50a49ecd4114cf87f7e442ec5dd03bd590e7094541f987057310dbb32a6341ad" dependencies = [ "axerrno 0.1.2", - "memory_addr 0.4.1", + "memory_addr", ] [[package]] @@ -3584,12 +3456,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -3668,18 +3534,8 @@ dependencies = [ ] [[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "nop" -version = "0.1.0" +name = "nop" +version = "0.3.0" [[package]] name = "num" @@ -3847,9 +3703,9 @@ dependencies = [ [[package]] name = "ostool" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9faa3c583310e624402b2c8ee736594bc1fc99c225a06e40a6e7073fba98f71" +checksum = "b6606e47adb53299e44254202d83481ce0bf6624eb8c3f8fca6ee4241c37e7a9" dependencies = [ "anyhow", "byte-unit", @@ -3868,7 +3724,7 @@ dependencies = [ "object", "ratatui", "regex", - "reqwest 0.12.26", + "reqwest 0.12.28", "schemars", "serde", "serde_json", @@ -3882,28 +3738,6 @@ dependencies = [ "ureq", ] -[[package]] -name = "page-table-arm" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ce2c42338660c47a35e7b2940dcccbe6612a4a0aa0485ecdf4e23aa8a2a1158" -dependencies = [ - "aarch64-cpu", - "bitflags 2.10.0", - "log", -] - -[[package]] -name = "page-table-generic" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "827063f64bbad7b7655092b0f98824ffbe85e89646388eb4dad1e3d797056a2f" -dependencies = [ - "bitflags 2.10.0", - "log", - "thiserror 2.0.17", -] - [[package]] name = "page-table-generic" version = "0.6.1" @@ -3922,9 +3756,9 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dda9891ec368fda90e4b2cc36592b4881073e25a339fe7e3eddd811f0cf6bf18" dependencies = [ - "aarch64-cpu", + "aarch64-cpu 10.0.0", "bitflags 2.10.0", - "memory_addr 0.4.1", + "memory_addr", "x86_64", ] @@ -3935,7 +3769,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa11a21844255e14aa6688ef0eafb058d7be19338633024fb59417f1bfb07f8" dependencies = [ "log", - "memory_addr 0.4.1", + "memory_addr", "page_table_entry", "riscv", "x86", @@ -3970,12 +3804,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pasts" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efcd36303871fb977a47dabc9af736c75c492bb32a92fa26262b2741531e97ce" - [[package]] name = "pci_types" version = "0.10.0" @@ -3986,19 +3814,6 @@ dependencies = [ "bitflags 2.10.0", ] -[[package]] -name = "pcie" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e45cda4b8ef9f2a8dae7cf5b58c11b41d35fbe62a4d9693fd2d143225fbf44" -dependencies = [ - "bit_field", - "bitflags 2.10.0", - "log", - "pci_types", - "sparreal-macros 0.0.5", -] - [[package]] name = "pcie" version = "0.4.5" @@ -4043,26 +3858,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "phytium-mci" -version = "0.1.0" -dependencies = [ - "bare-test", - "bare-test-macros", - "bitflags 2.10.0", - "byte-unit", - "bytemuck", - "dma-api 0.2.2", - "lazy_static", - "log", - "nb", - "pcie 0.2.7", - "rlsf", - "spin 0.10.0", - "spin_on", - "tock-registers 0.9.0", -] - [[package]] name = "phytium-mci" version = "0.1.0" @@ -4080,33 +3875,6 @@ dependencies = [ "tock-registers 0.9.0", ] -[[package]] -name = "pie-boot" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524d0fc5cd834d2179d8a88cde327b0e168dd6aedf30ce1832467a924a35594f" -dependencies = [ - "aarch64-cpu", - "aarch64-cpu-ext", - "bindeps-simple", - "fdt-parser", - "heapless 0.8.0", - "kasm-aarch64 0.1.3", - "kdef-pgtable", - "pie-boot-if 0.6.0", - "pie-boot-loader-aarch64 0.1.27", - "pie-boot-macros", -] - -[[package]] -name = "pie-boot-if" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00af8d4efee0eee91ead95b34c50c067163dc4c90b874b1cc4caa671eb1d85b" -dependencies = [ - "heapless 0.8.0", -] - [[package]] name = "pie-boot-if" version = "0.8.0" @@ -4116,47 +3884,23 @@ dependencies = [ "heapless 0.8.0", ] -[[package]] -name = "pie-boot-loader-aarch64" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ee18ed1de7f55f318f01803bf7dc353ef492db71a2005fa3af36f116f25d28" -dependencies = [ - "aarch64-cpu", - "aarch64-cpu-ext", - "any-uart", - "bitflags 2.10.0", - "fdt-parser", - "kasm-aarch64 0.1.3", - "kdef-pgtable", - "log", - "num-align", - "page-table-generic 0.6.1", - "pie-boot-if 0.6.0", - "prettyplease", - "quote", - "spin 0.10.0", - "syn 2.0.111", - "thiserror 2.0.17", -] - [[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", + "aarch64-cpu 10.0.0", "aarch64-cpu-ext", "any-uart", "bitflags 2.10.0", "fdt-parser", - "kasm-aarch64 0.2.0", + "kasm-aarch64", "kdef-pgtable", "log", "num-align", - "page-table-generic 0.6.1", - "pie-boot-if 0.8.0", + "page-table-generic", + "pie-boot-if", "prettyplease", "quote", "spin 0.10.0", @@ -4196,9 +3940,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" [[package]] name = "portable-atomic-util" @@ -4243,18 +3987,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "printf-compat" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b002af28ffe3d3d67202ae717810a28125a494d5396debc43de01ee136ac404" -dependencies = [ - "bitflags 1.3.2", - "cstr_core", - "cty", - "itertools 0.9.0", -] - [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -4455,6 +4187,16 @@ name = "range-alloc" version = "0.1.4" source = "git+https://github.com/arceos-hypervisor/range-alloc.git#fc826e54dab9072be5358a1b0e7fc34503d6630d" +[[package]] +name = "ranges-ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e350c92fb797d3d5f1ce45686618a922130cdd617c76a5a706f504e8b3d5904" +dependencies = [ + "heapless 0.9.2", + "thiserror 2.0.17", +] + [[package]] name = "ratatui" version = "0.29.0" @@ -4467,7 +4209,7 @@ dependencies = [ "crossterm 0.28.1", "indoc", "instability", - "itertools 0.13.0", + "itertools", "lru", "paste", "strum 0.26.3", @@ -4494,18 +4236,6 @@ dependencies = [ "bitflags 2.10.0", ] -[[package]] -name = "rdif-base" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6953f438bbbdf58e55513c31e70fa0f85daba2927e8a59130a04608141bb552" -dependencies = [ - "as-any", - "async-trait", - "rdif-def", - "thiserror 2.0.17", -] - [[package]] name = "rdif-base" version = "0.7.0" @@ -4519,16 +4249,6 @@ dependencies = [ "thiserror 2.0.17", ] -[[package]] -name = "rdif-block" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7b8e19dc3cb6cd7241085a9560a91d4346edbc525bcbfc3c86e5eeb11559c19" -dependencies = [ - "cfg-if", - "rdif-base 0.6.0", -] - [[package]] name = "rdif-block" version = "0.6.2" @@ -4538,7 +4258,7 @@ dependencies = [ "cfg-if", "dma-api 0.5.2", "futures", - "rdif-base 0.7.0", + "rdif-base", "spin_on", "thiserror 2.0.17", ] @@ -4549,7 +4269,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9af012204e25d45735aa141b475c411b833b4f4bc580924905745d4afbbf606" dependencies = [ - "rdif-base 0.7.0", + "rdif-base", ] [[package]] @@ -4561,17 +4281,6 @@ dependencies = [ "thiserror 2.0.17", ] -[[package]] -name = "rdif-intc" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e7622f78dc9b40958500119553f3c15b9bb9829002d87d0f4b114ebe302a40" -dependencies = [ - "cfg-if", - "rdif-base 0.6.0", - "thiserror 2.0.17", -] - [[package]] name = "rdif-intc" version = "0.12.1" @@ -4579,7 +4288,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "170ec813e6cf4d1e5fa53fa8fed0fadc7aaab96683d4f1d44c602a6109931eb4" dependencies = [ "cfg-if", - "rdif-base 0.7.0", + "rdif-base", "thiserror 2.0.17", ] @@ -4590,59 +4299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60c6e8dea6d432b2c03bc3f4238dc59a276aacac6f688a937351e7a313918738" dependencies = [ "pci_types", - "rdif-base 0.7.0", - "thiserror 2.0.17", -] - -[[package]] -name = "rdif-power" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7b6eefca0d1b44a5bef1e934d8ab2c8e00e19dd3d9e071855c0933637ee17a0" -dependencies = [ - "rdif-base 0.7.0", -] - -[[package]] -name = "rdif-serial" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673854a0c554806da63f0836c95b34b08956a143ff15e327644cbd07a8e0df31" -dependencies = [ - "futures", - "rdif-base 0.7.0", - "spin_on", - "thiserror 2.0.17", -] - -[[package]] -name = "rdif-systick" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11da4f362ab6cdcdee9d8e795faabd0f15e04167cb17939fd3aca19c2ef3421" -dependencies = [ - "rdif-base 0.7.0", -] - -[[package]] -name = "rdrive" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ce47e5a3d10943dfdb8c31dcca7a91c353ea43f4ac2eb72c92462e83a2baa4" -dependencies = [ - "enum_dispatch", - "fdt-parser", - "log", - "paste", - "rdif-base 0.6.0", - "rdif-block 0.5.0", - "rdif-clk", - "rdif-intc 0.11.0", - "rdif-power", - "rdif-serial", - "rdif-systick", - "rdrive-macros", - "spin 0.10.0", + "rdif-base", "thiserror 2.0.17", ] @@ -4655,25 +4312,14 @@ dependencies = [ "fdt-parser", "log", "paste", - "pcie 0.4.5", - "rdif-base 0.7.0", + "pcie", + "rdif-base", "rdif-pcie", "rdrive-macros", "spin 0.10.0", "thiserror 2.0.17", ] -[[package]] -name = "rdrive-macro-utils" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "977fcecf5b5fe8d7189d497d8754d27a4ffaedeac904cce1b7ea7bdfb5280934" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "rdrive-macros" version = "0.4.1" @@ -4819,9 +4465,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.26" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", @@ -4863,15 +4509,6 @@ dependencies = [ "webpki-roots", ] -[[package]] -name = "rgb" -version = "0.8.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" -dependencies = [ - "bytemuck", -] - [[package]] name = "ring" version = "0.17.14" @@ -4899,25 +4536,6 @@ dependencies = [ "riscv-pac", ] -[[package]] -name = "riscv-decode" -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.10.0", - "log", - "riscv", -] - [[package]] name = "riscv-macros" version = "0.2.0" @@ -4944,39 +4562,12 @@ dependencies = [ "tock-registers 0.10.1", ] -[[package]] -name = "riscv_vcpu" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13f38f28fe6c02bb3ced43087c9667b23d18adf729becdc5adf1253f7df83904" -dependencies = [ - "axaddrspace", - "axerrno 0.1.2", - "axvcpu", - "axvisor_api", - "bit_field", - "bitflags 2.10.0", - "cfg-if", - "crate_interface", - "log", - "memoffset", - "memory_addr 0.4.1", - "page_table_entry", - "riscv", - "riscv-decode", - "riscv-h", - "rustsbi", - "sbi-rt", - "sbi-spec", - "tock-registers 0.9.0", -] - [[package]] name = "rk3568_clk" version = "0.1.0" source = "git+https://github.com/drivercraft/rk3568-clk.git#2b63818f9f9f576d99988fafa70de41112524e00" dependencies = [ - "aarch64-cpu", + "aarch64-cpu 10.0.0", "bare-test-macros", "fdt-parser", "kspin", @@ -5043,19 +4634,10 @@ dependencies = [ "dma-api 0.5.2", "log", "mbarrier", - "rdif-base 0.7.0", + "rdif-base", "tock-registers 0.10.1", ] -[[package]] -name = "rsext4" -version = "0.1.0" -dependencies = [ - "bitflags 2.10.0", - "lazy_static", - "log", -] - [[package]] name = "rsext4" version = "0.1.0" @@ -5118,9 +4700,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags 2.10.0", "errno", @@ -5174,28 +4756,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "rustsbi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c13763120794ed11d64bac885fb31d384ae385c3287b0697711b97affbf8ab" -dependencies = [ - "rustsbi-macros", - "sbi-rt", - "sbi-spec", -] - -[[package]] -name = "rustsbi-macros" -version = "0.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a71347da9582cc6b6f3652c7d2c06516c9555690b3738ecdff7e84297f4e17fc" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "rustversion" version = "1.0.22" @@ -5213,9 +4773,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" [[package]] name = "sbi-rt" @@ -5277,7 +4837,7 @@ name = "sdmmc" version = "0.1.0" source = "git+https://github.com/drivercraft/sdmmc.git#cc6ae8e4ecb10b69d1e2fee5502f28198a057bba" dependencies = [ - "aarch64-cpu", + "aarch64-cpu 10.0.0", "arm_pl011", "bare-test-macros", "bitflags 2.10.0", @@ -5287,7 +4847,7 @@ dependencies = [ "kspin", "log", "paste", - "smccc 0.2.2", + "smccc", "spin 0.10.0", ] @@ -5395,9 +4955,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "217ca874ae0207aac254aa02c957ded05585a90892cc8d87f9e5fa49669dadd8" dependencies = [ "itoa", "memchr", @@ -5560,12 +5120,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "smccc" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617d17f088ec733e5a6b86da6ce4cce1414e6e856d6061c16dda51cceae6f68c" - [[package]] name = "smccc" version = "0.2.2" @@ -5615,107 +5169,29 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b5f763b9ab0ce9efd2d8eba9e5b457f93f6426ede68435fe9567cf7681f29d" dependencies = [ - "aarch64-cpu", + "aarch64-cpu 10.0.0", "aarch64-cpu-ext", "any-uart", "bindeps-simple", "fdt-parser", "futures", "heapless 0.8.0", - "kasm-aarch64 0.2.0", + "kasm-aarch64", "kdef-pgtable", "log", "num-align", - "page-table-generic 0.6.1", - "pie-boot-if 0.8.0", - "pie-boot-loader-aarch64 0.3.3", + "page-table-generic", + "pie-boot-if", + "pie-boot-loader-aarch64", "pie-boot-macros", "release-dep", "serde", - "smccc 0.2.2", + "smccc", "spin 0.10.0", "toml 0.9.10+spec-1.1.0", "url", ] -[[package]] -name = "sparreal-kernel" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b182a508314f1560ce8f94090f8c0990640bd849fab49e307ccafe2b51e67da9" -dependencies = [ - "ansi_rgb", - "anyhow", - "arrayvec", - "buddy_system_allocator", - "byte-unit", - "dma-api 0.3.1", - "fdt-parser", - "lazy_static", - "lock_api", - "log", - "memory_addr 0.3.2", - "page-table-generic 0.5.3", - "pasts", - "rdrive 0.15.3", - "rgb", - "sparreal-macros 0.9.3", - "spin 0.9.8", - "thiserror 2.0.17", -] - -[[package]] -name = "sparreal-macros" -version = "0.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f686073b67b2427c9243bddc10ea0a6a5300ab5354a8ee884d9126854b0abab7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "sparreal-macros" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c39b68430130f1c7587eb41f512dd1f6e48bc22a3e1dc11a69dc1b8294cdc90" -dependencies = [ - "abi-singleton", - "darling 0.20.11", - "proc-macro2", - "quote", - "rdrive-macro-utils", - "syn 2.0.111", -] - -[[package]] -name = "sparreal-rt" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9da6adb0285c99f180e9746ebbadcf4886b1b009904f6f9ab5be155ae1325a7" -dependencies = [ - "aarch64-cpu", - "aarch64-cpu-ext", - "ansi_rgb", - "any-uart", - "arm-gic-driver 0.14.9", - "arrayvec", - "buddy_system_allocator", - "fdt-parser", - "log", - "memory_addr 0.3.2", - "numeric-enum-macro", - "page-table-arm", - "page-table-generic 0.5.3", - "pie-boot", - "rgb", - "smccc 0.1.1", - "sparreal-kernel", - "sparreal-macros 0.9.3", - "spin 0.9.8", -] - [[package]] name = "spin" version = "0.9.8" @@ -5939,7 +5415,7 @@ dependencies = [ "fastrand", "getrandom 0.3.4", "once_cell", - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.61.2", ] @@ -6391,7 +5867,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ - "itertools 0.13.0", + "itertools", "unicode-segmentation", "unicode-width 0.1.14", ] @@ -6526,18 +6002,19 @@ dependencies = [ ] [[package]] -name = "vm-fdt" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e21282841a059bb62627ce8441c491f09603622cd5a21c43bfedc85a2952f23" +name = "vm-allocator" +version = "0.1.3" +source = "git+https://github.com/rust-vmm/vm-allocator.git#080ea204ee2b8f4a25a25b6502b22d7e365aa948" +dependencies = [ + "libc", + "thiserror 2.0.17", +] [[package]] name = "vm-fdt" version = "0.3.0" -source = "git+https://github.com/bullhh/vm-fdt.git#f8caf77fadf4e925e91df5bd211c8a96dc1f6e07" -dependencies = [ - "hashbrown 0.14.5", -] +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e21282841a059bb62627ce8441c491f09603622cd5a21c43bfedc85a2952f23" [[package]] name = "volatile" @@ -7069,25 +6546,20 @@ dependencies = [ [[package]] name = "x86_vcpu" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "873e097d52e94c31be3f0175a9f8d6f2edbc77d7e2f8e6995427df9c08b30a2b" dependencies = [ "axaddrspace", "axdevice_base", - "axerrno 0.1.2", - "axvcpu", - "axvisor_api", "bit_field", "bitflags 2.10.0", "cfg-if", - "crate_interface", "log", - "memory_addr 0.4.1", + "memory_addr", "numeric-enum-macro", "page_table_entry", "paste", "raw-cpuid 11.6.0", "spin 0.9.8", + "thiserror 2.0.17", "x86", "x86_64", "x86_vlapic", @@ -7096,16 +6568,13 @@ dependencies = [ [[package]] name = "x86_vlapic" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2556c62649a277ccf1c3c34c740be87bbde5f8dab0b20fcdcf4c2cd7bb6e7302" dependencies = [ "axaddrspace", "axdevice_base", "axerrno 0.1.2", - "axvisor_api", "bit", "log", - "memory_addr 0.4.1", + "memory_addr", "paste", "tock-registers 0.10.1", ] @@ -7117,7 +6586,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", - "rustix 1.1.2", + "rustix 1.1.3", ] [[package]] @@ -7139,7 +6608,7 @@ dependencies = [ "flate2", "jkconfig", "ostool", - "reqwest 0.12.26", + "reqwest 0.12.28", "schemars", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index f06c0194..ac2f4e6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,8 @@ [workspace] +exclude = [ + "modules/axvisor_api", + "arceos", +] members = [ "crates/*", "modules/*", @@ -16,7 +20,7 @@ lto = true authors = ["Keyang Hu ", "周睿 "] edition = "2024" license = "GPL-3.0-or-later OR Apache-2.0 OR MulanPubL-2.0 OR MulanPSL2" -version = "0.1.0" +version = "0.3.0" [workspace.dependencies] bitflags = "2.2" @@ -30,6 +34,8 @@ log = "0.4" spin = "0.9" timer_list = "0.1" toml = "0.9" +ranges-ext = "0.3" +vm-allocator = {git = "https://github.com/rust-vmm/vm-allocator.git", ref = "c66cfac", default-features = false} # System dependent modules provided by ArceOS. # FIXME: pin to a specific tag! @@ -39,6 +45,8 @@ axstd = {git = "https://github.com/arceos-org/arceos.git", tag = "dev-251216", f "irq", "multitask", "task-ext", + "alloc", + "myplat", "smp", # "page-alloc-64g", ]} @@ -57,13 +65,13 @@ axcpu = {git = "https://github.com/arceos-org/axcpu.git", tag = "dev-v03"} axplat = {git = "https://github.com/arceos-org/axplat_crates.git", tag = "dev-v03"} # System dependent modules provided by ArceOS-Hypervisor. -axaddrspace = "0.1.1" +axaddrspace = "0.2" axhvc = {git = "https://github.com/arceos-hypervisor/axhvc.git"} axklib = {git = "https://github.com/arceos-hypervisor/axklib.git"} axruntime = {path = "modules/axruntime"} axfs = {path = "modules/axfs"} axvcpu = "0.1" -axvm = {git = "https://github.com/arceos-hypervisor/axvm.git", branch = "next"} +axvm = {path = "modules/axvm"} # System independent crates provided by ArceOS, these crates could be imported by remote url. axerrno = "0.2" @@ -73,26 +81,40 @@ fdt-parser = "0.4" memory_addr = "0.4" page_table_entry = {version = "0.5", features = ["arm-el2"]} page_table_multiarch = "0.5" -percpu = {version = "0.2", features = ["arm-el2"]} rdif-intc = "0.12" rdrive = "0.18" -vm-fdt = {git = "https://github.com/bullhh/vm-fdt.git", default-features = false, features = ["alloc"]} +# percpu "0.2.1" can not compile on aarch64 +percpu = {version = "=0.2.0", features = ["arm-el2"]} +percpu_macros = "=0.2.0" axdevice = {git = "https://github.com/arceos-hypervisor/axdevice.git"} axdevice_base = "0.1" +axplat-aarch64-dyn = {path = "platform/axplat-aarch64-dyn", features = ["irq", "smp", "hv"]} axvisor_api = "0.1" +axvm-types = {path = "modules/axvm-types"} driver = {path = "modules/driver"} # platform axplat-x86-qemu-q35 = {path = "platform/x86-qemu-q35"} -axvmconfig = {git = "https://github.com/arceos-hypervisor/axvmconfig.git", branch = "next"} +axvmconfig = {version = "0.1", default-features = false} [patch.crates-io] -axvcpu = {git = "https://github.com/arceos-hypervisor/axvcpu.git", branch = "next"} -axvmconfig = {git = "https://github.com/arceos-hypervisor/axvmconfig.git", branch = "next"} +arm_vcpu = {path = "modules/arm_vcpu"} +arm_vgic = {path = "modules/arm_vgic"} +axaddrspace = {path = "modules/axaddrspace"} +axdevice_base = {path = "modules/axdevice_base"} +axvcpu = {path = "modules/axvcpu"} +axvisor_api = {path = "modules/axvisor_api"} +axvmconfig = {path = "modules/axvmconfig"} +x86_vcpu = {path = "modules/x86_vcpu"} +x86_vlapic = {path = "modules/x86_vlapic"} + +[patch."https://github.com/arceos-hypervisor/axdevice".axdevice] +path = "modules/axdevice" [patch."https://github.com/arceos-org/arceos"] +# axalloc = {path = "modules/axalloc"} axconfig = {path = "modules/axconfig"} axruntime = {path = "modules/axruntime"} axfs = {path = "modules/axfs"} diff --git a/configs/board/orangepi-5-plus.toml b/configs/board/orangepi-5-plus.toml index 21559ce5..957287b5 100644 --- a/configs/board/orangepi-5-plus.toml +++ b/configs/board/orangepi-5-plus.toml @@ -1,6 +1,5 @@ cargo_args = [] features = [ - # "ept-level-4", "dyn-plat", "axstd/bus-mmio", "driver/sdmmc", diff --git a/configs/board/phytiumpi.toml b/configs/board/phytiumpi.toml index a647a23c..69f2d018 100644 --- a/configs/board/phytiumpi.toml +++ b/configs/board/phytiumpi.toml @@ -1,6 +1,5 @@ cargo_args = [] features = [ - # "ept-level-4", "dyn-plat", "axstd/bus-mmio", "fs", diff --git a/configs/board/qemu-aarch64.toml b/configs/board/qemu-aarch64.toml index c379cc79..45b77c89 100644 --- a/configs/board/qemu-aarch64.toml +++ b/configs/board/qemu-aarch64.toml @@ -1,6 +1,5 @@ cargo_args = [] features = [ - "ept-level-4", "axstd/bus-mmio", "dyn-plat", ] diff --git a/configs/board/qemu-x86_64.toml b/configs/board/qemu-x86_64.toml index 1f5d32cd..c4ffa5af 100644 --- a/configs/board/qemu-x86_64.toml +++ b/configs/board/qemu-x86_64.toml @@ -1,7 +1,6 @@ cargo_args = [] features = [ "axstd/myplat", - "ept-level-4", "fs", ] log = "Info" diff --git a/configs/board/roc-rk3568-pc.toml b/configs/board/roc-rk3568-pc.toml index 7e5e8014..50d73c86 100644 --- a/configs/board/roc-rk3568-pc.toml +++ b/configs/board/roc-rk3568-pc.toml @@ -1,6 +1,5 @@ cargo_args = [] features = [ - # "ept-level-4", "dyn-plat", "axstd/bus-mmio", "fs", diff --git a/configs/vms/arceos-aarch64-e2000-smp1.toml b/configs/vms/arceos-aarch64-e2000-smp1.toml index d8f73022..7663b404 100644 --- a/configs/vms/arceos-aarch64-e2000-smp1.toml +++ b/configs/vms/arceos-aarch64-e2000-smp1.toml @@ -10,34 +10,36 @@ vm_type = 1 # The number of virtual CPUs. cpu_num = 1 # The physical CPU ids. -phys_cpu_ids = [0x00] +# phys_cpu_ids = [0x00] # # Vm kernel configs # [kernel] # The entry point of the kernel image. -entry_point = 0x20_2008_0000 +# entry_point = 0x20_2008_0000 # The location of image: "memory" | "fs". # Load from file system. image_location = "fs" # The load address of the kernel image. -kernel_load_addr = 0x20_2008_0000 +# kernel_load_addr = 0x20_2008_0000 ## The file path of the kernel image. kernel_path = "/guest/arceos/phytiumpi" ## The file path of the device tree blob (DTB). -dtb_load_addr = 0x20_2000_0000 +# dtb_load_addr = 0x20_2000_0000 #dtb_path = "/path/to/axvisor/configs/vms/arceos-aarch64-e2000_smp1.dtb" # Memory regions with format (`base_paddr`, `size`, `flags`, `map_type`). # For `map_type`, 0 means `MAP_ALLOC`, 1 means `MAP_IDENTICAL`. memory_regions = [ - [0x20_2000_0000, 0x2000_0000, 0x7, 1], # System RAM MAP_IDENTICAL + [0x9000_0000, 0x2000_0000, 0x7, 1], # System RAM MAP_IDENTICAL ] # # Device specifications # [devices] + +interrupt_mode = "passthrough" # Emu_devices. # Name Base-Ipa Ipa_len Alloc-Irq Emu-Type EmuConfig. emu_devices = [] diff --git a/configs/vms/arceos-aarch64-rk3568-smp1.toml b/configs/vms/arceos-aarch64-rk3568-smp1.toml index 432f5377..12f9cbb9 100644 --- a/configs/vms/arceos-aarch64-rk3568-smp1.toml +++ b/configs/vms/arceos-aarch64-rk3568-smp1.toml @@ -10,23 +10,23 @@ vm_type = 1 # The number of virtual CPUs. cpu_num = 1 # The physical CPU ids. -phys_cpu_ids = [0x200] +# cpu_ids = [0] # # Vm kernel configs # [kernel] # The entry point of the kernel image. -entry_point = 0x7008_0000 +# entry_point = 0x7008_0000 # The location of image: "memory" | "fs". # Load from memory. image_location = "fs" # The load address of the kernel image. -kernel_load_addr = 0x7008_0000 +# kernel_load_addr = 0x7008_0000 ## The file path of the kernel image. kernel_path = "/userdata/rootfs_overlay/guest/arceos/roc-rk3568-pc" ## The file path of the device tree blob (DTB). -dtb_load_addr = 0x7000_0000 +# dtb_load_addr = 0x7000_0000 #dtb_path = "/path/arceos-rk3568.dtb" # Memory regions with format (`base_paddr`, `size`, `flags`, `map_type`). # For `map_type`, 0 means `MAP_ALLOC`, 1 means `MAP_IDENTICAL`. diff --git a/configs/vms/linux-aarch64-e2000-smp1.toml b/configs/vms/linux-aarch64-e2000-smp1.toml index ba5a6d32..f7b35dc2 100644 --- a/configs/vms/linux-aarch64-e2000-smp1.toml +++ b/configs/vms/linux-aarch64-e2000-smp1.toml @@ -10,23 +10,23 @@ vm_type = 1 # The number of virtual CPUs. cpu_num = 1 # The physical CPU ids. -phys_cpu_ids = [0x100] +# phys_cpu_ids = [0x100] # # Vm kernel configs # [kernel] # The entry point of the kernel image. -entry_point = 0x20_4008_0000 +# entry_point = 0x20_4008_0000 # The location of image: "memory" | "fs". # Load from file system. image_location = "fs" # The load address of the kernel image. -kernel_load_addr = 0x20_4008_0000 +# kernel_load_addr = 0x20_4008_0000 ## The file path of the kernel image. kernel_path = "/guest/linux/phytiumpi" ## The file path of the device tree blob (DTB). -dtb_load_addr = 0x20_4000_0000 +# dtb_load_addr = 0x20_4000_0000 #dtb_path = "/path/to/axvisor/configs/vms/linux-aarch64-e2000_smp1.dtb" # Memory regions with format (`base_paddr`, `size`, `flags`, `map_type`). # For `map_type`, 0 means `MAP_ALLOC`, 1 means `MAP_IDENTICAL`. diff --git a/configs/vms/linux-aarch64-qemu-smp1.toml b/configs/vms/linux-aarch64-qemu-smp1.toml index e849a54d..73b717b2 100644 --- a/configs/vms/linux-aarch64-qemu-smp1.toml +++ b/configs/vms/linux-aarch64-qemu-smp1.toml @@ -10,26 +10,17 @@ vm_type = 1 # The number of virtual CPUs. cpu_num = 1 # Guest vm physical cpu sets. -phys_cpu_ids = [0] +# 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 = "linux-6.6.62.bin" kernel_path = "tmp/Image" -# The load address of the kernel image. -kernel_load_addr = 0x8020_0000 -# The file path of the device tree blob (DTB). -#dtb_path = "tmp/linux-aarch64-qemu-smp1.dtb" -# The load address of the device tree blob (DTB). -dtb_load_addr = 0x8000_0000 # Memory regions with format (`base_paddr`, `size`, `flags`, `map_type`). # For `map_type`, 0 means `MAP_ALLOC`, 1 means `MAP_IDENTICAL`. diff --git a/configs/vms/linux-aarch64-rk3568-smp1.toml b/configs/vms/linux-aarch64-rk3568-smp1.toml index ed0cf421..47d8fe90 100644 --- a/configs/vms/linux-aarch64-rk3568-smp1.toml +++ b/configs/vms/linux-aarch64-rk3568-smp1.toml @@ -10,23 +10,23 @@ vm_type = 1 # The number of virtual CPUs. cpu_num = 1 # The physical CPU ids. -phys_cpu_ids = [0x00] +# phys_cpu_ids = [0x00] # # Vm kernel configs # [kernel] # The entry point of the kernel image. -entry_point = 0x8008_0000 +# entry_point = 0x8008_0000 # The location of image: "memory" | "fs". # Load from memory. image_location = "fs" # The load address of the kernel image. -kernel_load_addr = 0x8008_0000 +# kernel_load_addr = 0x8008_0000 ## The file path of the kernel image. kernel_path = "/userdata/rootfs_overlay/guest/linux/roc-rk3568-pc" ## The file path of the device tree blob (DTB). -dtb_load_addr = 0x8000_0000 +# dtb_load_addr = 0x8000_0000 #dtb_path = "/path/linux-aarch64-rk3568_smp1.dtb" # Memory regions with format (`base_paddr`, `size`, `flags`, `map_type`). # For `map_type`, 0 means `MAP_ALLOC`, 1 means `MAP_IDENTICAL`. diff --git a/configs/vms/nimbos-x86_64-qemu-smp1.toml b/configs/vms/nimbos-x86_64-qemu-smp1.toml index 3e12b78f..2f2ec72c 100644 --- a/configs/vms/nimbos-x86_64-qemu-smp1.toml +++ b/configs/vms/nimbos-x86_64-qemu-smp1.toml @@ -10,7 +10,7 @@ vm_type = 1 # The number of virtual CPUs. cpu_num = 1 # Guest vm physical cpu sets. -phys_cpu_sets = [1] +# cpu_ids = [0] # # Vm kernel configs diff --git a/doc/old.drawio b/doc/old.drawio new file mode 100644 index 00000000..d64d1caf --- /dev/null +++ b/doc/old.drawio @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/old.svg b/doc/old.svg new file mode 100644 index 00000000..b4f3f7ee --- /dev/null +++ b/doc/old.svg @@ -0,0 +1 @@ +
Want arch spec func
Want driver(irq hyper) with lot of funcs
Want arch spec func...
AxVisor
AxVisor
x86 hal func
x86 hal func
aarch64 hal func
aarch64 hal func
device tree
image load
cache flush
device tree...
Hal func
Hal func
axvisor-api
(crate-interface)
axvisor-api...
Want arch spec func
Want arch spec func
AxVm
AxVm
ArceOS
ArceOS
Axvcpu
Axvcpu
Axdevice
Axdevice
arm_vcpu
arm_vcpu
VCpuImpl
VCpuImpl
x86_vcpu
x86_vcpu
AxplatImpl
AxplatImpl
Text is not SVG - cannot display
\ No newline at end of file diff --git a/doc/refactor-vm.dio b/doc/refactor-vm.dio new file mode 100644 index 00000000..52e72b39 --- /dev/null +++ b/doc/refactor-vm.dio @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/refactor-vm.svg b/doc/refactor-vm.svg new file mode 100644 index 00000000..bd863050 --- /dev/null +++ b/doc/refactor-vm.svg @@ -0,0 +1 @@ +
<<running>>
<<running>>
VmRunData
VmRunData
fn stop()
fn suspend()
fn is_running(): bool
fn stop()...
addrspace: Mutex<>
v-device-manager: Mutex<>
statistic
addrspace: Mutex<>...
VmData
VmData
fn is_running(): bool
fn is_running(): bool
id: usize
name: String
status: RWLock<VmMachine>
id: usize...
<<enumeration>>
VmMachine
<<enumeration>>...
InitData
RunData
StoppedData
InitData...
InitData
InitData
fn start()
fn start()
config ...
vcpus: Vec<VCpu>
addrspace: Mutex<>
config ......
StoppedData
StoppedData
<<>>
<<>>
statistic ...
statistic ...
Weak
Weak
VCpu
VCpu
<<>>
<<>>
data: Weak<VmData>
cpu: arch-vcpu
common: axvcpu
data: Weak<VmData>...
Arc
Arc
Vm
Vm
fn start()
fn stop()
fn suspend()
fn start()fn stop()...
data: Arc<VmData>
image_loader
fdt
data: Arc<VmData>...
AxVisor
AxVisor
kernel
kernel
config
shell
vms: Container<Vm>
config...
Create
Create
Thread1
vcpu1
loop fn run()
Thread1...
move
move
Thread2
vcpu2
loop fn run()
Thread2...
move
move
Stop
(drop)
Stop...
...
...
move
move
Text is not SVG - cannot display
\ No newline at end of file diff --git a/doc/refactor.dio b/doc/refactor.dio new file mode 100644 index 00000000..de3bc28b --- /dev/null +++ b/doc/refactor.dio @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/refactor.md b/doc/refactor.md new file mode 100644 index 00000000..db5eb80e --- /dev/null +++ b/doc/refactor.md @@ -0,0 +1,49 @@ +# AxVisor 架构继续分析 + +## 原有架构 + +![old](old.svg) + +1. 虚拟机需要大量 Arch 特定的虚拟化支持,而 axhal 统一了各 arch 接口,抹除了 arch 差异,导致每需要一个功能,都需要在 axhal 中添加对应 arch 的实现,而其他 arch 则需要添加空实现,增加了维护成本。 + +2. 同样的,axvcpu 也统一了 各 arch 的 vcpu 接口,导致每增加一个 vcpu 功能,都需要在 axvcpu 中添加对应 arch 的实现,增加了维护成本。 + +3. 全部组件依赖 `axvisor-api`, 任何组件想要使用其他组件的功能,都需要通过 `axvisor-api` 进行间接调用,而 arch 开发中,会增加 arch 相关的特有函数,这又需要在 axvcpu 或 axhal 中增加其他 arch 空实现,并修改 `axvisor-api`,进一步增加了 `axvisor-api` 修改的可能性,导致几乎任何修改,都需要修改 `axvisor-api`、`axhal`, 而修改 `axvisor-api`, `axhal`,则会导致修改所有依赖库,进而引发修改所有 `axplat` 等等几乎所有组件。 + +## 重构后 + +![new](refactor.svg) + +1. 将所有架构相关(arch-specific)的实现收敛到 AxVm 模块:由 AxVm 统一负责虚拟机生命周期管理,并对外提供一致的虚拟机管理接口。AxVm 内部按架构选择对应的 VCPU 实现与地址空间实现,避免 arch-specific 代码分散在各个模块中。各架构的 VM 也不再通过 axvcpu 抹平差异,而是直接调用 arch_vcpu;由 arch_vcpu 以各自方式实现/适配 axvcpu 的能力,从而复用通用逻辑,并允许每个 ArchVm 以自己的方式组合 vcpu、vdevice、addrspace 等组件。 + +2. `axvisor-api` 改动:各模块不再直接依赖 `axvisor-api`,而是各自暴露最小必要接口,降低模块变更引发的全局联动修改。 + +3. 对 ArceOS 的依赖:行为上仅依赖 `std` 部分;虚拟化相关的特有能力通过 crate-interface 或直接依赖 HAL 层实现,尽量避免对 ArceOS 做侵入式修改。 + +## 深化设计axvm + +![axvm](refactor-vm.svg) + +### AxVm 内部设计 + +如图所示,`AxVm` 通过状态机管理虚拟机生命周期与资源分配。 + +1. **整体结构**: + + - `AxVisor` 内核维护 `vms` 容器,承载多个 `Vm` 实例。 + - `Vm` 持有静态配置(`image_loader`, `fdt`)以及共享的运行时数据 `VmData`(`Arc` 管理)。 + +2. **状态管理 (`VmData`)** + + - `VmData` 记录基础信息(ID、Name)与核心状态 `status: RWLock`。 + - `VmMachine` 为枚举,涵盖三种状态数据: + - **InitData**:配置、`vcpus: Vec`、`addrspace: Mutex<>`,启动前的准备态。 + - **VmRunData**:`start()` 时将 `addrspace` 等从 InitData `move` 进来,初始化 `v-device-manager: Mutex<>`,提供 `stop()`, `suspend()`, `is_running()` 运行时接口。 + - **StoppedData**:停止后保留统计信息 `statistic ...`。 + +3. **VCPU 与线程模型** + + - `VCpu` 同时包含架构相关 `arch-vcpu` 与通用 `axvcpu` 部分,持有指向 `VmData` 的 `Weak`,避免循环引用。 + - 进入运行态后,为每个 vcpu 创建线程(Thread1/Thread2...),执行 `loop fn run()`;退出时走 `Stop(drop)` 路径释放资源。 + +通过在状态间显式迁移资源(Move)并结合 `Arc/Weak` 引用控制,AxVm 将不同阶段的能力与数据隔离,降低跨模块耦合并确保生命周期安全。 diff --git a/doc/refactor.svg b/doc/refactor.svg new file mode 100644 index 00000000..f3f9e483 --- /dev/null +++ b/doc/refactor.svg @@ -0,0 +1 @@ +
AxVisor Kernel
AxVisor Kernel
config
config
vmcontainer
vmcontainer
shell
shell
ArceOS
ArceOS
AxVm
AxVm
VmX86
VmX86
VCpu
VCpu
Status
Machine
Status...
axaddrspace
axaddrspace
VmTrait
VmTrait
VmCommon
VmCommon
VmAarch64
VmAarch64
VCpu
VCpu
Status
Machine
Status...
axaddrspace
axaddrspace
image load
image load
device tree
device tree
VmRiscv64
VmRiscv64
VCpu
VCpu
Status
Machine
Status...
axaddrspace
axaddrspace
cache flush
cache flush
AxDevice
AxDevice
PlatImpl
(somehal)
PlatImpl...
x86_vcpu
x86_vcpu
arm_vcpu
arm_vcpu
riscv_vcpu
riscv_vcpu
vgic
...
vgic...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index ea99ff32..492c3aea 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -3,13 +3,14 @@ authors.workspace = true edition.workspace = true license.workspace = true name = "axvisor" +version.workspace = true [features] -ept-level-4 = ["axaddrspace/4-level-ept"] -fs = ["axstd/fs", "axruntime/fs"] -dyn-plat = ["axstd/myplat", "axstd/driver-dyn", "axruntime/driver-dyn"] +dyn-plat = ["axstd/myplat", "axstd/driver-dyn"] +fs = ["axstd/fs"] [dependencies] +anyhow = {version = "1.0", default-features = false} bitflags.workspace = true cfg-if.workspace = true cpumask.workspace = true @@ -20,52 +21,27 @@ lazyinit = "0.2" log = "0.4" spin = "0.9" timer_list = "0.1.0" +toml = {version = "0.9", default-features = false} # System dependent modules provided by ArceOS. -axstd = {workspace = true, features = [ - "alloc-level-1", - "paging", - "irq", - "multitask", - "smp", -]} +axstd.workspace = true # System dependent modules provided by ArceOS-Hypervisor. -axaddrspace.workspace = true axhvc.workspace = true -axruntime = {workspace = true, features = ["alloc", "irq", "paging", "smp", "multitask"]} -axvcpu.workspace = true axvm.workspace = true +axvmconfig = {workspace = true} # System independent crates provided by ArceOS, these crates could be imported by remote url. axerrno.workspace = true byte-unit = {version = "5", default-features = false, features = ["byte"]} -crate_interface.workspace = true extern-trait = "0.2" -fdt-parser = "0.4" memory_addr.workspace = true -page_table_entry = {version = "0.5", features = ["arm-el2"]} -page_table_multiarch = "0.5" -percpu = {version = "0.2", features = ["arm-el2"]} -rdif-intc = "0.12" -rdrive = "0.18" - -vm-fdt = {workspace = true, default-features = false, features = ["alloc"]} - -axdevice.workspace = true -axdevice_base = "0.1" -axvisor_api = "0.1" driver.workspace = true - -[target.'cfg(target_arch = "aarch64")'.dependencies] -aarch64-cpu-ext = "0.1" -arm-gic-driver = {version = "0.15.5", features = ["rdif"]} - [build-dependencies] +anyhow = "1.0" axconfig.workspace = true prettyplease = "0.2" quote = "1.0" syn = "2.0" toml.workspace = true -anyhow = "1.0" \ No newline at end of file diff --git a/kernel/src/hal/arch/aarch64/api.rs b/kernel/src/hal/arch/aarch64/api.rs deleted file mode 100644 index 5071517d..00000000 --- a/kernel/src/hal/arch/aarch64/api.rs +++ /dev/null @@ -1,75 +0,0 @@ -#[axvisor_api::api_mod_impl(axvisor_api::arch)] -mod arch_api_impl { - use core::panic; - - use axvisor_api::memory::virt_to_phys; - - extern fn hardware_inject_virtual_interrupt(irq: axvisor_api::vmm::InterruptVector) { - crate::hal::arch::inject_interrupt(irq as _); - } - - extern fn read_vgicd_typer() -> u32 { - let mut gic = rdrive::get_one::() - .expect("Failed to get GIC driver") - .lock() - .unwrap(); - if let Some(gic) = gic.typed_mut::() { - return gic.typer_raw(); - } - - if let Some(gic) = gic.typed_mut::() { - // Use the GICv3 driver to read the typer register - return gic.typer_raw(); - } - panic!("No GIC driver found"); - } - - extern 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::() - .expect("Failed to get GIC driver") - .lock() - .unwrap(); - if let Some(gic) = gic.typed_mut::() { - return gic.iidr_raw(); - } - - if let Some(gic) = gic.typed_mut::() { - // Use the GICv3 driver to read the typer register - return gic.iidr_raw(); - } - - panic!("No GIC driver found"); - } - - extern fn get_host_gicd_base() -> memory_addr::PhysAddr { - let mut gic = rdrive::get_one::() - .expect("Failed to get GIC driver") - .lock() - .unwrap(); - if let Some(gic) = gic.typed_mut::() { - let ptr: *mut u8 = gic.gicd_addr().as_ptr(); - return virt_to_phys((ptr as usize).into()); - } - - if let Some(gic) = gic.typed_mut::() { - let ptr: *mut u8 = gic.gicd_addr().as_ptr(); - // Use the GICv3 driver to read the typer register - return virt_to_phys((ptr as usize).into()); - } - panic!("No GIC driver found"); - } - - extern fn get_host_gicr_base() -> memory_addr::PhysAddr { - let mut gic = rdrive::get_one::() - .expect("Failed to get GIC driver") - .lock() - .unwrap(); - if let Some(gic) = gic.typed_mut::() { - let ptr: *mut u8 = gic.gicr_addr().as_ptr(); - return virt_to_phys((ptr as usize).into()); - } - panic!("No GICv3 driver found"); - } -} diff --git a/kernel/src/hal/arch/aarch64/cache.rs b/kernel/src/hal/arch/aarch64/cache.rs deleted file mode 100644 index 76ea085b..00000000 --- a/kernel/src/hal/arch/aarch64/cache.rs +++ /dev/null @@ -1,17 +0,0 @@ -use memory_addr::VirtAddr; - -use crate::hal::CacheOp; - -impl From for aarch64_cpu_ext::cache::CacheOp { - fn from(op: CacheOp) -> Self { - match op { - CacheOp::Clean => aarch64_cpu_ext::cache::CacheOp::Clean, - CacheOp::Invalidate => aarch64_cpu_ext::cache::CacheOp::Invalidate, - CacheOp::CleanAndInvalidate => aarch64_cpu_ext::cache::CacheOp::CleanAndInvalidate, - } - } -} - -pub fn dcache_range(op: CacheOp, addr: VirtAddr, size: usize) { - aarch64_cpu_ext::cache::dcache_range(op.into(), addr.as_usize(), size); -} diff --git a/kernel/src/hal/arch/aarch64/mod.rs b/kernel/src/hal/arch/aarch64/mod.rs deleted file mode 100644 index 3a312b5d..00000000 --- a/kernel/src/hal/arch/aarch64/mod.rs +++ /dev/null @@ -1,145 +0,0 @@ -use aarch64_cpu_ext::registers::*; - -mod api; -pub mod cache; - -pub fn inject_interrupt(irq: usize) { - debug!("Injecting virtual interrupt: {irq}"); - - let mut gic = rdrive::get_one::() - .expect("Failed to get GIC driver") - .lock() - .unwrap(); - if let Some(gic) = gic.typed_mut::() { - use arm_gic_driver::{ - IntId, - v2::{VirtualInterruptConfig, VirtualInterruptState}, - }; - - let gich = gic.hypervisor_interface().expect("Failed to get GICH"); - gich.enable(); - gich.set_virtual_interrupt( - 0, - VirtualInterruptConfig::software( - unsafe { IntId::raw(irq as _) }, - None, - 0, - VirtualInterruptState::Pending, - false, - true, - ), - ); - return; - } - - if let Some(_gic) = gic.typed_mut::() { - inject_interrupt_gic_v3(irq as _); - return; - } - - panic!("no gic driver found") -} - -pub fn inject_interrupt_gic_v3(vector: usize) { - use arm_gic_driver::v3::*; - - debug!("Injecting virtual interrupt: vector={vector}"); - let elsr = ICH_ELRSR_EL2.read(ICH_ELRSR_EL2::STATUS); - let lr_num = ICH_VTR_EL2.read(ICH_VTR_EL2::LISTREGS) as usize + 1; - - let mut free_lr = -1_isize; - - // First, check if this interrupt is already pending/active - for i in 0..lr_num { - // find a free list register - if (1 << i) & elsr > 0 { - if free_lr == -1 { - free_lr = i as isize; - } - continue; - } - let lr_val = ich_lr_el2_get(i); - - if lr_val.read(ICH_LR_EL2::VINTID) == vector as u64 - && lr_val.matches_any(&[ICH_LR_EL2::STATE::Pending, ICH_LR_EL2::STATE::Active]) - { - debug!("Virtual interrupt {vector} already pending/active in LR{i}, skipping"); - // If the interrupt is already pending or active, we can skip injecting it again. - // This is important to avoid duplicate injections. - continue; - } - } - - debug!("use free lr {free_lr} to inject irq {vector}"); - - if free_lr == -1 { - warn!("No free list register to inject IRQ {vector}, checking ICH_HCR_EL2"); - - // Try to find and reuse an inactive LR - for i in 0..lr_num { - let lr_val = ich_lr_el2_get(i); - if lr_val.matches_any(&[ICH_LR_EL2::STATE::Invalid]) { - debug!("Reusing inactive LR{i} for IRQ {vector}"); - free_lr = i as isize; - - break; - } - } - - if free_lr == -1 { - panic!("No free list register to inject IRQ {}", vector); - } - } - - ich_lr_el2_write( - free_lr as _, - ICH_LR_EL2::VINTID.val(vector as u64) + ICH_LR_EL2::STATE::Pending + ICH_LR_EL2::GROUP::SET, - ); - - // Ensure the virtual interrupt interface is enabled - let en = ICH_HCR_EL2.is_set(ICH_HCR_EL2::EN); - if !en { - // Check EN bit - warn!("Virtual interrupt interface not enabled, enabling now"); - ICH_HCR_EL2.modify(ICH_HCR_EL2::EN::SET); - } - - debug!("Virtual interrupt {vector} injected successfully in LR{free_lr}"); -} - -pub fn hardware_check() { - let pa_bits = match ID_AA64MMFR0_EL1.read_as_enum(ID_AA64MMFR0_EL1::PARange) { - Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_32) => 32, - Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_36) => 36, - Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_40) => 40, - Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_42) => 42, - Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_44) => 44, - Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_48) => 48, - Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_52) => 52, - _ => 32, - }; - - let level = match pa_bits { - 44.. => 4, - _ => 3, - }; - - #[cfg(feature = "ept-level-4")] - { - if level < 4 { - panic!( - "4-level EPT feature is enabled, but the hardware only supports {}-level page tables. Please disable the 4-level EPT feature or use hardware that supports 4-level page tables.", - level - ); - } - } - #[cfg(not(feature = "ept-level-4"))] - { - if level > 3 { - panic!( - "The hardware supports {}-level page tables, but the 4-level EPT feature is not enabled. Please enable the 4-level EPT feature to utilize the hardware's full capabilities.", - level - ); - } - } -} diff --git a/kernel/src/hal/arch/x86_64/cache.rs b/kernel/src/hal/arch/x86_64/cache.rs deleted file mode 100644 index 1a905a9d..00000000 --- a/kernel/src/hal/arch/x86_64/cache.rs +++ /dev/null @@ -1,5 +0,0 @@ -use memory_addr::VirtAddr; - -use crate::hal::CacheOp; - -pub fn dcache_range(_op: CacheOp, _addr: VirtAddr, _size: usize) {} diff --git a/kernel/src/hal/arch/x86_64/mod.rs b/kernel/src/hal/arch/x86_64/mod.rs deleted file mode 100644 index ab92f90b..00000000 --- a/kernel/src/hal/arch/x86_64/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod cache; - -pub fn hardware_check() {} -pub fn inject_interrupt(_vector: u8) {} diff --git a/kernel/src/hal/mod.rs b/kernel/src/hal/mod.rs deleted file mode 100644 index dc058723..00000000 --- a/kernel/src/hal/mod.rs +++ /dev/null @@ -1,275 +0,0 @@ -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 arceos::modules::axhal; -use axaddrspace::{AxMmHal, HostPhysAddr, HostVirtAddr}; -use axvcpu::AxVCpuHal; -use axvm::{AxVMHal, AxVMPerCpu}; - -#[cfg_attr(target_arch = "aarch64", path = "arch/aarch64/mod.rs")] -#[cfg_attr(target_arch = "x86_64", path = "arch/x86_64/mod.rs")] -pub mod arch; - -use crate::{hal::arch::hardware_check, task::AsVCpuTask, vmm}; - -#[allow(unused)] -#[repr(C)] -#[derive(Debug, Clone, Copy)] -pub enum CacheOp { - /// Write back to memory - Clean, - /// Invalidate cache - Invalidate, - /// Clean and invalidate - 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() - } - - fn dealloc_frame(paddr: HostPhysAddr) { - ::PagingHandler::dealloc_frame(paddr) - } - - #[inline] - fn phys_to_virt(paddr: HostPhysAddr) -> HostVirtAddr { - ::PagingHandler::phys_to_virt(paddr) - } - - fn virt_to_phys(vaddr: axaddrspace::HostVirtAddr) -> axaddrspace::HostPhysAddr { - std::os::arceos::modules::axhal::mem::virt_to_phys(vaddr) - } -} - -pub struct AxVCpuHalImpl; - -impl AxVCpuHal for AxVCpuHalImpl { - type MmHal = AxMmHalImpl; - - fn irq_hanlder() { - axhal::irq::irq_handler(0); - } -} - -#[percpu::def_percpu] -static mut AXVM_PER_CPU: AxVMPerCpu = AxVMPerCpu::::new_uninit(); - -/// Init hardware virtualization support in each core. -pub(crate) fn enable_virtualization() { - use core::sync::atomic::AtomicUsize; - use core::sync::atomic::Ordering; - - use std::thread; - - use arceos::api::task::{AxCpuMask, ax_set_current_affinity}; - - static CORES: AtomicUsize = AtomicUsize::new(0); - - info!("Enabling hardware virtualization support on all cores..."); - - hardware_check(); - - let cpu_count = axruntime::cpu_count(); - - for cpu_id in 0..cpu_count { - thread::spawn(move || { - info!("Core {cpu_id} is initializing hardware virtualization support..."); - // Initialize cpu affinity here. - assert!( - ax_set_current_affinity(AxCpuMask::one_shot(cpu_id)).is_ok(), - "Initialize CPU affinity failed!" - ); - - info!("Enabling hardware virtualization support on core {cpu_id}"); - - vmm::init_timer_percpu(); - - let percpu = unsafe { AXVM_PER_CPU.current_ref_mut_raw() }; - percpu - .init(this_cpu_id()) - .expect("Failed to initialize percpu state"); - percpu - .hardware_enable() - .expect("Failed to enable virtualization"); - - info!("Hardware virtualization support enabled on core {cpu_id}"); - - let _ = CORES.fetch_add(1, Ordering::Release); - }); - } - - info!("Waiting for all cores to enable hardware virtualization..."); - - // Wait for all cores to enable virtualization. - while CORES.load(Ordering::Acquire) != cpu_count { - // Use `yield_now` instead of `core::hint::spin_loop` to avoid deadlock. - thread::yield_now(); - } - - 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 - axruntime::cpu_count() - } -} diff --git a/kernel/src/main.rs b/kernel/src/main.rs index cd4f078e..a6396ec1 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -9,12 +9,11 @@ extern crate alloc; extern crate axstd as std; -extern crate axruntime; +// extern crate axruntime; extern crate driver; -mod hal; mod logo; -mod shell; +// mod shell; mod task; mod vmm; @@ -23,13 +22,13 @@ fn main() { logo::print_logo(); info!("Starting virtualization..."); - info!("Hardware support: {:?}", axvm::has_hardware_support()); - hal::enable_virtualization(); + // info!("Hardware support: {:?}", axvm::has_hardware_support()); vmm::init(); - vmm::start(); + vmm::start_preconfigured_vms().unwrap(); info!("[OK] Default guest initialized"); - - shell::console_init(); + vmm::wait_for_all_vms_exit(); + info!("All guest VMs exited."); + // shell::console_init(); } diff --git a/kernel/src/task.rs b/kernel/src/task.rs index 2e06d8ba..653e29c8 100644 --- a/kernel/src/task.rs +++ b/kernel/src/task.rs @@ -1,45 +1,9 @@ -use alloc::sync::{Arc, Weak}; -use std::os::arceos::modules::axtask::{TaskExt, TaskInner}; - -use crate::vmm::{VCpuRef, VM, VMRef}; +use std::os::arceos::modules::axtask::TaskExt; /// Task extended data for the hypervisor. -pub struct VCpuTask { - /// The VM (Weak reference to avoid keeping VM alive). - pub vm: Weak, - /// The virtual CPU. - pub vcpu: VCpuRef, -} - -impl VCpuTask { - /// Create a new [`HvTask`]. - pub fn new(vm: &VMRef, vcpu: VCpuRef) -> Self { - Self { - vm: Arc::downgrade(vm), - vcpu, - } - } +pub struct VCpuTask {} - /// Get a strong reference to the VM if it's still alive. - /// Returns None if the VM has been dropped. - pub fn vm(&self) -> VMRef { - self.vm.upgrade().expect("VM has been dropped") - } -} +impl VCpuTask {} #[extern_trait::extern_trait] unsafe impl TaskExt for VCpuTask {} - -pub trait AsVCpuTask { - fn as_vcpu_task(&self) -> &VCpuTask; -} - -impl AsVCpuTask for TaskInner { - fn as_vcpu_task(&self) -> &VCpuTask { - unsafe { - self.task_ext() - .expect("Not a VCpuTask") - .downcast_ref::() - } - } -} diff --git a/kernel/src/vmm/config.rs b/kernel/src/vmm/config.rs index 99994387..c4673acd 100644 --- a/kernel/src/vmm/config.rs +++ b/kernel/src/vmm/config.rs @@ -1,17 +1,76 @@ -use axaddrspace::GuestPhysAddr; -use axerrno::AxResult; +use std::string::ToString; + +use alloc::vec::Vec; use axvm::{ - VMMemoryRegion, - config::{AxVMConfig, AxVMCrateConfig, VmMemMappingType}, + AxVMConfig, CpuId, + config::{AxVMCrateConfig, CpuNumType, MemoryKind}, }; -use core::alloc::Layout; -use crate::vmm::{VM, images::ImageLoader, vm_list::push_vm}; +pub fn get_guest_prelude_vmconfig() -> anyhow::Result> { + let mut vm_configs = Vec::new(); + // First try to get configs from filesystem if fs feature is enabled + let mut gvm_raw_configs = config::filesystem_vm_configs(); + + // If no filesystem configs found, fallback to static configs + if gvm_raw_configs.is_empty() { + let static_configs = config::static_vm_configs(); + if static_configs.is_empty() { + info!("Static VM configs are empty."); + info!("Now axvisor will entry the shell..."); + } else { + info!("Using static VM configs."); + } + + gvm_raw_configs.extend(static_configs.into_iter().map(|s| s.to_string())); + } + for raw in gvm_raw_configs { + let vm_config: AxVMCrateConfig = toml::from_str(&raw)?; + vm_configs.push(vm_config); + } + + Ok(vm_configs) +} + +pub fn build_vmconfig(cfg: AxVMCrateConfig) -> anyhow::Result { + let mut cpu_num = CpuNumType::Alloc(1); + if let Some(num) = cfg.base.cpu_num { + cpu_num = CpuNumType::Alloc(num); + } + if let Some(ref ids) = cfg.base.cpu_ids { + cpu_num = CpuNumType::Fixed(ids.iter().map(|&id| CpuId::new(id)).collect()); + } + + let image_config = super::images::load_images(&cfg)?; + + let mut memory_regions = vec![]; -#[cfg(target_arch = "aarch64")] -use crate::vmm::fdt::*; + for region in &cfg.kernel.memory_regions { + let mem_region = match region.map_type { + axvmconfig::VmMemMappingType::MapAlloc => MemoryKind::Vmem { + gpa: region.gpa.into(), + size: region.size, + }, + axvmconfig::VmMemMappingType::MapIdentical => { + MemoryKind::Identical { size: region.size } + } + axvmconfig::VmMemMappingType::MapReserved => MemoryKind::Reserved { + hpa: region.gpa.into(), + size: region.size, + }, + }; -use alloc::sync::Arc; + memory_regions.push(mem_region); + } + + Ok(AxVMConfig { + id: cfg.base.id, + name: cfg.base.name, + cpu_num, + image_config, + memory_regions, + interrupt_mode: cfg.devices.interrupt_mode, + }) +} #[allow(clippy::module_inception, dead_code)] pub mod config { @@ -37,11 +96,11 @@ pub mod config { let entries = match fs::read_dir(config_dir) { Ok(entries) => { - info!("Find dir: {}", config_dir); + info!("Find dir: {config_dir}"); entries } Err(_e) => { - info!("NOT find dir: {} in filesystem", config_dir); + info!("NOT find dir: {config_dir} in filesystem"); return configs; } }; @@ -50,7 +109,7 @@ pub mod config { let path = entry.path(); // Check if the file has a .toml extension let path_str = path.as_str(); - debug!("Considering file: {}", path_str); + debug!("Considering file: {path_str}"); if path_str.ends_with(".toml") { let toml_file = fs::File::open(path_str).expect("Failed to open file"); let file_size = toml_file @@ -58,10 +117,10 @@ pub mod config { .expect("Failed to get file metadata") .len() as usize; - info!("File {} size: {}", path_str, file_size); + info!("File {path_str} size: {file_size}"); if file_size == 0 { - warn!("File {} is empty", path_str); + warn!("File {path_str} is empty"); continue; } @@ -84,18 +143,16 @@ pub mod config { { configs.push(content); info!( - "TOML config: {} is valid, start the virtual machine directly now. ", - path_str + "TOML config: {path_str} is valid, start the virtual machine directly now. " ); } else { warn!( - "File {} does not appear to contain valid VM config structure", - path_str + "File {path_str} does not appear to contain valid VM config structure" ); } } Err(e) => { - error!("Failed to read file {}: {:?}", path_str, e); + error!("Failed to read file {path_str}: {e:?}"); } } } @@ -112,146 +169,3 @@ pub mod config { include!(concat!(env!("OUT_DIR"), "/vm_configs.rs")); } - -pub fn get_vm_dtb_arc(_vm_cfg: &AxVMConfig) -> Option> { - #[cfg(target_arch = "aarch64")] - { - let cache_lock = dtb_cache().lock(); - if let Some(dtb) = cache_lock.get(&_vm_cfg.id()) { - return Some(Arc::from(dtb.as_slice())); - } - } - None -} - -pub fn init_guest_vms() { - // Initialize the DTB cache in the fdt module - #[cfg(target_arch = "aarch64")] - { - init_dtb_cache(); - } - - // First try to get configs from filesystem if fs feature is enabled - let mut gvm_raw_configs = config::filesystem_vm_configs(); - - // If no filesystem configs found, fallback to static configs - if gvm_raw_configs.is_empty() { - let static_configs = config::static_vm_configs(); - if static_configs.is_empty() { - info!("Static VM configs are empty."); - info!("Now axvisor will entry the shell..."); - } else { - info!("Using static VM configs."); - } - // Convert static configs to String type - gvm_raw_configs.extend(static_configs.into_iter().map(|s| s.into())); - } - - for raw_cfg_str in gvm_raw_configs { - debug!("Initializing guest VM with config: {:#?}", raw_cfg_str); - if let Err(e) = init_guest_vm(&raw_cfg_str) { - error!("Failed to initialize guest VM: {e:?}"); - } - } -} - -pub fn init_guest_vm(raw_cfg: &str) -> AxResult { - let vm_create_config = - AxVMCrateConfig::from_toml(raw_cfg).expect("Failed to resolve VM config"); - - if let Some(linux) = super::images::get_image_header(&vm_create_config) { - debug!( - "VM[{}] Linux header: {:#x?}", - vm_create_config.base.id, linux - ); - } - - #[cfg(target_arch = "aarch64")] - let mut vm_config = AxVMConfig::from(vm_create_config.clone()); - - #[cfg(not(target_arch = "aarch64"))] - let vm_config = AxVMConfig::from(vm_create_config.clone()); - - // Handle FDT-related operations for aarch64 - #[cfg(target_arch = "aarch64")] - handle_fdt_operations(&mut vm_config, &vm_create_config); - - // info!("after parse_vm_interrupt, crate VM[{}] with config: {:#?}", vm_config.id(), vm_config); - info!("Creating VM[{}] {:?}", vm_config.id(), vm_config.name()); - - // Create VM. - let vm = VM::new(vm_config).expect("Failed to create VM"); - let vm_id = vm.id(); - push_vm(vm.clone()); - - vm_alloc_memorys(&vm_create_config, &vm); - - let main_mem = vm - .memory_regions() - .first() - .cloned() - .expect("VM must have at least one memory region"); - - config_guest_address(&vm, &main_mem); - - // Load corresponding images for VM. - info!("VM[{}] created success, loading images...", vm.id()); - - let mut loader = ImageLoader::new(main_mem, vm_create_config, vm.clone()); - loader.load().expect("Failed to load VM images"); - - if let Err(e) = vm.init() { - panic!("VM[{}] setup failed: {:?}", vm.id(), e); - } - - vm.set_vm_status(axvm::VMStatus::Loaded); - - Ok(vm_id) -} - -fn config_guest_address(vm: &VM, main_memory: &VMMemoryRegion) { - const MB: usize = 1024 * 1024; - vm.with_config(|config| { - if main_memory.is_identical() { - debug!( - "Adjusting kernel load address from {:#x} to {:#x}", - config.image_config.kernel_load_gpa, main_memory.gpa - ); - let mut kernel_addr = main_memory.gpa; - if config.image_config.bios_load_gpa.is_some() { - kernel_addr += MB * 2; // leave 2MB for BIOS - } - - config.image_config.kernel_load_gpa = kernel_addr; - config.cpu_config.bsp_entry = kernel_addr; - config.cpu_config.ap_entry = kernel_addr; - } - }); -} - -fn vm_alloc_memorys(vm_create_config: &AxVMCrateConfig, vm: &VM) { - const MB: usize = 1024 * 1024; - const ALIGN: usize = 2 * MB; - - for memory in &vm_create_config.kernel.memory_regions { - match memory.map_type { - VmMemMappingType::MapAlloc => { - vm.alloc_memory_region( - Layout::from_size_align(memory.size, ALIGN).unwrap(), - Some(GuestPhysAddr::from(memory.gpa)), - ) - .expect("Failed to allocate memory region for VM"); - } - VmMemMappingType::MapIdentical => { - vm.alloc_memory_region(Layout::from_size_align(memory.size, ALIGN).unwrap(), None) - .expect("Failed to allocate memory region for VM"); - } - VmMemMappingType::MapReserved => { - info!("VM[{}] map same region: {:#x?}", vm.id(), memory); - let layout = Layout::from_size_align(memory.size, ALIGN).unwrap(); - vm.map_reserved_memory_region(layout, Some(GuestPhysAddr::from(memory.gpa))) - .expect("Failed to map memory region for VM"); - } - } - } -} diff --git a/kernel/src/vmm/images/mod.rs b/kernel/src/vmm/images/mod.rs index 23addd73..98e10547 100644 --- a/kernel/src/vmm/images/mod.rs +++ b/kernel/src/vmm/images/mod.rs @@ -1,309 +1,78 @@ -use axaddrspace::GuestPhysAddr; -use axerrno::AxResult; +use axvm::GuestPhysAddr; -use axvm::VMMemoryRegion; -use axvm::config::AxVMCrateConfig; -use byte_unit::Byte; +// use axvm::VMMemoryRegion; +use axvm::config::{AxVMCrateConfig, VMImageConfig, VMImagesConfig}; +use axvmconfig::ImageLocation; -use crate::hal::CacheOp; -use crate::vmm::VMRef; -use crate::vmm::config::{config, get_vm_dtb_arc}; +use crate::vmm::config::config::MemoryImage; mod linux; -pub fn get_image_header(config: &AxVMCrateConfig) -> Option { - match config.kernel.image_location.as_deref() { - Some("memory") => with_memory_image(config, linux::Header::parse), - #[cfg(feature = "fs")] - Some("fs") => { - let read_size = linux::Header::hdr_size(); - let data = fs::kernal_read(config, read_size).ok()?; - linux::Header::parse(&data) - } - _ => unimplemented!( - "Check your \"image_location\" in config.toml, \"memory\" and \"fs\" are supported,\n NOTE: \"fs\" feature should be enabled if you want to load images from filesystem. (APP_FEATURES=fs)" - ), - } -} - -fn with_memory_image(config: &AxVMCrateConfig, func: F) -> R -where - F: FnOnce(&[u8]) -> R, -{ - let vm_imags = config::get_memory_images() - .iter() - .find(|&v| v.id == config.base.id) - .expect("VM images is missed, Perhaps add `VM_CONFIGS=PATH/CONFIGS/FILE` command."); - - func(vm_imags.kernel) -} - -pub struct ImageLoader { - main_memory: VMMemoryRegion, - vm: VMRef, - config: AxVMCrateConfig, - kernel_load_gpa: GuestPhysAddr, - bios_load_gpa: Option, - dtb_load_gpa: Option, - ramdisk_load_gpa: Option, -} - -impl ImageLoader { - pub fn new(main_memory: VMMemoryRegion, config: AxVMCrateConfig, vm: VMRef) -> Self { - Self { - main_memory, - vm, - config, - kernel_load_gpa: GuestPhysAddr::default(), - bios_load_gpa: None, - dtb_load_gpa: None, - ramdisk_load_gpa: None, - } - } - - pub fn load(&mut self) -> AxResult { - info!( - "Loading VM[{}] images into memory region: gpa={:#x}, hva={:#x}, size={:#}", - self.vm.id(), - self.main_memory.gpa, - self.main_memory.hva, - Byte::from(self.main_memory.size()) - ); - - self.vm.with_config(|config| { - self.kernel_load_gpa = config.image_config.kernel_load_gpa; - self.dtb_load_gpa = config.image_config.dtb_load_gpa; - self.bios_load_gpa = config.image_config.bios_load_gpa; - self.ramdisk_load_gpa = config.image_config.ramdisk_load_gpa; - }); - - match self.config.kernel.image_location.as_deref() { - Some("memory") => self.load_vm_images_from_memory(), +pub fn load_images(config: &AxVMCrateConfig) -> anyhow::Result { + match config.kernel.image_location { + None | Some(ImageLocation::Fs) => { #[cfg(feature = "fs")] - Some("fs") => fs::load_vm_images_from_filesystem(self), - _ => unimplemented!( - "Check your \"image_location\" in config.toml, \"memory\" and \"fs\" are supported,\n NOTE: \"fs\" feature should be enabled if you want to load images from filesystem. (APP_FEATURES=fs)" - ), - } - } - - /// Load VM images from memory - /// into the guest VM's memory space based on the VM configuration. - fn load_vm_images_from_memory(&self) -> AxResult { - info!("Loading VM[{}] images from memory", self.config.base.id); - - let vm_imags = config::get_memory_images() - .iter() - .find(|&v| v.id == self.config.base.id) - .expect("VM images is missed, Perhaps add `VM_CONFIGS=PATH/CONFIGS/FILE` command."); - - load_vm_image_from_memory(vm_imags.kernel, self.kernel_load_gpa, self.vm.clone()) - .expect("Failed to load VM images"); - // Load DTB image - let vm_config = axvm::config::AxVMConfig::from(self.config.clone()); - - if let Some(dtb_arc) = get_vm_dtb_arc(&vm_config) { - let _dtb_slice: &[u8] = &dtb_arc; - #[cfg(target_arch = "aarch64")] - crate::vmm::fdt::update_fdt( - core::ptr::NonNull::new(_dtb_slice.as_ptr() as *mut u8).unwrap(), - _dtb_slice.len(), - self.vm.clone(), - ); - } else { - info!("dtb_load_gpa not provided"); - } - - // Load BIOS image - if let Some(buffer) = vm_imags.bios { - load_vm_image_from_memory(buffer, self.bios_load_gpa.unwrap(), self.vm.clone()) - .expect("Failed to load BIOS images"); + { + fs::load_images_fs(config) + } + #[cfg(not(feature = "fs"))] + { + Err(anyhow::anyhow!( + "Filesystem feature is not enabled, cannot load images from fs" + )) + } } - - // Load Ramdisk image - if let Some(buffer) = vm_imags.ramdisk { - load_vm_image_from_memory(buffer, self.ramdisk_load_gpa.unwrap(), self.vm.clone()) - .expect("Failed to load Ramdisk images"); - }; - - Ok(()) + Some(ImageLocation::Memory) => load_images_mem(config), } } -pub fn load_vm_image_from_memory( - image_buffer: &[u8], - load_addr: GuestPhysAddr, - vm: VMRef, -) -> AxResult { - let mut buffer_pos = 0; - - let image_size = image_buffer.len(); - - debug!( - "loading VM image from memory {:?} {}", - load_addr, - image_buffer.len() - ); - - let image_load_regions = vm.get_image_load_region(load_addr, image_size)?; - - for region in image_load_regions { - let region_len = region.len(); - let bytes_to_write = region_len.min(image_size - buffer_pos); - - // copy data from memory - unsafe { - core::ptr::copy_nonoverlapping( - image_buffer[buffer_pos..].as_ptr(), - region.as_mut_ptr().cast(), - bytes_to_write, - ); - } - - crate::hal::arch::cache::dcache_range( - CacheOp::Clean, - (region.as_ptr() as usize).into(), - region_len, - ); - - // Update the position of the buffer. - buffer_pos += bytes_to_write; +fn load_images_mem(config: &AxVMCrateConfig) -> anyhow::Result { + let memory_image = memory_image(config); + + // Load kernel image + let kernel = VMImageConfig { + gpa: config.kernel.kernel_load_addr.map(GuestPhysAddr::from), + data: memory_image.kernel.to_vec(), + }; + + let bios = memory_image.bios.map(|bios| VMImageConfig { + gpa: config.kernel.bios_load_addr.map(GuestPhysAddr::from), + data: bios.to_vec(), + }); + + let dtb = memory_image.dtb.map(|dtb| VMImageConfig { + gpa: config.kernel.dtb_load_addr.map(GuestPhysAddr::from), + data: dtb.to_vec(), + }); + + let ramdisk = memory_image.ramdisk.map(|ramdisk| VMImageConfig { + gpa: config.kernel.ramdisk_load_addr.map(GuestPhysAddr::from), + data: ramdisk.to_vec(), + }); + + Ok(VMImagesConfig { + kernel, + bios, + dtb, + ramdisk, + }) +} - // If the buffer is fully written, exit the loop. - if buffer_pos >= image_size { - debug!("copy size: {bytes_to_write}"); - break; +fn memory_image(config: &AxVMCrateConfig) -> &'static MemoryImage { + let images = super::config::config::get_memory_images(); + for img in images.iter() { + if img.id == config.base.id { + return img; } } - - Ok(()) + panic!("Cannot find memory image for VM id {}", config.base.id); } #[cfg(feature = "fs")] pub mod fs { use super::*; - use crate::hal::CacheOp; - use axerrno::{AxResult, ax_err, ax_err_type}; - use std::{fs::File, vec::Vec}; - - pub fn kernal_read(config: &AxVMCrateConfig, read_size: usize) -> AxResult> { - use std::fs::File; - use std::io::Read; - let file_name = &config.kernel.kernel_path; - - let mut file = File::open(file_name).map_err(|err| { - ax_err_type!( - NotFound, - format!( - "Failed to open {}, err {:?}, please check your disk.img", - file_name, err - ) - ) - })?; - - let mut buffer = vec![0u8; read_size]; - - file.read_exact(&mut buffer).map_err(|err| { - ax_err_type!( - NotFound, - format!( - "Failed to read {}, err {:?}, please check your disk.img", - file_name, err - ) - ) - })?; - - Ok(buffer) - } - - /// Loads the VM image files from the filesystem - /// into the guest VM's memory space based on the VM configuration. - pub(crate) fn load_vm_images_from_filesystem(loader: &ImageLoader) -> AxResult { - info!("Loading VM images from filesystem"); - // Load kernel image. - load_vm_image( - &loader.config.kernel.kernel_path, - loader.kernel_load_gpa, - loader.vm.clone(), - )?; - // Load BIOS image if needed. - if let Some(bios_path) = &loader.config.kernel.bios_path { - if let Some(bios_load_addr) = loader.bios_load_gpa { - load_vm_image(bios_path, bios_load_addr, loader.vm.clone())?; - } else { - return ax_err!(NotFound, "BIOS load addr is missed"); - } - }; - // Load Ramdisk image if needed. - if let Some(ramdisk_path) = &loader.config.kernel.ramdisk_path { - if let Some(ramdisk_load_addr) = loader.ramdisk_load_gpa { - load_vm_image(ramdisk_path, ramdisk_load_addr, loader.vm.clone())?; - } else { - return ax_err!(NotFound, "Ramdisk load addr is missed"); - } - }; - // Load DTB image if needed. - let vm_config = axvm::config::AxVMConfig::from(loader.config.clone()); - if let Some(dtb_arc) = get_vm_dtb_arc(&vm_config) { - let _dtb_slice: &[u8] = &dtb_arc; - #[cfg(target_arch = "aarch64")] - crate::vmm::fdt::update_fdt( - core::ptr::NonNull::new(_dtb_slice.as_ptr() as *mut u8).unwrap(), - _dtb_slice.len(), - loader.vm.clone(), - ); - } - - Ok(()) - } - - fn load_vm_image(image_path: &str, image_load_gpa: GuestPhysAddr, vm: VMRef) -> AxResult { - use std::io::{BufReader, Read}; - let (image_file, image_size) = open_image_file(image_path)?; - - let image_load_regions = vm.get_image_load_region(image_load_gpa, image_size)?; - let mut file = BufReader::new(image_file); - - for buffer in image_load_regions { - file.read_exact(buffer).map_err(|err| { - ax_err_type!( - Io, - format!("Failed in reading from file {}, err {:?}", image_path, err) - ) - })?; - - crate::hal::arch::cache::dcache_range( - CacheOp::Clean, - (buffer.as_ptr() as usize).into(), - buffer.len(), - ); - } - - Ok(()) - } - pub fn open_image_file(file_name: &str) -> AxResult<(File, usize)> { - let file = File::open(file_name).map_err(|err| { - ax_err_type!( - NotFound, - format!( - "Failed to open {}, err {:?}, please check your disk.img", - file_name, err - ) - ) - })?; - let file_size = file - .metadata() - .map_err(|err| { - ax_err_type!( - Io, - format!( - "Failed to get metadate of file {}, err {:?}", - file_name, err - ) - ) - })? - .size() as usize; - Ok((file, file_size)) + pub fn load_images_fs(_config: &AxVMCrateConfig) -> anyhow::Result { + todo!() } } diff --git a/kernel/src/vmm/mod.rs b/kernel/src/vmm/mod.rs index 5beecbce..95801907 100644 --- a/kernel/src/vmm/mod.rs +++ b/kernel/src/vmm/mod.rs @@ -1,141 +1,41 @@ -mod hvc; -mod ivc; +// mod hvc; +// mod ivc; pub mod config; pub mod images; -pub mod timer; -pub mod vcpus; +// pub mod timer; pub mod vm_list; -#[cfg(target_arch = "aarch64")] -pub mod fdt; - -use core::sync::atomic::{AtomicUsize, Ordering}; -use std::os::arceos::{ - api::task::{self, AxWaitQueueHandle}, - modules::axtask, -}; - -use axerrno::{AxResult, ax_err_type}; - -use crate::{ - hal::{AxVCpuHalImpl, AxVMHalImpl}, - task::AsVCpuTask, -}; -pub use timer::init_percpu as init_timer_percpu; - -/// The instantiated VM type. -pub type VM = axvm::AxVM; -/// The instantiated VM ref type (by `Arc`). -pub type VMRef = axvm::AxVMRef; -/// The instantiated VCpu ref type (by `Arc`). -pub type VCpuRef = axvm::AxVCpuRef; - -static VMM: AxWaitQueueHandle = AxWaitQueueHandle::new(); - -/// The number of running VMs. This is used to determine when to exit the VMM. -static RUNNING_VM_COUNT: AtomicUsize = AtomicUsize::new(0); +use axvm::AxVMConfig; /// Initialize the VMM. /// /// This function creates the VM structures and sets up the primary VCpu for each VM. pub fn init() { info!("Initializing VMM..."); - // Initialize guest VM according to config file. - config::init_guest_vms(); - - // Setup vcpus, spawn axtask for primary VCpu. - info!("Setting up vcpus..."); - for vm in vm_list::get_vm_list() { - vcpus::setup_vm_primary_vcpu(vm); - } + axvm::enable_viretualization().unwrap(); } -/// Start the VMM. -pub fn start() { - info!("VMM starting, booting VMs..."); - for vm in vm_list::get_vm_list() { - match vm.boot() { - Ok(_) => { - vcpus::notify_primary_vcpu(vm.id()); - RUNNING_VM_COUNT.fetch_add(1, Ordering::Release); - info!("VM[{}] boot success", vm.id()) - } - Err(err) => warn!("VM[{}] boot failed, error {:?}", vm.id(), err), - } +pub fn start_preconfigured_vms() -> anyhow::Result<()> { + // Initialize guest VM according to config file. + for config in config::get_guest_prelude_vmconfig()? { + let vm_config = config::build_vmconfig(config)?; + start_vm(vm_config)?; } - - // Do not exit until all VMs are stopped. - task::ax_wait_queue_wait_until( - &VMM, - || { - let vm_count = RUNNING_VM_COUNT.load(Ordering::Acquire); - info!("a VM exited, current running VM count: {vm_count}"); - vm_count == 0 - }, - None, - ); + Ok(()) } -#[allow(unused_imports)] -pub use vcpus::with_vcpu_task; - -/// Run a closure with the specified VM. -pub fn with_vm(vm_id: usize, f: impl FnOnce(VMRef) -> T) -> Option { - let vm = vm_list::get_vm_by_id(vm_id)?; - Some(f(vm)) +pub fn start_vm(config: AxVMConfig) -> anyhow::Result<()> { + debug!("Starting guest VM `{}`", config.name()); + let vm = axvm::Vm::new(config)?; + let vm = vm_list::push_vm(vm); + vm.boot()?; + Ok(()) } -/// Run a closure with the specified VM and vCPU. -pub fn with_vm_and_vcpu( - vm_id: usize, - vcpu_id: usize, - f: impl FnOnce(VMRef, VCpuRef) -> T, -) -> Option { - let vm = vm_list::get_vm_by_id(vm_id)?; - let vcpu = vm.vcpu(vcpu_id)?; - - Some(f(vm, vcpu)) -} - -/// Run a closure with the specified VM and vCPU, with the guarantee that the closure will be -/// executed on the physical CPU where the vCPU is running, waiting, or queueing. -/// -/// TODO: It seems necessary to disable scheduling when running the closure. -pub fn with_vm_and_vcpu_on_pcpu( - vm_id: usize, - vcpu_id: usize, - f: impl FnOnce(VMRef, VCpuRef) + 'static, -) -> AxResult { - // Disables preemption and IRQs to prevent the current task from being preempted or re-scheduled. - let guard = kernel_guard::NoPreemptIrqSave::new(); - - let current_vm = axtask::current().as_vcpu_task().vm().id(); - let current_vcpu = axtask::current().as_vcpu_task().vcpu.id(); - - // The target vCPU is the current task, execute the closure directly. - if current_vm == vm_id && current_vcpu == vcpu_id { - with_vm_and_vcpu(vm_id, vcpu_id, f).unwrap(); // unwrap is safe here - return Ok(()); +pub fn wait_for_all_vms_exit() { + let ls = vm_list::get_vm_list(); + for vm in ls.iter() { + vm.wait().unwrap(); } - - // The target vCPU is not the current task, send an IPI to the target physical CPU. - drop(guard); - - let _pcpu_id = vcpus::with_vcpu_task(vm_id, vcpu_id, |task| task.cpu_id()) - .ok_or_else(|| ax_err_type!(NotFound))?; - - unimplemented!(); - // use std::os::arceos::modules::axipi; - // Ok(axipi::send_ipi_event_to_one(pcpu_id as usize, move || { - // with_vm_and_vcpu_on_pcpu(vm_id, vcpu_id, f); - // })) -} - -pub fn add_running_vm_count(count: usize) { - RUNNING_VM_COUNT.fetch_add(count, Ordering::Release); -} - -pub fn sub_running_vm_count(count: usize) { - RUNNING_VM_COUNT.fetch_sub(count, Ordering::Release); } diff --git a/kernel/src/vmm/timer.rs b/kernel/src/vmm/timer.rs deleted file mode 100644 index 00a6ff9d..00000000 --- a/kernel/src/vmm/timer.rs +++ /dev/null @@ -1,113 +0,0 @@ -use core::sync::atomic::AtomicUsize; -use core::sync::atomic::Ordering; - -use std::os::arceos::modules::axhal; - -use alloc::boxed::Box; -use kspin::SpinNoIrq; -use lazyinit::LazyInit; -use timer_list::{TimeValue, TimerEvent, TimerList}; - -static TOKEN: AtomicUsize = AtomicUsize::new(0); -// const PERIODIC_INTERVAL_NANOS: u64 = axhal::time::NANOS_PER_SEC / axconfig::TICKS_PER_SEC as u64; - -/// Represents a timer event in the virtual machine monitor (VMM). -/// -/// This struct holds a unique token for the timer and a callback function -/// that will be executed when the timer expires. -pub struct VmmTimerEvent { - // Unique identifier for the timer event - token: usize, - // Callback function to be executed when the timer expires - timer_callback: Box, -} - -impl VmmTimerEvent { - fn new(token: usize, f: F) -> Self - where - F: FnOnce(TimeValue) + Send + 'static, - { - Self { - token, - timer_callback: Box::new(f), - } - } -} - -impl TimerEvent for VmmTimerEvent { - fn callback(self, now: TimeValue) { - (self.timer_callback)(now) - } -} - -#[percpu::def_percpu] -static TIMER_LIST: LazyInit>> = LazyInit::new(); - -/// Registers a new timer that will execute at the specified deadline -/// -/// # Arguments -/// - `deadline`: The absolute time in nanoseconds when the timer should trigger -/// - `handler`: The callback function to execute when the timer expires -/// -/// # Returns -/// A unique token that can be used to cancel this timer later -pub fn register_timer(deadline: u64, handler: F) -> usize -where - F: FnOnce(TimeValue) + Send + 'static, -{ - trace!("Registering timer..."); - trace!( - "deadline is {:#?} = {:#?}", - deadline, - TimeValue::from_nanos(deadline) - ); - let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; - let mut timers = timer_list.lock(); - let token = TOKEN.fetch_add(1, Ordering::Release); - let event = VmmTimerEvent::new(token, handler); - timers.set(TimeValue::from_nanos(deadline), event); - token -} - -/// Cancels a timer with the specified token. -/// -/// # Parameters -/// - `token`: The unique token of the timer to cancel. -pub fn cancel_timer(token: usize) { - let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; - let mut timers = timer_list.lock(); - timers.cancel(|event| event.token == token); -} - -/// Check and process any pending timer events -pub fn check_events() { - // info!("Checking timer events..."); - // info!("now is {:#?}", axhal::time::wall_time()); - let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; - loop { - let now = axhal::time::wall_time(); - let event = timer_list.lock().expire_one(now); - if let Some((_deadline, event)) = event { - trace!("pick one {_deadline:#?} to handle!!!"); - event.callback(now); - } else { - break; - } - } -} - -// /// Schedule the next timer event based on the periodic interval -// pub fn scheduler_next_event() { -// trace!("Scheduling next event..."); -// let now_ns = axhal::time::monotonic_time_nanos(); -// let deadline = now_ns + PERIODIC_INTERVAL_NANOS; -// debug!("PHY deadline {} !!!", deadline); -// axhal::time::set_oneshot_timer(deadline); -// } - -/// Initialize the hypervisor timer system -pub fn init_percpu() { - info!("Initing HV Timer..."); - let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; - timer_list.init_once(SpinNoIrq::new(TimerList::new())); -} diff --git a/kernel/src/vmm/vm_list.rs b/kernel/src/vmm/vm_list.rs index 7c6dd419..2a878a04 100644 --- a/kernel/src/vmm/vm_list.rs +++ b/kernel/src/vmm/vm_list.rs @@ -1,9 +1,8 @@ -use alloc::collections::BTreeMap; -use alloc::vec::Vec; +use alloc::{collections::BTreeMap, sync::Arc, vec::Vec}; use spin::Mutex; -use crate::vmm::VMRef; +pub type VMRef = Arc; /// Represents a list of VMs, /// stored in a BTreeMap where the key is the VM ID and the value is a reference to the VM. @@ -71,8 +70,10 @@ static GLOBAL_VM_LIST: Mutex = Mutex::new(VMList::new()); /// # Arguments /// /// * `vm` - A reference to the VM instance. -pub fn push_vm(vm: VMRef) { - GLOBAL_VM_LIST.lock().push_vm(vm.id(), vm) +pub fn push_vm(vm: axvm::Vm) -> VMRef { + let vm = Arc::new(vm); + GLOBAL_VM_LIST.lock().push_vm(vm.id().into(), vm.clone()); + vm } /// Removes a VM from the global VM list by its ID. diff --git a/modules/arm_vcpu b/modules/arm_vcpu new file mode 160000 index 00000000..84042ae5 --- /dev/null +++ b/modules/arm_vcpu @@ -0,0 +1 @@ +Subproject commit 84042ae5fdbfe6d4c9ea4076d8caf5d48ca55d19 diff --git a/modules/arm_vgic b/modules/arm_vgic new file mode 160000 index 00000000..de8b70bf --- /dev/null +++ b/modules/arm_vgic @@ -0,0 +1 @@ +Subproject commit de8b70bffba107f7d2e10fd540450f0ff25115a9 diff --git a/modules/axaddrspace b/modules/axaddrspace new file mode 160000 index 00000000..21ab84bc --- /dev/null +++ b/modules/axaddrspace @@ -0,0 +1 @@ +Subproject commit 21ab84bc1ba194641879182d4efac2535dff32d4 diff --git a/modules/axalloc/Cargo.toml b/modules/axalloc/Cargo.toml new file mode 100644 index 00000000..7fae668d --- /dev/null +++ b/modules/axalloc/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "axalloc" +authors.workspace = true +edition.workspace = true +license.workspace = true +version = "0.2.0" + +[features] +buddy = [] +default = [] +level-1 = [] +page-alloc-4g = [] # Support up to 4G memory capacity +page-alloc-64g = [] # Support up to 64G memory capacity +slab = [] +tlsf = [] + + +[dependencies] +buddy_system_allocator = "0.11" +log = "0.4" +kspin = "0.1" +axerrno = "0.1" \ No newline at end of file diff --git a/modules/axalloc/src/lib.rs b/modules/axalloc/src/lib.rs new file mode 100644 index 00000000..a40f565c --- /dev/null +++ b/modules/axalloc/src/lib.rs @@ -0,0 +1,227 @@ +#![no_std] + +//! [ArceOS](https://github.com/arceos-org/arceos) global memory allocator. +//! +//! It provides [`GlobalAllocator`], which implements the trait +//! [`core::alloc::GlobalAlloc`]. A static global variable of type +//! [`GlobalAllocator`] is defined with the `#[global_allocator]` attribute, to +//! be registered as the standard library’s default allocator. + +#![no_std] + +#[macro_use] +extern crate log; +extern crate alloc; + +// mod page; + +use buddy_system_allocator::Heap; +use core::alloc::{GlobalAlloc, Layout}; +use core::ptr::NonNull; +use kspin::SpinNoIrq; + +const PAGE_SIZE: usize = 0x1000; +const MIN_HEAP_SIZE: usize = 0x8000; // 32 K + +// pub use page::GlobalPage; + +/// The global allocator used by ArceOS. +/// +/// It combines a [`ByteAllocator`] and a [`PageAllocator`] into a simple +/// two-level allocator: firstly tries allocate from the byte allocator, if +/// there is no memory, asks the page allocator for more memory and adds it to +/// the byte allocator. +/// +/// Currently, [`TlsfByteAllocator`] is used as the byte allocator, while +/// [`BitmapPageAllocator`] is used as the page allocator. +/// +/// [`TlsfByteAllocator`]: allocator::TlsfByteAllocator +pub struct GlobalAllocator { + palloc: SpinNoIrq>, +} + +impl Default for GlobalAllocator { + fn default() -> Self { + Self::new() + } +} + +impl GlobalAllocator { + /// Creates an empty [`GlobalAllocator`]. + pub const fn new() -> Self { + Self { + palloc: SpinNoIrq::new(Heap::empty()), + } + } + + /// Returns the name of the allocator. + pub const fn name(&self) -> &'static str { + "buddy" + } + + /// Initializes the allocator with the given region. + /// + /// It firstly adds the whole region to the page allocator, then allocates + /// a small region (32 KB) to initialize the byte allocator. Therefore, + /// the given region must be larger than 32 KB. + pub fn init(&self, start_vaddr: usize, size: usize) { + assert!(size > MIN_HEAP_SIZE); + debug!( + "initialize global allocator at: [{:#x}, {:#x})", + start_vaddr, + start_vaddr + size + ); + unsafe { self.palloc.lock().init(start_vaddr, size) }; + } + + /// Add the given region to the allocator. + /// + /// It will add the whole region to the byte allocator. + pub fn add_memory(&self, start_vaddr: usize, size: usize) { + unsafe { self.palloc.lock().add_to_heap(start_vaddr, size) } + } + + /// Allocate arbitrary number of bytes. Returns the left bound of the + /// allocated region. + /// + /// It firstly tries to allocate from the byte allocator. If there is no + /// memory, it asks the page allocator for more memory and adds it to the + /// byte allocator. + pub fn alloc(&self, layout: Layout) -> Result, ()> { + // single-level allocator: only use the byte allocator. + let mut balloc = self.palloc.lock(); + balloc.alloc(layout) + } + + /// Gives back the allocated region to the byte allocator. + /// + /// The region should be allocated by [`alloc`], and `align_pow2` should be + /// the same as the one used in [`alloc`]. Otherwise, the behavior is + /// undefined. + /// + /// [`alloc`]: GlobalAllocator::alloc + pub fn dealloc(&self, pos: NonNull, layout: Layout) { + self.palloc.lock().dealloc(pos, layout) + } + + /// Allocates contiguous pages. + /// + /// It allocates `num_pages` pages from the page allocator. + /// + /// `align_pow2` must be a power of 2, and the returned region bound will be + /// aligned to it. + pub fn alloc_pages(&self, num_pages: usize, align_pow2: usize) -> Result { + // single-level allocator: allocate from the byte allocator. + let mut balloc = self.palloc.lock(); + let layout = Layout::from_size_align(num_pages * PAGE_SIZE, align_pow2).unwrap(); + let ptr = balloc.alloc(layout)?; + Ok(ptr.as_ptr() as usize) + } + + /// Allocates contiguous pages starting from the given address. + /// + /// It allocates `num_pages` pages from the page allocator starting from the + /// given address. + /// + /// `align_pow2` must be a power of 2, and the returned region bound will be + /// aligned to it. + pub fn alloc_pages_at( + &self, + start: usize, + num_pages: usize, + align_pow2: usize, + ) -> Result { + unimplemented!("level-1 allocator does not support alloc_pages_at") + } + + /// Gives back the allocated pages starts from `pos` to the page allocator. + /// + /// The pages should be allocated by [`alloc_pages`], and `align_pow2` + /// should be the same as the one used in [`alloc_pages`]. Otherwise, the + /// behavior is undefined. + /// + /// [`alloc_pages`]: GlobalAllocator::alloc_pages + pub fn dealloc_pages(&self, pos: usize, num_pages: usize) { + // single-level allocator: deallocate to the byte allocator. + let mut balloc = self.palloc.lock(); + let layout = Layout::from_size_align(num_pages * PAGE_SIZE, PAGE_SIZE).unwrap(); + let ptr = NonNull::new(pos as *mut u8).unwrap(); + balloc.dealloc(ptr, layout); + } + + /// Returns the number of allocated bytes in the byte allocator. + pub fn used_bytes(&self) -> usize { + self.palloc.lock().stats_alloc_actual() + } + + /// Returns the number of available bytes in the byte allocator. + pub fn available_bytes(&self) -> usize { + let g = self.palloc.lock(); + g.stats_total_bytes() - g.stats_alloc_actual() + } + + /// Returns the number of allocated pages in the page allocator. + pub fn used_pages(&self) -> usize { + self.used_bytes().div_ceil(PAGE_SIZE) + } + + /// Returns the number of available pages in the page allocator. + pub fn available_pages(&self) -> usize { + self.available_bytes().div_ceil(PAGE_SIZE) + } +} + +unsafe impl GlobalAlloc for GlobalAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + if let Ok(ptr) = GlobalAllocator::alloc(self, layout) { + ptr.as_ptr() + } else { + alloc::alloc::handle_alloc_error(layout) + } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + GlobalAllocator::dealloc(self, NonNull::new(ptr).expect("dealloc null ptr"), layout) + } +} + +#[cfg_attr(all(target_os = "none", not(test)), global_allocator)] +static GLOBAL_ALLOCATOR: GlobalAllocator = GlobalAllocator::new(); + +/// Returns the reference to the global allocator. +pub fn global_allocator() -> &'static GlobalAllocator { + &GLOBAL_ALLOCATOR +} + +/// Initializes the global allocator with the given memory region. +/// +/// Note that the memory region bounds are just numbers, and the allocator +/// does not actually access the region. Users should ensure that the region +/// is valid and not being used by others, so that the allocated memory is also +/// valid. +/// +/// This function should be called only once, and before any allocation. +pub fn global_init(start_vaddr: usize, size: usize) { + debug!( + "initialize global allocator at: [{:#x}, {:#x})", + start_vaddr, + start_vaddr + size + ); + GLOBAL_ALLOCATOR.init(start_vaddr, size); +} + +/// Add the given memory region to the global allocator. +/// +/// Users should ensure that the region is valid and not being used by others, +/// so that the allocated memory is also valid. +/// +/// It's similar to [`global_init`], but can be called multiple times. +pub fn global_add_memory(start_vaddr: usize, size: usize) -> Result<(), ()> { + debug!( + "add a memory region to global allocator: [{:#x}, {:#x})", + start_vaddr, + start_vaddr + size + ); + GLOBAL_ALLOCATOR.add_memory(start_vaddr, size); + Ok(()) +} diff --git a/modules/axalloc/src/page.rs b/modules/axalloc/src/page.rs new file mode 100644 index 00000000..a4b52315 --- /dev/null +++ b/modules/axalloc/src/page.rs @@ -0,0 +1,107 @@ +use axerrno::{AxError, AxResult}; +use memory_addr::{PhysAddr, VirtAddr}; + +use crate::{PAGE_SIZE, global_allocator}; + +/// A RAII wrapper of contiguous 4K-sized pages. +/// +/// It will automatically deallocate the pages when dropped. +#[derive(Debug)] +pub struct GlobalPage { + start_vaddr: VirtAddr, + num_pages: usize, +} + +impl GlobalPage { + /// Allocate one 4K-sized page. + pub fn alloc() -> AxResult { + global_allocator() + .alloc_pages(1, PAGE_SIZE) + .map(|vaddr| Self { + start_vaddr: vaddr.into(), + num_pages: 1, + }) + .map_err(alloc_err_to_ax_err) + } + + /// Allocate one 4K-sized page and fill with zero. + pub fn alloc_zero() -> AxResult { + let mut p = Self::alloc()?; + p.zero(); + Ok(p) + } + + /// Allocate contiguous 4K-sized pages. + pub fn alloc_contiguous(num_pages: usize, align_pow2: usize) -> AxResult { + global_allocator() + .alloc_pages(num_pages, align_pow2) + .map(|vaddr| Self { + start_vaddr: vaddr.into(), + num_pages, + }) + .map_err(alloc_err_to_ax_err) + } + + /// Get the start virtual address of this page. + pub fn start_vaddr(&self) -> VirtAddr { + self.start_vaddr + } + + /// Get the start physical address of this page. + pub fn start_paddr(&self, virt_to_phys: F) -> PhysAddr + where + F: FnOnce(VirtAddr) -> PhysAddr, + { + virt_to_phys(self.start_vaddr) + } + + /// Get the total size (in bytes) of these page(s). + pub fn size(&self) -> usize { + self.num_pages * PAGE_SIZE + } + + /// Convert to a raw pointer. + pub fn as_ptr(&self) -> *const u8 { + self.start_vaddr.as_ptr() + } + + /// Convert to a mutable raw pointer. + pub fn as_mut_ptr(&mut self) -> *mut u8 { + self.start_vaddr.as_mut_ptr() + } + + /// Fill `self` with `byte`. + pub fn fill(&mut self, byte: u8) { + unsafe { core::ptr::write_bytes(self.as_mut_ptr(), byte, self.size()) } + } + + /// Fill `self` with zero. + pub fn zero(&mut self) { + self.fill(0) + } + + /// Forms a slice that can read data. + pub fn as_slice(&self) -> &[u8] { + unsafe { core::slice::from_raw_parts(self.as_ptr(), self.size()) } + } + + /// Forms a mutable slice that can write data. + pub fn as_slice_mut(&mut self) -> &mut [u8] { + unsafe { core::slice::from_raw_parts_mut(self.as_mut_ptr(), self.size()) } + } +} + +impl Drop for GlobalPage { + fn drop(&mut self) { + global_allocator().dealloc_pages(self.start_vaddr.into(), self.num_pages); + } +} + +const fn alloc_err_to_ax_err(e: AllocError) -> AxError { + match e { + AllocError::InvalidParam | AllocError::MemoryOverlap | AllocError::NotAllocated => { + AxError::InvalidInput + } + AllocError::NoMemory => AxError::NoMemory, + } +} diff --git a/modules/axconfig/Cargo.toml b/modules/axconfig/Cargo.toml index 4157cbe8..f9b62db7 100644 --- a/modules/axconfig/Cargo.toml +++ b/modules/axconfig/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "axconfig" -version.workspace = true +version = "0.1.0" edition.workspace = true authors = ["Yuekai Jia "] description = "Platform-specific constants and parameters for ArceOS" diff --git a/modules/axdevice b/modules/axdevice new file mode 160000 index 00000000..0f78e270 --- /dev/null +++ b/modules/axdevice @@ -0,0 +1 @@ +Subproject commit 0f78e270cfe5fa47d888a1cad3cbb1e3cc15014e diff --git a/modules/axdevice_base b/modules/axdevice_base new file mode 160000 index 00000000..a831babc --- /dev/null +++ b/modules/axdevice_base @@ -0,0 +1 @@ +Subproject commit a831babcd400240bf9496135ff7f241b2ee4d656 diff --git a/modules/axruntime/Cargo.toml b/modules/axruntime/Cargo.toml index c89c419e..b876148c 100644 --- a/modules/axruntime/Cargo.toml +++ b/modules/axruntime/Cargo.toml @@ -6,7 +6,7 @@ edition.workspace = true license.workspace = true name = "axruntime" repository = "https://github.com/arceos-org/arceos/tree/main/modules/axruntime" -version.workspace = true +version = "0.1.0" [features] default = [] @@ -42,9 +42,11 @@ axtask = {workspace = true, optional = true} log = "0.4" +memory_addr = "0.4" crate_interface = "0.1" ctor_bare = "0.2" -percpu = {version = "0.2", optional = true} +percpu = { workspace = true, optional = true} +percpu_macros = { workspace = true} cfg-if = "1.0" chrono = {version = "0.4.38", default-features = false} @@ -53,5 +55,5 @@ chrono = {version = "0.4.38", default-features = false} axplat-x86-qemu-q35 = {workspace = true} [target.'cfg(target_arch = "aarch64")'.dependencies] -axplat-aarch64-dyn = {git = "https://github.com/arceos-hypervisor/axplat-aarch64-dyn.git", tag = "v0.4.0", features = ["irq", "smp", "hv"]} +axplat-aarch64-dyn.workspace = true somehal = "0.4" \ No newline at end of file diff --git a/modules/axruntime/src/lib.rs b/modules/axruntime/src/lib.rs index 2586dfad..b7714daa 100644 --- a/modules/axruntime/src/lib.rs +++ b/modules/axruntime/src/lib.rs @@ -48,7 +48,7 @@ const LOGO: &str = r#" d88P 888 888 "Y8888P "Y8888 "Y88888P" "Y8888P" "#; -unsafe extern { +unsafe extern "C" { /// Application's entry point. fn main(); } @@ -172,7 +172,7 @@ pub fn rust_main(cpu_id: usize, arg: usize) -> ! { axhal::init_later(cpu_id, arg); #[cfg(feature = "multitask")] - axtask::init_scheduler(); + axtask::init_scheduler_with_cpu_num(cpu_count()); #[cfg(any(feature = "fs", feature = "net", feature = "display"))] { @@ -227,6 +227,7 @@ pub fn rust_main(cpu_id: usize, arg: usize) -> ! { #[cfg(feature = "alloc")] fn init_allocator() { use axhal::mem::{MemRegionFlags, memory_regions, phys_to_virt}; + use memory_addr::MemoryAddr; info!("Initialize global memory allocator..."); info!(" use {} allocator.", axalloc::global_allocator().name()); @@ -250,7 +251,10 @@ fn init_allocator() { } for r in memory_regions() { if r.flags.contains(MemRegionFlags::FREE) && r.paddr == max_region_paddr { - axalloc::global_init(phys_to_virt(r.paddr).as_usize(), r.size); + let end = r.paddr + r.size; + let start = r.paddr.align_up(0x8000usize); + let size = end - start; + axalloc::global_init(phys_to_virt(start).as_usize(), size); break; } } diff --git a/modules/axvcpu b/modules/axvcpu new file mode 160000 index 00000000..9f3fa73d --- /dev/null +++ b/modules/axvcpu @@ -0,0 +1 @@ +Subproject commit 9f3fa73dbd7e2078f6d9109b43f022370cb80b35 diff --git a/modules/axvisor_api b/modules/axvisor_api new file mode 160000 index 00000000..4cfc508f --- /dev/null +++ b/modules/axvisor_api @@ -0,0 +1 @@ +Subproject commit 4cfc508f0fe22e6c284182a64d788236d3e7eea9 diff --git a/modules/axvm b/modules/axvm new file mode 160000 index 00000000..bce19d92 --- /dev/null +++ b/modules/axvm @@ -0,0 +1 @@ +Subproject commit bce19d92ee18ee6e38d480acbd6c90bc958a1681 diff --git a/modules/axvm-types/Cargo.toml b/modules/axvm-types/Cargo.toml new file mode 100644 index 00000000..a79cc820 --- /dev/null +++ b/modules/axvm-types/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "axvm-types" +authors.workspace = true +edition.workspace = true +license.workspace = true +version = "0.1.0" + +[dependencies] +memory_addr = "0.4" +bitflags = "2.9" \ No newline at end of file diff --git a/modules/axvm-types/src/addr.rs b/modules/axvm-types/src/addr.rs new file mode 100644 index 00000000..82a7ed2e --- /dev/null +++ b/modules/axvm-types/src/addr.rs @@ -0,0 +1,23 @@ +use memory_addr::{AddrRange, PhysAddr, VirtAddr, def_usize_addr, def_usize_addr_formatter}; + +/// Host virtual address. +pub type HostVirtAddr = VirtAddr; +/// Host physical address. +pub type HostPhysAddr = PhysAddr; + +def_usize_addr! { + /// Guest virtual address. + pub type GuestVirtAddr; + /// Guest physical address. + pub type GuestPhysAddr; +} + +def_usize_addr_formatter! { + GuestVirtAddr = "GVA:{}"; + GuestPhysAddr = "GPA:{}"; +} + +/// Guest virtual address range. +pub type GuestVirtAddrRange = AddrRange; +/// Guest physical address range. +pub type GuestPhysAddrRange = AddrRange; diff --git a/modules/axvm-types/src/device/device_addr.rs b/modules/axvm-types/src/device/device_addr.rs new file mode 100644 index 00000000..b4698ed3 --- /dev/null +++ b/modules/axvm-types/src/device/device_addr.rs @@ -0,0 +1,97 @@ +use core::fmt::LowerHex; + +use memory_addr::AddrRange; + +use crate::addr::GuestPhysAddr; + +use super::{Port, SysRegAddr}; + +/// An address-like type that can be used to access devices. +pub trait DeviceAddr: Copy + Eq + Ord + core::fmt::Debug {} + +/// A range of device addresses. It may be contiguous or not. +pub trait DeviceAddrRange { + /// The address type of the range. + type Addr: DeviceAddr; + + /// Returns whether the address range contains the given address. + fn contains(&self, addr: Self::Addr) -> bool; +} + +impl DeviceAddr for GuestPhysAddr {} + +impl DeviceAddrRange for AddrRange { + type Addr = GuestPhysAddr; + + fn contains(&self, addr: Self::Addr) -> bool { + Self::contains(*self, addr) + } +} + +impl DeviceAddr for SysRegAddr {} + +/// A inclusive range of system register addresses. +/// +/// Unlike [`AddrRange`], this type is inclusive on both ends. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct SysRegAddrRange { + /// The start address of the range. + pub start: SysRegAddr, + /// The end address of the range. + pub end: SysRegAddr, +} + +impl SysRegAddrRange { + /// Creates a new [`SysRegAddrRange`] instance. + pub fn new(start: SysRegAddr, end: SysRegAddr) -> Self { + Self { start, end } + } +} + +impl DeviceAddrRange for SysRegAddrRange { + type Addr = SysRegAddr; + + fn contains(&self, addr: Self::Addr) -> bool { + addr.0 >= self.start.0 && addr.0 <= self.end.0 + } +} + +impl LowerHex for SysRegAddrRange { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#x}..={:#x}", self.start.0, self.end.0) + } +} + +impl DeviceAddr for Port {} + +/// A inclusive range of port numbers. +/// +/// Unlike [`AddrRange`], this type is inclusive on both ends. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct PortRange { + /// The start port number of the range. + pub start: Port, + /// The end port number of the range. + pub end: Port, +} + +impl PortRange { + /// Creates a new [`PortRange`] instance. + pub fn new(start: Port, end: Port) -> Self { + Self { start, end } + } +} + +impl DeviceAddrRange for PortRange { + type Addr = Port; + + fn contains(&self, addr: Self::Addr) -> bool { + addr.0 >= self.start.0 && addr.0 <= self.end.0 + } +} + +impl LowerHex for PortRange { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#x}..={:#x}", self.start.0, self.end.0) + } +} diff --git a/modules/axvm-types/src/device/mod.rs b/modules/axvm-types/src/device/mod.rs new file mode 100644 index 00000000..36c96e2d --- /dev/null +++ b/modules/axvm-types/src/device/mod.rs @@ -0,0 +1,132 @@ +//! Definitions about device accessing. + +use core::fmt::{Debug, LowerHex, UpperHex}; + +mod device_addr; + +pub use device_addr::*; + +/// The width of an access. +/// +/// Note that the term "word" here refers to 16-bit data, as in the x86 architecture. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum AccessWidth { + /// 8-bit access. + Byte, + /// 16-bit access. + Word, + /// 32-bit access. + Dword, + /// 64-bit access. + Qword, +} + +impl TryFrom for AccessWidth { + type Error = (); + + fn try_from(value: usize) -> Result { + match value { + 1 => Ok(Self::Byte), + 2 => Ok(Self::Word), + 4 => Ok(Self::Dword), + 8 => Ok(Self::Qword), + _ => Err(()), + } + } +} + +impl From for usize { + fn from(width: AccessWidth) -> usize { + match width { + AccessWidth::Byte => 1, + AccessWidth::Word => 2, + AccessWidth::Dword => 4, + AccessWidth::Qword => 8, + } + } +} + +impl AccessWidth { + /// Returns the size of the access in bytes. + pub fn size(&self) -> usize { + (*self).into() + } + + /// Returns the range of bits that the access covers. + pub fn bits_range(&self) -> core::ops::Range { + match self { + AccessWidth::Byte => 0..8, + AccessWidth::Word => 0..16, + AccessWidth::Dword => 0..32, + AccessWidth::Qword => 0..64, + } + } +} + +/// The port number of an I/O operation. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Port(pub u16); + +impl Port { + /// Creates a new `Port` instance. + pub fn new(port: u16) -> Self { + Self(port) + } + + /// Returns the port number. + pub fn number(&self) -> u16 { + self.0 + } +} + +impl LowerHex for Port { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Port({:#x})", self.0) + } +} + +impl UpperHex for Port { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Port({:#X})", self.0) + } +} + +impl Debug for Port { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Port({})", self.0) + } +} + +/// A system register address. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub struct SysRegAddr(pub usize); // u32 seems to be enough, but we use usize for generality. + +impl SysRegAddr { + /// Creates a new `SysRegAddr` instance. + pub const fn new(addr: usize) -> Self { + Self(addr) + } + + /// Returns the address. + pub const fn addr(&self) -> usize { + self.0 + } +} + +impl LowerHex for SysRegAddr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "SysRegAddr({:#x})", self.0) + } +} + +impl UpperHex for SysRegAddr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "SysRegAddr({:#X})", self.0) + } +} + +impl Debug for SysRegAddr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "SysRegAddr({})", self.0) + } +} diff --git a/modules/axvm-types/src/lib.rs b/modules/axvm-types/src/lib.rs new file mode 100644 index 00000000..773e10c8 --- /dev/null +++ b/modules/axvm-types/src/lib.rs @@ -0,0 +1,5 @@ +#![no_std] + +pub mod addr; +pub mod device; +pub mod mem; diff --git a/modules/axvm-types/src/mem.rs b/modules/axvm-types/src/mem.rs new file mode 100644 index 00000000..059f3914 --- /dev/null +++ b/modules/axvm-types/src/mem.rs @@ -0,0 +1,25 @@ +bitflags::bitflags! { + /// Generic page table entry flags that indicate the corresponding mapped + /// memory region permissions and attributes. + #[derive(Clone, Copy, PartialEq)] + pub struct MappingFlags: usize { + /// The memory is readable. + const READ = 1 << 0; + /// The memory is writable. + const WRITE = 1 << 1; + /// The memory is executable. + const EXECUTE = 1 << 2; + /// The memory is user accessible. + const USER = 1 << 3; + /// The memory is device memory. + const DEVICE = 1 << 4; + /// The memory is uncached. + const UNCACHED = 1 << 5; + } +} + +impl core::fmt::Debug for MappingFlags { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Debug::fmt(&self.0, f) + } +} diff --git a/modules/axvmconfig b/modules/axvmconfig new file mode 160000 index 00000000..2ec04bf1 --- /dev/null +++ b/modules/axvmconfig @@ -0,0 +1 @@ +Subproject commit 2ec04bf1f3d6096af789e461dbf5be737a865432 diff --git a/modules/x86_vcpu b/modules/x86_vcpu new file mode 160000 index 00000000..809d7205 --- /dev/null +++ b/modules/x86_vcpu @@ -0,0 +1 @@ +Subproject commit 809d7205fd8a9296b608c4b2f251069029000e02 diff --git a/modules/x86_vlapic b/modules/x86_vlapic new file mode 160000 index 00000000..199e1dd1 --- /dev/null +++ b/modules/x86_vlapic @@ -0,0 +1 @@ +Subproject commit 199e1dd128e56fe6e42905845874053c8c427c29 diff --git a/platform/axplat-aarch64-dyn b/platform/axplat-aarch64-dyn new file mode 160000 index 00000000..36deb395 --- /dev/null +++ b/platform/axplat-aarch64-dyn @@ -0,0 +1 @@ +Subproject commit 36deb3954fb8c8aeff56a3f47c8dd65f9f5ea69c diff --git a/platform/x86-qemu-q35/src/lib.rs b/platform/x86-qemu-q35/src/lib.rs index 92136980..18a7a5d4 100644 --- a/platform/x86-qemu-q35/src/lib.rs +++ b/platform/x86-qemu-q35/src/lib.rs @@ -38,13 +38,13 @@ fn current_cpu_id() -> usize { } } -unsafe extern fn rust_entry(magic: usize, mbi: usize) { +unsafe extern "C" fn rust_entry(magic: usize, mbi: usize) { if magic == self::boot::MULTIBOOT_BOOTLOADER_MAGIC { axplat::call_main(current_cpu_id(), mbi); } } -unsafe extern fn rust_entry_secondary(_magic: usize) { +unsafe extern "C" fn rust_entry_secondary(_magic: usize) { #[cfg(feature = "smp")] if _magic == self::boot::MULTIBOOT_BOOTLOADER_MAGIC { axplat::call_secondary_main(current_cpu_id()); @@ -52,7 +52,7 @@ unsafe extern fn rust_entry_secondary(_magic: usize) { } pub fn cpu_count() -> usize { - unsafe extern { + unsafe extern "C" { static SMP: usize; } diff --git a/platform/x86-qemu-q35/src/mp.rs b/platform/x86-qemu-q35/src/mp.rs index 14e8e671..da89b9a8 100644 --- a/platform/x86-qemu-q35/src/mp.rs +++ b/platform/x86-qemu-q35/src/mp.rs @@ -12,7 +12,7 @@ core::arch::global_asm!( ); unsafe fn setup_startup_page(stack_top: PhysAddr) { - unsafe extern { + unsafe extern "C" { fn ap_entry32(); fn ap_start(); fn ap_end(); @@ -25,7 +25,7 @@ unsafe fn setup_startup_page(stack_top: PhysAddr) { core::ptr::copy_nonoverlapping( ap_start as *const u64, start_page_ptr, - (ap_end as usize - ap_start as usize) / 8, + (ap_end as *const () as usize - ap_start as *const () as usize) / 8, ); } start_page[U64_PER_PAGE - 2] = stack_top.as_usize() as u64; // stack_top diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 17d89782..40f15c58 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -19,7 +19,6 @@ serde_json = "1" sha2 = "0.10" tokio = {version = "1", features = ["full"]} toml.workspace = true - axvmconfig = {workspace = true, features = ["std"]} tar = "0.4" flate2 = "1.0" diff --git a/xtask/src/cargo.rs b/xtask/src/cargo.rs index 0fe676f3..a821bdf5 100644 --- a/xtask/src/cargo.rs +++ b/xtask/src/cargo.rs @@ -1,5 +1,5 @@ -use std::{fs, path::PathBuf}; use ostool::build::CargoRunnerKind; +use std::{fs, path::PathBuf}; use crate::ctx::Context; diff --git a/xtask/src/devspace.rs b/xtask/src/devspace.rs index 0bcaae63..684ad497 100644 --- a/xtask/src/devspace.rs +++ b/xtask/src/devspace.rs @@ -14,12 +14,17 @@ const PATCH_END_MARKER: &str = "# <<< devspace patches <<<"; const CRATES_IO_SOURCE_KEY: &str = "crates-io"; const DEVSPACE_REPOS: &[&str] = &[ + "axvm", + "axvcpu", + "axdevice", "arm_vcpu", "arm_vgic", "axaddrspace", "axdevice_base", + "axvisor_api", "x86_vcpu", "x86_vlapic", + "axvmconfig", ]; const DEVSPACE_REPO_OVERRIDES: &[(&str, &str)] = &[( diff --git a/xtask/src/image.rs b/xtask/src/image.rs index dcde79bf..c54e2eb2 100644 --- a/xtask/src/image.rs +++ b/xtask/src/image.rs @@ -46,25 +46,25 @@ pub struct ImageArgs { pub enum ImageCommands { /// List all available images Ls, - + /// Download the specified image and automatically extract it Download { /// Name of the image to download image_name: String, - + /// Output directory for the downloaded image #[arg(short, long)] output_dir: Option, - + /// Do not extract after download #[arg(long, help = "Do not extract after download")] no_extract: bool, }, - + /// Remove the specified image from temp directory Rm { /// Name of the image to remove - image_name: String + image_name: String, }, } @@ -433,12 +433,16 @@ async fn image_download(image_name: &str, output_dir: Option, extract: b Ok(false) => { // Remove the invalid downloaded file let _ = fs::remove_file(&output_path); - return Err(anyhow!("Download completed but file SHA256 verification failed")); + return Err(anyhow!( + "Download completed but file SHA256 verification failed" + )); } Err(e) => { // Remove the potentially corrupted downloaded file let _ = fs::remove_file(&output_path); - return Err(anyhow!("Download completed but error verifying downloaded file: {e}")); + return Err(anyhow!( + "Download completed but error verifying downloaded file: {e}" + )); } } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 68390dc7..f5fdb5ef 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -55,11 +55,11 @@ struct QemuArgs { /// Path to custom build configuration file (TOML format) #[arg(long)] build_config: Option, - + /// Path to custom QEMU configuration file #[arg(long)] qemu_config: Option, - + /// Comma-separated list of VM configuration files #[arg(long)] vmconfigs: Vec, @@ -73,23 +73,23 @@ struct ClippyArgs { /// Only check specific packages (comma separated) #[arg(long)] packages: Option, - + /// Only check specific targets (comma separated) #[arg(long)] targets: Option, - + /// Continue on error instead of exiting immediately #[arg(long)] continue_on_error: bool, - + /// Dry run - show what would be checked without running clippy #[arg(long)] dry_run: bool, - + /// Automatically fix clippy warnings where possible #[arg(long)] fix: bool, - + /// Allow fixing when the working directory is dirty (has uncommitted changes) #[arg(long)] allow_dirty: bool, @@ -100,11 +100,11 @@ struct UbootArgs { /// Path to custom build configuration file (TOML format) #[arg(long)] build_config: Option, - + /// Path to custom U-Boot configuration file #[arg(long)] uboot_config: Option, - + /// Comma-separated list of VM configuration files #[arg(long)] vmconfigs: Vec, diff --git a/xtask/src/menuconfig.rs b/xtask/src/menuconfig.rs index 199b440f..0460dc47 100644 --- a/xtask/src/menuconfig.rs +++ b/xtask/src/menuconfig.rs @@ -10,7 +10,10 @@ impl Context { let config_path = self.ctx.paths.workspace.join(".build.toml"); if config_path.exists() { - println!("\nCurrent .build.toml configuration file: {}", config_path.display()); + println!( + "\nCurrent .build.toml configuration file: {}", + config_path.display() + ); } else { println!("\nNo .build.toml configuration file found, will use default configuration"); } From a5d00b1104018d2bc803bcc2e4b8accf42f5d572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 26 Dec 2025 11:24:04 +0800 Subject: [PATCH 02/19] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20changelog=20?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=92=8C=E5=B8=83=E5=B1=80=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/changelog/README.md | 1 + doc/changelog/v0.3.0/AxVCpu.md | 4 + doc/changelog/v0.3.0/layout.dio | 206 ++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 doc/changelog/README.md create mode 100644 doc/changelog/v0.3.0/AxVCpu.md create mode 100644 doc/changelog/v0.3.0/layout.dio diff --git a/doc/changelog/README.md b/doc/changelog/README.md new file mode 100644 index 00000000..4e768b56 --- /dev/null +++ b/doc/changelog/README.md @@ -0,0 +1 @@ +# \ No newline at end of file diff --git a/doc/changelog/v0.3.0/AxVCpu.md b/doc/changelog/v0.3.0/AxVCpu.md new file mode 100644 index 00000000..dc074c19 --- /dev/null +++ b/doc/changelog/v0.3.0/AxVCpu.md @@ -0,0 +1,4 @@ +# 整体重构 + +## 架构分层 + diff --git a/doc/changelog/v0.3.0/layout.dio b/doc/changelog/v0.3.0/layout.dio new file mode 100644 index 00000000..7651ee30 --- /dev/null +++ b/doc/changelog/v0.3.0/layout.dio @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From a41eef88f913f6c2c74ce488d04a8701901f6bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 26 Dec 2025 12:19:24 +0800 Subject: [PATCH 03/19] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20changelog=20?= =?UTF-8?q?=E5=B8=83=E5=B1=80=EF=BC=8C=E6=B7=BB=E5=8A=A0=E8=99=9A=E6=8B=9F?= =?UTF-8?q?=20CPU=20=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/changelog/v0.3.0/layout.dio | 40 ++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/doc/changelog/v0.3.0/layout.dio b/doc/changelog/v0.3.0/layout.dio index 7651ee30..fa9a0be1 100644 --- a/doc/changelog/v0.3.0/layout.dio +++ b/doc/changelog/v0.3.0/layout.dio @@ -54,6 +54,9 @@ + + + @@ -142,7 +145,7 @@ - + @@ -154,7 +157,7 @@ - + @@ -163,13 +166,16 @@ + + + - + @@ -194,12 +200,36 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + From 63bd96bfafdcce3cc3acd5da6db9e25bb79c6e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 26 Dec 2025 12:33:58 +0800 Subject: [PATCH 04/19] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20changelog=20?= =?UTF-8?q?=E5=B8=83=E5=B1=80=EF=BC=8C=E6=B7=BB=E5=8A=A0=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E5=92=8C=E7=89=B9=E5=BE=81=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/changelog/v0.3.0/layout.dio | 79 ++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/doc/changelog/v0.3.0/layout.dio b/doc/changelog/v0.3.0/layout.dio index fa9a0be1..14f73271 100644 --- a/doc/changelog/v0.3.0/layout.dio +++ b/doc/changelog/v0.3.0/layout.dio @@ -10,32 +10,32 @@ - + - + - + - - + + - + - + - + @@ -60,15 +60,15 @@ - + - + - + @@ -90,7 +90,7 @@ - + @@ -130,58 +130,58 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -203,33 +203,42 @@ - + - + - + - + - + - + - + - + + + + + + + + + + From 5779cbaca311d147c2ac3474caa9c39cf8ea14cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 26 Dec 2025 12:40:47 +0800 Subject: [PATCH 05/19] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20changelog=20?= =?UTF-8?q?=E5=B8=83=E5=B1=80=EF=BC=8C=E8=B0=83=E6=95=B4=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E5=B9=B6=E6=B7=BB=E5=8A=A0=E6=96=B0=E7=BB=84?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/changelog/v0.3.0/layout.dio | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/doc/changelog/v0.3.0/layout.dio b/doc/changelog/v0.3.0/layout.dio index 14f73271..a33d3f66 100644 --- a/doc/changelog/v0.3.0/layout.dio +++ b/doc/changelog/v0.3.0/layout.dio @@ -72,7 +72,10 @@ - + + + + @@ -128,7 +131,7 @@ - + @@ -139,6 +142,9 @@ + + + From 029a10e7c1f84ac5fd8490a357c93fca9d636080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 26 Dec 2025 14:16:18 +0800 Subject: [PATCH 06/19] Refactor code structure for improved readability and maintainability --- doc/changelog/v0.3.0/AxVCpu.md | 97 +++++++++++++++++++++- doc/changelog/v0.3.0/architecture-old.png | Bin 0 -> 98723 bytes doc/changelog/v0.3.0/layout.dio | 56 +++++++++++++ doc/changelog/v0.3.0/layout.svg | 1 + 4 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 doc/changelog/v0.3.0/architecture-old.png create mode 100644 doc/changelog/v0.3.0/layout.svg diff --git a/doc/changelog/v0.3.0/AxVCpu.md b/doc/changelog/v0.3.0/AxVCpu.md index dc074c19..c3ae0c75 100644 --- a/doc/changelog/v0.3.0/AxVCpu.md +++ b/doc/changelog/v0.3.0/AxVCpu.md @@ -1,4 +1,97 @@ -# 整体重构 +# 重构 AxVCpu -## 架构分层 +## 1. 背景与动机 +当前 axvisor 项目包含三个核心 crate: +- axvisor:顶层虚拟化与平台管理逻辑 +- axvm:虚拟机(VMM / guest execution)相关实现 +- axdevice:虚拟/直通设备抽象与注册 + +随着对多架构(如 RISC-V、AArch64、x86_64 等)与多平台(不同 SoC / 板级)的支持增加,项目内部出现以下问题: +1. arch 相关条件编译(`#[cfg(...)]`)在 axvm 与 axdevice 中大量散落,难以维护。 +2. 配置项(arch、平台、虚拟设备开关、内存/核数限制等)分布于: + - axvm(如启动参数、MMU/内存模型) + - axdevice(设备选择、初始化) + - axvmconfig / axvisor_api(跨 crate 传递结构) + 造成信息路径割裂。 +3. 配置“流向”不清晰:谁定义 / 谁持有 / 谁消费不明确。 +4. 架构耦合阻碍复用:axvm、axdevice 难以在其他宿主或测试环境中独立使用(例如单元测试 / 模拟)。 +5. 虚拟设备在axdevice直接注册,造成配置需经过axvisor->axvm->axdevice,跨多个crate,重复传递。 +6. 架构感知逻辑与“功能逻辑”耦合,增加新增架构或改动现有策略的风险与成本。 +7. API接口不统一,同一个库中同时使用多种不同的接口风格,包括trait直接作为接口、axvisor-api外部函数和extern "C"函数。 +8. axvcpu本意是设计为抹除架构间vcpu差异,但实际是各架构差异较大,在这一层抹除差异造成接口过度复杂,无法根据特性优化。 +**vCPU核心功能差异:** + +| 功能特性 | ARM64 | x86_64 | RISC-V | 抽象复杂度 | +|---------|-------|--------|--------|-----------| +| **虚拟化扩展** | VHE/VNCR | VMX | H-extension | ⭐⭐⭐ | +| **上下文切换** | EL2/EL1切换 | VMCS管理 | HSTATE管理 | ⭐⭐⭐ | +| **异常处理** | Syndrome解析 | Exit Reason | Exit Cause | ⭐⭐ | +| **内存管理** | Stage-2页表 | EPT/NPT | Sv-39页表 | ⭐⭐⭐ | +| **寄存器模型** | 通用+系统寄存器 | 通用+MSR | 通用+CSR | ⭐⭐ | + +**虚拟中断控制器差异对比:** + +| 特性 | ARM vGIC | Intel APIC | RISC-V IMSIC | 接口统一难度 | +|------|----------|------------|--------------|-------------| +| **消息传递** | SGI/ID/PPI | LAPIC/IOAPIC | MSI | ⭐⭐⭐⭐⭐ | +| **路由机制** | Affinity/Target | Fixed/Lowest | AIA | ⭐⭐⭐⭐ | +| **优先级** | 32级优先级 | 8级优先级 | 配置优先级 | ⭐⭐⭐ | +| **虚拟化支持** | vGICv2/vGICv3 | APICv | IMSIC+HVIP | ⭐⭐⭐⭐⭐ | +| **配置接口** | Distributor | MSR访问 | MMIO访问 | ⭐⭐⭐⭐ | + + +![dependency-problems](architecture-old.png) + + +## 2. 目标 + +| 目标类别 | 目标描述 | 衡量指标 | +|----------|----------|----------| +| 架构解耦 | axvm、axdevice 不再直接包含 arch 特定条件编译 | arch 条件编译迁移到 axvisor | +| 配置集中 | 所有外部可调配置集中在 axvisor | 单一入口(Configuration Root) | +| 接口稳定 | axvm / axdevice 通过清晰 API 接收已解析配置 | API 文档化,函数/类型变更频率降低 | +| 可测试性 | 可在无真实硬件 / 无特定 arch 条件下运行核心逻辑测试 | CI 引入“generic host” profile | +| 可扩展性 | 支持新增 arch / 虚拟设备时最小化侵入 | 新增 arch 时仅修改 axvisor 层及少量 adapter | + +## 3. 优化方案 + +![new-architecture](layout.svg) + +新的设计理念:从"vCPU层抹除差异"转变为"VM层抹除差异,vCPU求同存异"。核心相似功能抽象,架构特性独立,最小公约的函数组合,最终在VM层抹除架构差异 + +### 1. 单一配置源(Single Source of Truth) + +所有运行参数(arch、平台、设备编排、资源限制等)在 axvisor 中构建并冻结。 + +### 2. 明确数据流方向 + +配置只“下行”到 axvm、axdevice;运行期状态可“上行”以供监控。 + +### 3. Arch 隔离, 模块化设计 + +arch 专属代码集中于 axvm::arch::*。 + +`common` 提供跨架构通用逻辑、模块与接口定义。 + +每个架构实现其特定的 VCPU、内存模型、中断注入等,组合`common`中的模块,如 `device`、`addrspace` 等。最终通过 `trait ArchVm` 适配器暴露统一接口供上层调用。从而实现增加或修改某个架构时不影响其他架构。 + +### 4. `AxVm` 通过状态机管理虚拟机生命周期与资源分配 + +- `AxVisor` 内核维护 `vmm` 容器,承载多个 `Vm` 实例。 +- `VmData` 记录基础信息(ID、Name)与状态机 `VmMachineState`。 +- `VmMachineState` 为枚举,涵盖状态数据: + +```rust +pub enum VmMachineState { + Uninit(VmMachineUninit), + Inited(VmMachineInited), + Running(VmMachineRunning), + Switching, + #[allow(unused)] + Stopping(VmStatusStopping), + Stopped, +} +``` + +确保不混淆相应状态的数据与行为。状态转换通过 `Switching` 中间态进行,用 `Switching` 换出前一状态,使数据所有权可以 `move` 到下一状态。 diff --git a/doc/changelog/v0.3.0/architecture-old.png b/doc/changelog/v0.3.0/architecture-old.png new file mode 100644 index 0000000000000000000000000000000000000000..52425a8aedf70ba01e4fbafd806ad8df7dbad8d9 GIT binary patch literal 98723 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/changelog/v0.3.0/layout.svg b/doc/changelog/v0.3.0/layout.svg new file mode 100644 index 00000000..a16614e9 --- /dev/null +++ b/doc/changelog/v0.3.0/layout.svg @@ -0,0 +1 @@ +
ArceOS
ArceOS
axfs
axfs
axruntime
axruntime
axconfig
axconfig
axtask
axtask
Patch
Patch
axfs
axfs
axruntime
axruntime
axconfig
axconfig
AxVisor
AxVisor
shell
shell
config
config
vmm
vmm
main
main
std
std
std
(os independent)
std...
crate interface
crate interface
axplat
axplat
Somehal
(dyn platform)
Somehal...
x86(todo)
x86(todo)
riscv(todo)
riscv(todo)
aarch64
aarch64
axplat-dyn
axplat-dyn
Arch sepc HAL
reexport somehal
Arch sepc HAL...
AxVm
AxVm
Commen
Commen
fdt-edit
fdt-edit
cache<Arch>
cache<Arch>
addrspace<Arch>
addrspace<Arch>
std
std
AArch64
AArch64
fdt-edit
fdt-edit
arm_vcpu
arm_vcpu
addrspace<AArch64>
addrspace<AArch64>
Machine<AArch64>
Machine<AArch64>
X86
X86
acpi-edit
acpi-edit
addrspace<x86>
addrspace<x86>
Machine<x86>
Machine<x86>
x86_vcpu
x86_vcpu
RsicV64
RsicV64
fdt-edit
fdt-edit
riscv_vcpu
riscv_vcpu
addrspace<riscv64>
addrspace<riscv64>
Machine<riscv64>
Machine<riscv64>
Arch sepc HAL
Arch sepc HAL
arch independent vm
arch independent vm
Component
Component
arm_vcpu
arm_vcpu
x86_vcpu
x86_vcpu
riscv_vcpu
riscv_vcpu
axdevice
axdevice
axaddrspace
axaddrspace
arm_vgic
arm_vgic
x86_vlapic
x86_vlapic
axvmconfig
axvmconfig
component
component
trait
trait
Enum Statemachine
Enum Statemachine
Uninit
(VmMachineUninit)
Uninit...
Switching
Switching
Inited
(VmMachineInited)
Inited...
Running
(VmMachineRunning)
Running...
Stopping
(VmMachineStopping)
Stopping...
Switching
Switching
Switching
Switching
Stopped
Stopped
Text is not SVG - cannot display
\ No newline at end of file From efe71e88228c88d45b80135b1ea98962b53b9276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 26 Dec 2025 14:27:04 +0800 Subject: [PATCH 07/19] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20AxVCpu=20?= =?UTF-8?q?=E6=96=87=E6=A1=A3=EF=BC=8C=E4=BC=98=E5=8C=96=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E7=90=86=E5=BF=B5=E5=92=8C=E6=A8=A1=E5=9D=97=E5=8C=96=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/changelog/v0.3.0/AxVCpu.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/doc/changelog/v0.3.0/AxVCpu.md b/doc/changelog/v0.3.0/AxVCpu.md index c3ae0c75..3f6e33e3 100644 --- a/doc/changelog/v0.3.0/AxVCpu.md +++ b/doc/changelog/v0.3.0/AxVCpu.md @@ -60,23 +60,25 @@ 新的设计理念:从"vCPU层抹除差异"转变为"VM层抹除差异,vCPU求同存异"。核心相似功能抽象,架构特性独立,最小公约的函数组合,最终在VM层抹除架构差异 -### 1. 单一配置源(Single Source of Truth) +### 单一配置源(Single Source of Truth) 所有运行参数(arch、平台、设备编排、资源限制等)在 axvisor 中构建并冻结。 -### 2. 明确数据流方向 +### Arch 隔离, 模块化设计 配置只“下行”到 axvm、axdevice;运行期状态可“上行”以供监控。 -### 3. Arch 隔离, 模块化设计 +循环依赖(crate-interface)被限制在 `axplat <-> axplat-dyn` 最小范围内。 -arch 专属代码集中于 axvm::arch::*。 +对 `arceos` 的依赖仅包括 `std` 兼容部分,以此使 `AxVisor` 本体和所有组件做到 OS 无关,后续可方便支持其他单内核甚至宏内核,微内核等不同宿主环境。 + +`Arch` 专属代码集中于 `axvm::arch::*`。对于 `Arch` 相关的功能,通过 `axplat-dyn` 透传 `somehal` 的 `Arch` 实现,使得 `Arch` 相关功能限制在 `AxVm` 内部,避免其他组件产生对 `Arch` 的依赖。 `common` 提供跨架构通用逻辑、模块与接口定义。 每个架构实现其特定的 VCPU、内存模型、中断注入等,组合`common`中的模块,如 `device`、`addrspace` 等。最终通过 `trait ArchVm` 适配器暴露统一接口供上层调用。从而实现增加或修改某个架构时不影响其他架构。 -### 4. `AxVm` 通过状态机管理虚拟机生命周期与资源分配 +### 通过状态机管理虚拟机生命周期与资源分配 - `AxVisor` 内核维护 `vmm` 容器,承载多个 `Vm` 实例。 - `VmData` 记录基础信息(ID、Name)与状态机 `VmMachineState`。 From 12321a64ae620e092eb4b297d6fc28140b8e2d1d Mon Sep 17 00:00:00 2001 From: TQ <128586861+YanLien@users.noreply.github.com> Date: Tue, 6 Jan 2026 14:59:25 +0800 Subject: [PATCH 08/19] refactor: extract shell and VMM into separate modules with enhanced shell features (#340) * refactor: extract shell and vmm into separate modules * fix: update paths for axvisor-vmm and axvisor-shell to relative references * feat: Implement tab completion and command history management * refactor: integrate shell and vmm modules directly into kernel --- kernel/Cargo.toml | 10 +- kernel/src/main.rs | 9 +- kernel/src/shell/command/mod.rs | 566 ------- kernel/src/shell/command/vm.rs | 1399 ----------------- kernel/src/shell/commands/builtin.rs | 129 ++ .../shell/{command/base.rs => commands/fs.rs} | 150 +- kernel/src/shell/commands/mod.rs | 239 +++ kernel/src/shell/commands/vm.rs | 680 ++++++++ kernel/src/shell/completion.rs | 290 ++++ kernel/src/shell/mod.rs | 255 +-- .../src/shell/{command => parser}/history.rs | 10 + kernel/src/shell/parser/mod.rs | 11 + kernel/src/shell/parser/node.rs | 172 ++ kernel/src/shell/parser/parser.rs | 241 +++ kernel/src/shell/shell.rs | 385 +++++ kernel/src/vmm/config.rs | 3 +- kernel/src/vmm/fdt/create.rs | 469 ------ kernel/src/vmm/fdt/device.rs | 508 ------ kernel/src/vmm/fdt/mod.rs | 122 -- kernel/src/vmm/fdt/parser.rs | 397 ----- kernel/src/vmm/fdt/print.rs | 135 -- kernel/src/vmm/images/mod.rs | 71 +- kernel/src/vmm/mod.rs | 6 +- kernel/src/vmm/vcpus.rs | 2 +- kernel/src/vmm/vm_list.rs | 1 - modules/axfs/src/fs/fatfs.rs | 2 +- modules/axvm | 2 +- 27 files changed, 2302 insertions(+), 3962 deletions(-) delete mode 100644 kernel/src/shell/command/mod.rs delete mode 100644 kernel/src/shell/command/vm.rs create mode 100644 kernel/src/shell/commands/builtin.rs rename kernel/src/shell/{command/base.rs => commands/fs.rs} (84%) create mode 100644 kernel/src/shell/commands/mod.rs create mode 100644 kernel/src/shell/commands/vm.rs create mode 100644 kernel/src/shell/completion.rs rename kernel/src/shell/{command => parser}/history.rs (80%) create mode 100644 kernel/src/shell/parser/mod.rs create mode 100644 kernel/src/shell/parser/node.rs create mode 100644 kernel/src/shell/parser/parser.rs create mode 100644 kernel/src/shell/shell.rs delete mode 100644 kernel/src/vmm/fdt/create.rs delete mode 100644 kernel/src/vmm/fdt/device.rs delete mode 100644 kernel/src/vmm/fdt/mod.rs delete mode 100644 kernel/src/vmm/fdt/parser.rs delete mode 100644 kernel/src/vmm/fdt/print.rs diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 492c3aea..e17fb82d 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -16,10 +16,8 @@ cfg-if.workspace = true cpumask.workspace = true kernel_guard.workspace = true kspin = "0.1" -lazy_static = {version = "1.5", default-features = false, features = ["spin_no_std"]} lazyinit = "0.2" log = "0.4" -spin = "0.9" timer_list = "0.1.0" toml = {version = "0.9", default-features = false} @@ -29,7 +27,7 @@ axstd.workspace = true # System dependent modules provided by ArceOS-Hypervisor. axhvc.workspace = true axvm.workspace = true -axvmconfig = {workspace = true} +axvmconfig = { workspace = true } # System independent crates provided by ArceOS, these crates could be imported by remote url. axerrno.workspace = true @@ -37,6 +35,12 @@ byte-unit = {version = "5", default-features = false, features = ["byte"]} extern-trait = "0.2" memory_addr.workspace = true driver.workspace = true +spin = "0.9" + +# axvm = { path = "../axvm" } +# axvmconfig = { version = "0.1", default-features = false } + +lazy_static = {version = "1.5", default-features = false, features = ["spin_no_std"]} [build-dependencies] anyhow = "1.0" diff --git a/kernel/src/main.rs b/kernel/src/main.rs index a6396ec1..e8b65bea 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -8,15 +8,18 @@ extern crate log; extern crate alloc; extern crate axstd as std; +extern crate driver; // extern crate axruntime; -extern crate driver; mod logo; -// mod shell; mod task; +mod shell; mod vmm; +pub use shell::*; +pub use vmm::*; + #[unsafe(no_mangle)] fn main() { logo::print_logo(); @@ -30,5 +33,5 @@ fn main() { info!("[OK] Default guest initialized"); vmm::wait_for_all_vms_exit(); info!("All guest VMs exited."); - // shell::console_init(); + shell::console_init(); } diff --git a/kernel/src/shell/command/mod.rs b/kernel/src/shell/command/mod.rs deleted file mode 100644 index 954a20bf..00000000 --- a/kernel/src/shell/command/mod.rs +++ /dev/null @@ -1,566 +0,0 @@ -mod base; -mod history; -mod vm; - -pub use base::*; -pub use history::*; -pub use vm::*; - -use std::io::prelude::*; -use std::string::String; -use std::vec::Vec; -use std::{collections::BTreeMap, string::ToString}; -use std::{print, println}; - -lazy_static::lazy_static! { - pub static ref COMMAND_TREE: BTreeMap = build_command_tree(); -} - -#[derive(Debug, Clone)] -pub struct CommandNode { - handler: Option, - subcommands: BTreeMap, - description: &'static str, - usage: Option<&'static str>, - #[allow(dead_code)] - log_level: log::LevelFilter, - options: Vec, - flags: Vec, -} - -#[derive(Debug, Clone)] -pub struct OptionDef { - name: &'static str, - short: Option, - long: Option<&'static str>, - description: &'static str, - required: bool, -} - -#[derive(Debug, Clone)] -pub struct FlagDef { - name: &'static str, - short: Option, - long: Option<&'static str>, - description: &'static str, -} - -#[derive(Debug, Clone)] -pub struct ParsedCommand { - pub command_path: Vec, - pub options: BTreeMap, - pub flags: BTreeMap, - pub positional_args: Vec, -} - -#[derive(Debug)] -pub enum ParseError { - UnknownCommand(String), - UnknownOption(String), - MissingValue(String), - MissingRequiredOption(String), - NoHandler(String), -} - -impl CommandNode { - pub fn new(description: &'static str) -> Self { - Self { - handler: None, - subcommands: BTreeMap::new(), - description, - usage: None, - log_level: log::LevelFilter::Off, - options: Vec::new(), - flags: Vec::new(), - } - } - - pub fn with_handler(mut self, handler: fn(&ParsedCommand)) -> Self { - self.handler = Some(handler); - self - } - - pub fn with_usage(mut self, usage: &'static str) -> Self { - self.usage = Some(usage); - self - } - - #[allow(dead_code)] - pub fn with_log_level(mut self, level: log::LevelFilter) -> Self { - self.log_level = level; - self - } - - pub fn with_option(mut self, option: OptionDef) -> Self { - self.options.push(option); - self - } - - pub fn with_flag(mut self, flag: FlagDef) -> Self { - self.flags.push(flag); - self - } - - pub fn add_subcommand>(mut self, name: S, node: CommandNode) -> Self { - self.subcommands.insert(name.into(), node); - self - } -} - -impl OptionDef { - pub fn new(name: &'static str, description: &'static str) -> Self { - Self { - name, - short: None, - long: None, - description, - required: false, - } - } - - #[allow(dead_code)] - pub fn with_short(mut self, short: char) -> Self { - self.short = Some(short); - self - } - - pub fn with_long(mut self, long: &'static str) -> Self { - self.long = Some(long); - self - } - - #[allow(dead_code)] - pub fn required(mut self) -> Self { - self.required = true; - self - } -} - -impl FlagDef { - pub fn new(name: &'static str, description: &'static str) -> Self { - Self { - name, - short: None, - long: None, - description, - } - } - - pub fn with_short(mut self, short: char) -> Self { - self.short = Some(short); - self - } - - pub fn with_long(mut self, long: &'static str) -> Self { - self.long = Some(long); - self - } -} - -// Command Parser -pub struct CommandParser; - -impl CommandParser { - pub fn parse(input: &str) -> Result { - let tokens = Self::tokenize(input); - if tokens.is_empty() { - return Err(ParseError::UnknownCommand("empty command".to_string())); - } - - // Find the command path - let (command_path, command_node, remaining_tokens) = Self::find_command(&tokens)?; - - // Parse the arguments - let (options, flags, positional_args) = Self::parse_args(remaining_tokens, command_node)?; - - // Validate required options - Self::validate_required_options(command_node, &options)?; - - Ok(ParsedCommand { - command_path, - options, - flags, - positional_args, - }) - } - - fn tokenize(input: &str) -> Vec { - let mut tokens = Vec::new(); - let mut current_token = String::new(); - let mut in_quotes = false; - let mut escape_next = false; - - for ch in input.chars() { - if escape_next { - current_token.push(ch); - escape_next = false; - } else if ch == '\\' { - escape_next = true; - } else if ch == '"' { - in_quotes = !in_quotes; - } else if ch.is_whitespace() && !in_quotes { - if !current_token.is_empty() { - tokens.push(current_token.clone()); - current_token.clear(); - } - } else { - current_token.push(ch); - } - } - - if !current_token.is_empty() { - tokens.push(current_token); - } - - tokens - } - - fn find_command( - tokens: &[String], - ) -> Result<(Vec, &CommandNode, &[String]), ParseError> { - let mut current_node = COMMAND_TREE - .get(&tokens[0]) - .ok_or_else(|| ParseError::UnknownCommand(tokens[0].clone()))?; - - let mut command_path = vec![tokens[0].clone()]; - let mut token_index = 1; - - // Traverse to find the deepest command node - while token_index < tokens.len() { - if let Some(subcommand) = current_node.subcommands.get(&tokens[token_index]) { - current_node = subcommand; - command_path.push(tokens[token_index].clone()); - token_index += 1; - } else { - break; - } - } - - Ok((command_path, current_node, &tokens[token_index..])) - } - - #[allow(clippy::type_complexity)] - fn parse_args( - tokens: &[String], - command_node: &CommandNode, - ) -> Result< - ( - BTreeMap, - BTreeMap, - Vec, - ), - ParseError, - > { - let mut options = BTreeMap::new(); - let mut flags = BTreeMap::new(); - let mut positional_args = Vec::new(); - let mut i = 0; - - while i < tokens.len() { - let token = &tokens[i]; - - if let Some(name) = token.strip_prefix("--") { - // Long options/flags - if let Some(eq_pos) = name.find('=') { - // --option=value format - let (opt_name, value) = name.split_at(eq_pos); - let value = &value[1..]; // Skip '=' - if Self::is_option(opt_name, command_node) { - options.insert(opt_name.to_string(), value.to_string()); - } else { - return Err(ParseError::UnknownOption(format!("--{opt_name}"))); - } - } else if Self::is_flag(name, command_node) { - flags.insert(name.to_string(), true); - } else if Self::is_option(name, command_node) { - // --option value format - if i + 1 >= tokens.len() { - return Err(ParseError::MissingValue(format!("--{name}"))); - } - options.insert(name.to_string(), tokens[i + 1].clone()); - i += 1; // Skip value - } else { - return Err(ParseError::UnknownOption(format!("--{name}"))); - } - } else if token.starts_with('-') && token.len() > 1 { - // Short options/flags - let chars: Vec = token[1..].chars().collect(); - for (j, &ch) in chars.iter().enumerate() { - if Self::is_short_flag(ch, command_node) { - flags.insert( - Self::get_flag_name_by_short(ch, command_node) - .unwrap() - .to_string(), - true, - ); - } else if Self::is_short_option(ch, command_node) { - let opt_name = Self::get_option_name_by_short(ch, command_node).unwrap(); - if j == chars.len() - 1 && i + 1 < tokens.len() { - // Last character and there is a next token as value - options.insert(opt_name.to_string(), tokens[i + 1].clone()); - i += 1; // Skip value - } else { - return Err(ParseError::MissingValue(format!("-{ch}"))); - } - } else { - return Err(ParseError::UnknownOption(format!("-{ch}"))); - } - } - } else { - // Positional arguments - positional_args.push(token.clone()); - } - i += 1; - } - - Ok((options, flags, positional_args)) - } - - fn is_option(name: &str, node: &CommandNode) -> bool { - node.options - .iter() - .any(|opt| (opt.long == Some(name)) || opt.name == name) - } - - fn is_flag(name: &str, node: &CommandNode) -> bool { - node.flags - .iter() - .any(|flag| (flag.long == Some(name)) || flag.name == name) - } - - fn is_short_option(ch: char, node: &CommandNode) -> bool { - node.options.iter().any(|opt| opt.short == Some(ch)) - } - - fn is_short_flag(ch: char, node: &CommandNode) -> bool { - node.flags.iter().any(|flag| flag.short == Some(ch)) - } - - fn get_option_name_by_short(ch: char, node: &CommandNode) -> Option<&str> { - node.options - .iter() - .find(|opt| opt.short == Some(ch)) - .map(|opt| opt.name) - } - - fn get_flag_name_by_short(ch: char, node: &CommandNode) -> Option<&str> { - node.flags - .iter() - .find(|flag| flag.short == Some(ch)) - .map(|flag| flag.name) - } - - fn validate_required_options( - node: &CommandNode, - options: &BTreeMap, - ) -> Result<(), ParseError> { - for option in &node.options { - if option.required && !options.contains_key(option.name) { - return Err(ParseError::MissingRequiredOption(option.name.to_string())); - } - } - Ok(()) - } -} - -// Command execution function -pub fn execute_command(input: &str) -> Result<(), ParseError> { - let parsed = CommandParser::parse(input)?; - - // Find the corresponding command node - let mut current_node = COMMAND_TREE.get(&parsed.command_path[0]).unwrap(); - for cmd in &parsed.command_path[1..] { - current_node = current_node.subcommands.get(cmd).unwrap(); - } - - // Execute the command - if let Some(handler) = current_node.handler { - handler(&parsed); - Ok(()) - } else { - Err(ParseError::NoHandler(parsed.command_path.join(" "))) - } -} - -// Build command tree -fn build_command_tree() -> BTreeMap { - let mut tree = BTreeMap::new(); - - build_base_cmd(&mut tree); - build_vm_cmd(&mut tree); - - tree -} - -// Helper function: Display command help -pub fn show_help(command_path: &[String]) -> Result<(), ParseError> { - let mut current_node = COMMAND_TREE - .get(&command_path[0]) - .ok_or_else(|| ParseError::UnknownCommand(command_path[0].clone()))?; - - for cmd in &command_path[1..] { - current_node = current_node - .subcommands - .get(cmd) - .ok_or_else(|| ParseError::UnknownCommand(cmd.clone()))?; - } - - println!("Command: {}", command_path.join(" ")); - println!("Description: {}", current_node.description); - - if let Some(usage) = current_node.usage { - println!("Usage: {}", usage); - } - - if !current_node.options.is_empty() { - println!("\nOptions:"); - for option in ¤t_node.options { - let mut opt_str = String::new(); - if let Some(short) = option.short { - opt_str.push_str(&format!("-{short}")); - } - if let Some(long) = option.long { - if !opt_str.is_empty() { - opt_str.push_str(", "); - } - opt_str.push_str(&format!("--{long}")); - } - if opt_str.is_empty() { - opt_str = option.name.to_string(); - } - - let required_str = if option.required { " (required)" } else { "" }; - println!(" {:<20} {}{}", opt_str, option.description, required_str); - } - } - - if !current_node.flags.is_empty() { - println!("\nFlags:"); - for flag in ¤t_node.flags { - let mut flag_str = String::new(); - if let Some(short) = flag.short { - flag_str.push_str(&format!("-{short}")); - } - if let Some(long) = flag.long { - if !flag_str.is_empty() { - flag_str.push_str(", "); - } - flag_str.push_str(&format!("--{long}")); - } - if flag_str.is_empty() { - flag_str = flag.name.to_string(); - } - - println!(" {:<20} {}", flag_str, flag.description); - } - } - - if !current_node.subcommands.is_empty() { - println!("\nSubcommands:"); - for (name, node) in ¤t_node.subcommands { - println!(" {:<20} {}", name, node.description); - } - } - - Ok(()) -} - -pub fn print_prompt() { - #[cfg(feature = "fs")] - print!("axvisor:{}$ ", std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - print!("axvisor:$ "); - std::io::stdout().flush().unwrap(); -} - -pub fn run_cmd_bytes(cmd_bytes: &[u8]) { - match str::from_utf8(cmd_bytes) { - Ok(cmd_str) => { - let trimmed = cmd_str.trim(); - if trimmed.is_empty() { - return; - } - - match execute_command(trimmed) { - Ok(_) => { - // Command executed successfully - } - Err(ParseError::UnknownCommand(cmd)) => { - println!("Error: Unknown command '{}'", cmd); - println!("Type 'help' to see available commands"); - } - Err(ParseError::UnknownOption(opt)) => { - println!("Error: Unknown option '{}'", opt); - } - Err(ParseError::MissingValue(opt)) => { - println!("Error: Option '{}' is missing a value", opt); - } - Err(ParseError::MissingRequiredOption(opt)) => { - println!("Error: Missing required option '{}'", opt); - } - Err(ParseError::NoHandler(cmd)) => { - println!("Error: Command '{}' has no handler function", cmd); - } - } - } - Err(_) => { - println!("Error: Input contains invalid UTF-8 characters"); - } - } -} - -// Built-in command handler -pub fn handle_builtin_commands(input: &str) -> bool { - match input.trim() { - "help" => { - show_available_commands(); - true - } - "exit" | "quit" => { - println!("Goodbye!"); - std::process::exit(0); - } - "clear" => { - print!("\x1b[2J\x1b[H"); // ANSI clear screen sequence - std::io::stdout().flush().unwrap(); - true - } - _ if input.starts_with("help ") => { - let cmd_parts: Vec = input[5..] - .split_whitespace() - .map(|s| s.to_string()) - .collect(); - if let Err(e) = show_help(&cmd_parts) { - println!("Error: {:?}", e); - } - true - } - _ => false, - } -} - -pub fn show_available_commands() { - println!("ArceOS Shell - Available Commands:"); - println!(); - - // Display all top-level commands - for (name, node) in COMMAND_TREE.iter() { - println!(" {:<15} {}", name, node.description); - - // Display subcommands - if !node.subcommands.is_empty() { - for (sub_name, sub_node) in &node.subcommands { - println!(" {:<13} {}", sub_name, sub_node.description); - } - } - } - - println!(); - println!("Built-in Commands:"); - println!(" help Show help information"); - println!(" help Show help for a specific command"); - println!(" clear Clear the screen"); - println!(" exit/quit Exit the shell"); - println!(); - println!("Tip: Use 'help ' to see detailed usage of a command"); -} diff --git a/kernel/src/shell/command/vm.rs b/kernel/src/shell/command/vm.rs deleted file mode 100644 index c54fe2e6..00000000 --- a/kernel/src/shell/command/vm.rs +++ /dev/null @@ -1,1399 +0,0 @@ -use std::{ - collections::btree_map::BTreeMap, - println, - string::{String, ToString}, - vec::Vec, -}; - -use axvm::VMStatus; -#[cfg(feature = "fs")] -use std::fs::read_to_string; - -use crate::{ - shell::command::{CommandNode, FlagDef, OptionDef, ParsedCommand}, - vmm::{add_running_vm_count, vcpus, vm_list, with_vm}, -}; - -/// Check if a VM can transition to Running state. -/// Returns Ok(()) if the transition is valid, Err with a message otherwise. -fn can_start_vm(status: VMStatus) -> Result<(), &'static str> { - match status { - VMStatus::Loaded | VMStatus::Stopped => Ok(()), - VMStatus::Running => Err("VM is already running"), - VMStatus::Suspended => Err("VM is suspended, use 'vm resume' instead"), - VMStatus::Stopping => Err("VM is stopping, wait for it to fully stop"), - VMStatus::Loading => Err("VM is still loading"), - } -} - -/// Check if a VM can transition to Stopping state. -/// Returns Ok(()) if the transition is valid, Err with a message otherwise. -fn can_stop_vm(status: VMStatus, force: bool) -> Result<(), &'static str> { - match status { - VMStatus::Running | VMStatus::Suspended => Ok(()), - VMStatus::Stopping => { - if force { - Ok(()) - } else { - Err("VM is already stopping") - } - } - VMStatus::Stopped => Err("VM is already stopped"), - VMStatus::Loading | VMStatus::Loaded => Ok(()), // Allow stopping VMs in these states - } -} - -/// Check if a VM can be suspended. -fn can_suspend_vm(status: VMStatus) -> Result<(), &'static str> { - match status { - VMStatus::Running => Ok(()), - VMStatus::Suspended => Err("VM is already suspended"), - VMStatus::Stopped => Err("VM is stopped, cannot suspend"), - VMStatus::Stopping => Err("VM is stopping, cannot suspend"), - VMStatus::Loading => Err("VM is loading, cannot suspend"), - VMStatus::Loaded => Err("VM is not running, cannot suspend"), - } -} - -/// Check if a VM can be resumed. -fn can_resume_vm(status: VMStatus) -> Result<(), &'static str> { - match status { - VMStatus::Suspended => Ok(()), - VMStatus::Running => Err("VM is already running"), - VMStatus::Stopped => Err("VM is stopped, use 'vm start' instead"), - VMStatus::Stopping => Err("VM is stopping, cannot resume"), - VMStatus::Loading => Err("VM is loading, cannot resume"), - VMStatus::Loaded => Err("VM is not started yet, use 'vm start' instead"), - } -} - -/// Format memory size in a human-readable way. -fn format_memory_size(bytes: usize) -> String { - if bytes < 1024 { - format!("{}B", bytes) - } else if bytes < 1024 * 1024 { - format!("{}KB", bytes / 1024) - } else if bytes < 1024 * 1024 * 1024 { - format!("{}MB", bytes / (1024 * 1024)) - } else { - format!("{}GB", bytes / (1024 * 1024 * 1024)) - } -} - -// ============================================================================ -// Command Handlers -// ============================================================================ - -fn vm_help(_cmd: &ParsedCommand) { - println!("VM - virtual machine management"); - println!(); - println!("Most commonly used vm commands:"); - println!(" create Create a new virtual machine"); - println!(" start Start a virtual machine"); - println!(" stop Stop a virtual machine"); - println!(" suspend Suspend (pause) a running virtual machine"); - println!(" resume Resume a suspended virtual machine"); - println!(" restart Restart a virtual machine"); - println!(" delete Delete a virtual machine"); - println!(); - println!("Information commands:"); - println!(" list Show table of all VMs"); - println!(" show Show VM details (requires VM_ID)"); - println!(" - Default: basic information"); - println!(" - --full: complete detailed information"); - println!(" - --config: show configuration"); - println!(" - --stats: show statistics"); - println!(); - println!("Use 'vm --help' for more information on a specific command."); -} - -#[cfg(feature = "fs")] -fn vm_create(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - - println!("Positional args: {:?}", args); - - if args.is_empty() { - println!("Error: No VM configuration file specified"); - println!("Usage: vm create [CONFIG_FILE]"); - return; - } - - let initial_vm_count = vm_list::get_vm_list().len(); - - for config_path in args.iter() { - println!("Creating VM from config: {}", config_path); - - use crate::vmm::config::init_guest_vm; - match read_to_string(config_path) { - Ok(raw_cfg) => match init_guest_vm(&raw_cfg) { - Ok(vm_id) => { - println!( - "✓ Successfully created VM[{}] from config: {}", - vm_id, config_path - ); - } - Err(_) => { - println!( - "✗ Failed to create VM from {}: Configuration error or panic occurred", - config_path - ); - } - }, - Err(e) => { - println!("✗ Failed to read config file {}: {:?}", config_path, e); - } - } - } - - // Check the actual number of VMs created - let final_vm_count = vm_list::get_vm_list().len(); - let created_count = final_vm_count - initial_vm_count; - - if created_count > 0 { - println!("Successfully created {} VM(s)", created_count); - println!("Use 'vm start ' to start the created VMs."); - } else { - println!("No VMs were created."); - } -} - -#[cfg(feature = "fs")] -fn vm_start(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - let detach = cmd.flags.get("detach").unwrap_or(&false); - - if args.is_empty() { - // start all VMs - info!("VMM starting, booting all VMs..."); - let mut started_count = 0; - - for vm in vm_list::get_vm_list() { - // Check current status before starting - let status: VMStatus = vm.vm_status(); - if status == VMStatus::Running { - println!("⚠ VM[{}] is already running, skipping", vm.id()); - continue; - } - - if status != VMStatus::Loaded && status != VMStatus::Stopped { - println!("⚠ VM[{}] is in {:?} state, cannot start", vm.id(), status); - continue; - } - - if let Err(e) = start_single_vm(vm.clone()) { - println!("✗ VM[{}] failed to start: {:?}", vm.id(), e); - } else { - println!("✓ VM[{}] started successfully", vm.id()); - started_count += 1; - } - } - println!("Started {} VM(s)", started_count); - } else { - // Start specified VMs - for vm_name in args { - // Try to parse as VM ID or lookup VM name - if let Ok(vm_id) = vm_name.parse::() { - start_vm_by_id(vm_id); - } else { - println!("Error: VM name lookup not implemented. Use VM ID instead."); - println!("Available VMs:"); - vm_list_simple(); - } - } - } - - if *detach { - println!("VMs started in background mode"); - } -} - -/// Start a single VM by setting up vCPUs and calling boot. -/// Returns Ok(()) if successful, Err otherwise. -fn start_single_vm(vm: crate::vmm::VMRef) -> Result<(), &'static str> { - let vm_id = vm.id(); - let status = vm.vm_status(); - - // Validate state transition using helper function - can_start_vm(status)?; - - // Set up primary virtual CPU before starting - vcpus::setup_vm_primary_vcpu(vm.clone()); - - // Boot the VM - match vm.boot() { - Ok(_) => { - // Transition to Running state and notify the primary VCpu - // Note: Since the VCpu task is created directly in the wait queue (blocked state), - // we can immediately notify it without waiting for it to be scheduled first. - vcpus::notify_primary_vcpu(vm_id); - add_running_vm_count(1); - Ok(()) - } - Err(err) => { - // Revert status on failure - error!("Failed to boot VM[{}]: {:?}", vm_id, err); - Err("Failed to boot VM") - } - } -} - -fn start_vm_by_id(vm_id: usize) { - match with_vm(vm_id, |vm| start_single_vm(vm.clone())) { - Some(Ok(_)) => { - println!("✓ VM[{}] started successfully", vm_id); - } - Some(Err(err)) => { - println!("✗ VM[{}] failed to start: {}", vm_id, err); - } - None => { - println!("✗ VM[{}] not found", vm_id); - } - } -} - -fn vm_stop(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - let force = cmd.flags.get("force").unwrap_or(&false); - - if args.is_empty() { - println!("Error: No VM specified"); - println!("Usage: vm stop [OPTIONS] "); - return; - } - - for vm_name in args { - if let Ok(vm_id) = vm_name.parse::() { - stop_vm_by_id(vm_id, *force); - } else { - println!("Error: Invalid VM ID: {}", vm_name); - } - } -} - -fn stop_vm_by_id(vm_id: usize, force: bool) { - match with_vm(vm_id, |vm| { - let status = vm.vm_status(); - - // Validate state transition using helper function - if let Err(err) = can_stop_vm(status, force) { - println!("⚠ VM[{}] {}", vm_id, err); - return Err(err); - } - - // Print appropriate message based on status - match status { - VMStatus::Stopping if force => { - println!("Force stopping VM[{}]...", vm_id); - } - VMStatus::Running => { - if force { - println!("Force stopping VM[{}]...", vm_id); - } else { - println!("Gracefully stopping VM[{}]...", vm_id); - } - } - VMStatus::Loading | VMStatus::Loaded => { - println!( - "⚠ VM[{}] is in {:?} state, stopping anyway...", - vm_id, status - ); - } - _ => {} - } - - // Call shutdown - match vm.shutdown() { - Ok(_) => Ok(()), - Err(_err) => { - // Revert status on failure - Err("Failed to shutdown VM") - } - } - }) { - Some(Ok(_)) => { - println!("✓ VM[{}] stop signal sent successfully", vm_id); - println!( - " Note: vCPU threads will exit gracefully, VM status will transition to Stopped" - ); - } - Some(Err(err)) => { - println!("✗ Failed to stop VM[{}]: {:?}", vm_id, err); - } - None => { - println!("✗ VM[{}] not found", vm_id); - } - } -} - -/// Restart a VM by stopping it (if running) and then starting it again.(functionality incomplete) -fn vm_restart(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - let force = cmd.flags.get("force").unwrap_or(&false); - - if args.is_empty() { - println!("Error: No VM specified"); - println!("Usage: vm restart [OPTIONS] "); - return; - } - - for vm_name in args { - if let Ok(vm_id) = vm_name.parse::() { - restart_vm_by_id(vm_id, *force); - } else { - println!("Error: Invalid VM ID: {}", vm_name); - } - } -} - -fn restart_vm_by_id(vm_id: usize, force: bool) { - println!("Restarting VM[{}]...", vm_id); - - // Check current status - let current_status = with_vm(vm_id, |vm| vm.vm_status()); - if current_status.is_none() { - println!("✗ VM[{}] not found", vm_id); - return; - } - - let status = current_status.unwrap(); - match status { - VMStatus::Stopped | VMStatus::Loaded => { - // VM is already stopped, just start it - println!("VM[{}] is already stopped, starting...", vm_id); - start_vm_by_id(vm_id); - } - VMStatus::Suspended | VMStatus::Running => { - // Stop the VM (this will wake up suspended VCpus automatically) - println!("Stopping VM[{}]...", vm_id); - stop_vm_by_id(vm_id, force); - - // Wait for VM to fully stop - println!("Waiting for VM[{}] to stop completely...", vm_id); - let max_wait_iterations = 50; // 5 seconds timeout (50 * 100ms) - let mut iterations = 0; - - loop { - if let Some(vm_status) = with_vm(vm_id, |vm| vm.vm_status()) { - match vm_status { - VMStatus::Stopped => { - println!("✓ VM[{}] stopped successfully", vm_id); - break; - } - VMStatus::Stopping => { - // Still stopping, wait a bit - iterations += 1; - if iterations >= max_wait_iterations { - println!( - "⚠ VM[{}] stop timeout, it may still be shutting down", - vm_id - ); - println!(" Use 'vm status {}' to check status manually", vm_id); - return; - } - // Sleep for 100ms - std::os::arceos::modules::axhal::time::busy_wait( - core::time::Duration::from_millis(100), - ); - } - _ => { - println!("⚠ VM[{}] in unexpected state: {:?}", vm_id, vm_status); - return; - } - } - } else { - println!("✗ VM[{}] no longer exists", vm_id); - return; - } - } - - // Now restart the VM - println!("Starting VM[{}]...", vm_id); - start_vm_by_id(vm_id); - } - VMStatus::Stopping => { - if force { - println!( - "⚠ VM[{}] is currently stopping, waiting for shutdown to complete...", - vm_id - ); - // Could implement similar wait logic here if needed - } else { - println!("⚠ VM[{}] is currently stopping", vm_id); - println!( - " Wait for shutdown to complete, then use 'vm start {}'", - vm_id - ); - println!(" Or use --force to wait and then restart"); - } - } - VMStatus::Loading => { - println!("✗ VM[{}] is still loading, cannot restart", vm_id); - } - } -} - -/// Suspend a running VM (functionality incomplete) -fn vm_suspend(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - - if args.is_empty() { - println!("Error: No VM specified"); - println!("Usage: vm suspend ..."); - return; - } - - for vm_name in args { - if let Ok(vm_id) = vm_name.parse::() { - suspend_vm_by_id(vm_id); - } else { - println!("Error: Invalid VM ID: {}", vm_name); - } - } -} - -fn suspend_vm_by_id(vm_id: usize) { - println!("Suspending VM[{}]...", vm_id); - - let result = with_vm(vm_id, |vm| { - let status = vm.vm_status(); - - // Check if VM can be suspended - if let Err(err_msg) = can_suspend_vm(status) { - return Err(err_msg); - } - - // Set VM status to Suspended - vm.set_vm_status(VMStatus::Suspended); - info!("VM[{}] status set to Suspended", vm_id); - - Ok(()) - }); - - match result { - Some(Ok(_)) => { - println!("✓ VM[{}] suspend signal sent", vm_id); - - // Get VM to check VCpu count - let vcpu_count = with_vm(vm_id, |vm| vm.vcpu_num()).unwrap_or(0); - println!( - " Note: {} VCpu task(s) will enter wait queue at next VMExit", - vcpu_count - ); - - // Wait a brief moment for VCpus to enter suspended state - println!(" Waiting for VCpus to suspend..."); - let max_wait_iterations = 10; // 1 second timeout (10 * 100ms) - let mut iterations = 0; - let mut all_suspended = false; - - while iterations < max_wait_iterations { - // Check if all VCpus are in blocked state - if let Some(vm) = crate::vmm::vm_list::get_vm_by_id(vm_id) { - let vcpu_states: Vec<_> = - vm.vcpu_list().iter().map(|vcpu| vcpu.state()).collect(); - - let blocked_count = vcpu_states - .iter() - .filter(|s| matches!(s, axvcpu::VCpuState::Blocked)) - .count(); - - if blocked_count == vcpu_states.len() { - all_suspended = true; - break; - } - - // Show progress for the first few iterations - if iterations < 3 { - debug!(" VCpus blocked: {}/{}", blocked_count, vcpu_states.len()); - } - } - - iterations += 1; - std::os::arceos::modules::axhal::time::busy_wait( - core::time::Duration::from_millis(100), - ); - } - - if all_suspended { - println!("✓ All VCpu tasks are now suspended"); - } else { - println!("⚠ Some VCpu tasks may still be transitioning to suspended state"); - println!(" VCpus will suspend at next VMExit (timer interrupt, I/O, etc.)"); - println!(" This is normal for VMs with low interrupt rates"); - } - - println!(" Use 'vm resume {}' to resume the VM", vm_id); - } - Some(Err(err)) => { - println!("✗ Failed to suspend VM[{}]: {}", vm_id, err); - } - None => { - println!("✗ VM[{}] not found", vm_id); - } - } -} - -// Resume a suspended VM (functionality incomplete) -fn vm_resume(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - - if args.is_empty() { - println!("Error: No VM specified"); - println!("Usage: vm resume ..."); - return; - } - - for vm_name in args { - if let Ok(vm_id) = vm_name.parse::() { - resume_vm_by_id(vm_id); - } else { - println!("Error: Invalid VM ID: {}", vm_name); - } - } -} - -fn resume_vm_by_id(vm_id: usize) { - println!("Resuming VM[{}]...", vm_id); - - let result = with_vm(vm_id, |vm| { - let status = vm.vm_status(); - - // Check if VM can be resumed - if let Err(err_msg) = can_resume_vm(status) { - return Err(err_msg); - } - - // Set VM status back to Running - vm.set_vm_status(VMStatus::Running); - - // Notify all VCpus to wake up - vcpus::notify_all_vcpus(vm_id); - - info!("VM[{}] resumed", vm_id); - Ok(()) - }); - - match result { - Some(Ok(_)) => { - println!("✓ VM[{}] resumed successfully", vm_id); - } - Some(Err(err)) => { - println!("✗ Failed to resume VM[{}]: {}", vm_id, err); - } - None => { - println!("✗ VM[{}] not found", vm_id); - } - } -} - -fn vm_delete(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - let force = cmd.flags.get("force").unwrap_or(&false); - let keep_data = cmd.flags.get("keep-data").unwrap_or(&false); - - if args.is_empty() { - println!("Error: No VM specified"); - println!("Usage: vm delete [OPTIONS] "); - return; - } - - let vm_name = &args[0]; - - if let Ok(vm_id) = vm_name.parse::() { - // Check if VM exists and get its status first - let vm_status = with_vm(vm_id, |vm| vm.vm_status()); - - if vm_status.is_none() { - println!("✗ VM[{}] not found", vm_id); - return; - } - - let status = vm_status.unwrap(); - - // Check if VM is running - match status { - VMStatus::Running => { - if !force { - println!("✗ VM[{}] is currently running", vm_id); - println!( - " Use 'vm stop {}' first, or use '--force' to force delete", - vm_id - ); - return; - } - println!("⚠ Force deleting running VM[{}]...", vm_id); - } - VMStatus::Stopping => { - if !force { - println!("⚠ VM[{}] is currently stopping", vm_id); - println!(" Wait for it to stop completely, or use '--force' to force delete"); - return; - } - println!("⚠ Force deleting stopping VM[{}]...", vm_id); - } - VMStatus::Stopped => { - println!("Deleting stopped VM[{}]...", vm_id); - } - _ => { - println!("⚠ VM[{}] is in {:?} state", vm_id, status); - if !force { - println!("Use --force to force delete"); - return; - } - } - } - - delete_vm_by_id(vm_id, *keep_data); - } else { - println!("Error: Invalid VM ID: {}", vm_name); - } -} - -fn delete_vm_by_id(vm_id: usize, keep_data: bool) { - // First check VM status and try to stop it if running - let vm_status = with_vm(vm_id, |vm| { - let status = vm.vm_status(); - - // If VM is running, suspended, or stopping, send shutdown signal - match status { - VMStatus::Running | VMStatus::Suspended | VMStatus::Stopping => { - println!( - " VM[{}] is {:?}, sending shutdown signal...", - vm_id, status - ); - vm.set_vm_status(VMStatus::Stopping); - let _ = vm.shutdown(); - } - VMStatus::Loaded => { - // Transition from Loaded to Stopped - vm.set_vm_status(VMStatus::Stopped); - } - _ => {} - } - - use alloc::sync::Arc; - let count = Arc::strong_count(&vm); - println!(" [Debug] VM Arc strong_count: {}", count); - - status - }); - - if vm_status.is_none() { - println!("✗ VM[{}] not found or already removed", vm_id); - return; - } - - let status = vm_status.unwrap(); - - // Remove VM from global list - // Note: This drops the reference from the global list, but the VM object - // will only be fully destroyed when all vCPU threads exit and drop their references - match crate::vmm::vm_list::remove_vm(vm_id) { - Some(vm) => { - println!("✓ VM[{}] removed from VM list", vm_id); - - // Wait for vCPU threads to exit if VM has VCpu tasks - match status { - VMStatus::Running - | VMStatus::Suspended - | VMStatus::Stopping - | VMStatus::Stopped => { - println!(" Waiting for vCPU threads to exit..."); - - // Debug: Check Arc count before cleanup - use alloc::sync::Arc; - println!( - " [Debug] VM Arc count before cleanup: {}", - Arc::strong_count(&vm) - ); - - // Clean up VCpu resources after threads have exited - println!(" Cleaning up VCpu resources..."); - vcpus::cleanup_vm_vcpus(vm_id); - - // Debug: Check Arc count after final wait - println!( - " [Debug] VM Arc count after final wait: {}", - Arc::strong_count(&vm) - ); - } - _ => { - // VM not running, no vCPU threads to wait for - // But still need to clean up VCpu queue entry if it exists - vcpus::cleanup_vm_vcpus(vm_id); - } - } - - if keep_data { - println!("✓ VM[{}] deleted (configuration and data preserved)", vm_id); - } else { - println!("✓ VM[{}] deleted completely", vm_id); - - // Debug: Check Arc count - should be 1 now (only this variable) - // TaskExt uses Weak reference, so it doesn't count - use alloc::sync::Arc; - let count = Arc::strong_count(&vm); - println!(" [Debug] VM Arc strong_count: {}", count); - - if count == 1 { - println!(" ✓ Perfect! VM will be freed immediately when function returns"); - } else { - println!( - " ⚠ Warning: Unexpected Arc count {}, possible reference leak!", - count - ); - } - - // TODO: Clean up VM-related data files - // - Remove disk images - // - Remove configuration files - // - Remove log files - } - - // When function returns, the 'vm' variable is dropped - // Since Arc count is 1, AxVM::drop() is called immediately - println!(" VM[{}] will be freed now", vm_id); - } - None => { - println!( - "✗ Failed to remove VM[{}] from list (may have been removed already)", - vm_id - ); - } - } - - // When function returns, the 'vm' Arc is dropped - // If all vCPU threads have exited (ref_count was 1), AxVM::drop() is called here - println!("✓ VM[{}] deletion completed", vm_id); -} - -#[cfg(feature = "fs")] -fn vm_list_simple() { - let vms = vm_list::get_vm_list(); - println!("ID NAME STATE VCPU MEMORY"); - println!("---- ----------- ------- ---- ------"); - for vm in vms { - let status = vm.vm_status(); - - // Calculate total memory size - let total_memory: usize = vm.memory_regions().iter().map(|region| region.size()).sum(); - - println!( - "{:<4} {:<11} {:<7} {:<4} {}", - vm.id(), - vm.with_config(|cfg| cfg.name()), - status.as_str(), - vm.vcpu_num(), - format_memory_size(total_memory) - ); - } -} - -fn vm_list(cmd: &ParsedCommand) { - let binding = "table".to_string(); - let format = cmd.options.get("format").unwrap_or(&binding); - - let display_vms = vm_list::get_vm_list(); - - if display_vms.is_empty() { - println!("No virtual machines found."); - return; - } - - if format == "json" { - // JSON output - println!("{{"); - println!(" \"vms\": ["); - for (i, vm) in display_vms.iter().enumerate() { - let status = vm.vm_status(); - let total_memory: usize = vm.memory_regions().iter().map(|region| region.size()).sum(); - - println!(" {{"); - println!(" \"id\": {},", vm.id()); - println!(" \"name\": \"{}\",", vm.with_config(|cfg| cfg.name())); - println!(" \"state\": \"{}\",", status.as_str()); - println!(" \"vcpu\": {},", vm.vcpu_num()); - println!(" \"memory\": \"{}\"", format_memory_size(total_memory)); - - if i < display_vms.len() - 1 { - println!(" }},"); - } else { - println!(" }}"); - } - } - println!(" ]"); - println!("}}"); - } else { - // Table output (default) - println!( - "{:<6} {:<15} {:<12} {:<15} {:<10} {:<20}", - "VM ID", "NAME", "STATUS", "VCPU", "MEMORY", "VCPU STATE" - ); - println!( - "{:-<6} {:-<15} {:-<12} {:-<15} {:-<10} {:-<20}", - "", "", "", "", "", "" - ); - - for vm in display_vms { - let status = vm.vm_status(); - let total_memory: usize = vm.memory_regions().iter().map(|region| region.size()).sum(); - - // Get VCpu ID list - let vcpu_ids: Vec = vm - .vcpu_list() - .iter() - .map(|vcpu| vcpu.id().to_string()) - .collect(); - let vcpu_id_list = vcpu_ids.join(","); - - // Get VCpu state summary - let mut state_counts = std::collections::BTreeMap::new(); - for vcpu in vm.vcpu_list() { - let state = match vcpu.state() { - axvcpu::VCpuState::Free => "Free", - axvcpu::VCpuState::Running => "Run", - axvcpu::VCpuState::Blocked => "Blk", - axvcpu::VCpuState::Invalid => "Inv", - axvcpu::VCpuState::Created => "Cre", - axvcpu::VCpuState::Ready => "Rdy", - }; - *state_counts.entry(state).or_insert(0) += 1; - } - - // Format: Run:2,Blk:1 - let summary: Vec = state_counts - .iter() - .map(|(state, count)| format!("{}:{}", state, count)) - .collect(); - let vcpu_state_summary = summary.join(","); - - println!( - "{:<6} {:<15} {:<12} {:<15} {:<10} {:<20}", - vm.id(), - vm.with_config(|cfg| cfg.name()), - status.as_str(), - vcpu_id_list, - format_memory_size(total_memory), - vcpu_state_summary - ); - } - } -} - -fn vm_show(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - let show_config = cmd.flags.get("config").unwrap_or(&false); - let show_stats = cmd.flags.get("stats").unwrap_or(&false); - let show_full = cmd.flags.get("full").unwrap_or(&false); - - if args.is_empty() { - println!("Error: No VM specified"); - println!("Usage: vm show [OPTIONS] "); - println!(); - println!("Options:"); - println!(" -f, --full Show full detailed information"); - println!(" -c, --config Show configuration details"); - println!(" -s, --stats Show statistics"); - println!(); - println!("Use 'vm list' to see all VMs"); - return; - } - - // Show specific VM details - let vm_name = &args[0]; - if let Ok(vm_id) = vm_name.parse::() { - if *show_full { - show_vm_full_details(vm_id); - } else { - show_vm_basic_details(vm_id, *show_config, *show_stats); - } - } else { - println!("Error: Invalid VM ID: {}", vm_name); - } -} - -/// Show basic VM information (default view) -fn show_vm_basic_details(vm_id: usize, show_config: bool, show_stats: bool) { - match with_vm(vm_id, |vm| { - let status = vm.vm_status(); - - println!("VM Details: {}", vm_id); - println!(); - - // Basic Information - println!(" VM ID: {}", vm.id()); - println!(" Name: {}", vm.with_config(|cfg| cfg.name())); - println!(" Status: {}", status.as_str_with_icon()); - println!(" VCPUs: {}", vm.vcpu_num()); - - // Calculate total memory - let total_memory: usize = vm.memory_regions().iter().map(|region| region.size()).sum(); - println!(" Memory: {}", format_memory_size(total_memory)); - - // Add state-specific information - match status { - VMStatus::Suspended => { - println!(); - println!(" ℹ VM is paused. Use 'vm resume {}' to continue.", vm_id); - } - VMStatus::Stopped => { - println!(); - println!(" ℹ VM is stopped. Use 'vm delete {}' to clean up.", vm_id); - } - VMStatus::Loaded => { - println!(); - println!(" ℹ VM is ready. Use 'vm start {}' to boot.", vm_id); - } - _ => {} - } - - // VCPU Summary - println!(); - println!("VCPU Summary:"); - let mut state_counts = std::collections::BTreeMap::new(); - for vcpu in vm.vcpu_list() { - let state = match vcpu.state() { - axvcpu::VCpuState::Free => "Free", - axvcpu::VCpuState::Running => "Running", - axvcpu::VCpuState::Blocked => "Blocked", - axvcpu::VCpuState::Invalid => "Invalid", - axvcpu::VCpuState::Created => "Created", - axvcpu::VCpuState::Ready => "Ready", - }; - *state_counts.entry(state).or_insert(0) += 1; - } - - for (state, count) in state_counts { - println!(" {}: {}", state, count); - } - - // Memory Summary - println!(); - println!("Memory Summary:"); - println!(" Total Regions: {}", vm.memory_regions().len()); - println!(" Total Size: {}", format_memory_size(total_memory)); - - // Configuration Summary - if show_config { - println!(); - println!("Configuration:"); - vm.with_config(|cfg| { - println!(" BSP Entry: {:#x}", cfg.bsp_entry().as_usize()); - println!(" AP Entry: {:#x}", cfg.ap_entry().as_usize()); - println!(" Interrupt Mode: {:?}", cfg.interrupt_mode()); - if let Some(dtb_addr) = cfg.image_config().dtb_load_gpa { - println!(" DTB Address: {:#x}", dtb_addr.as_usize()); - } - }); - } - - // Device Summary - if show_stats { - println!(); - println!("Device Summary:"); - println!( - " MMIO Devices: {}", - vm.get_devices().iter_mmio_dev().count() - ); - println!( - " SysReg Devices: {}", - vm.get_devices().iter_sys_reg_dev().count() - ); - } - - println!(); - println!("Use 'vm show {} --full' for detailed information", vm_id); - }) { - Some(_) => {} - None => { - println!("✗ VM[{}] not found", vm_id); - } - } -} - -/// Show full detailed information about a specific VM (--full flag) -fn show_vm_full_details(vm_id: usize) { - match with_vm(vm_id, |vm| { - let status = vm.vm_status(); - - println!("=== VM Details: {} ===", vm_id); - println!(); - - // Basic Information - println!("Basic Information:"); - println!(" VM ID: {}", vm.id()); - println!(" Name: {}", vm.with_config(|cfg| cfg.name())); - println!(" Status: {}", status.as_str_with_icon()); - println!(" VCPUs: {}", vm.vcpu_num()); - - // Calculate total memory - let total_memory: usize = vm.memory_regions().iter().map(|region| region.size()).sum(); - println!(" Memory: {}", format_memory_size(total_memory)); - println!(" EPT Root: {:#x}", vm.ept_root().as_usize()); - - // Add state-specific information - match status { - VMStatus::Suspended => { - println!( - " ℹ VM is paused, VCpu tasks are waiting. Use 'vm resume {}' to continue.", - vm_id - ); - } - VMStatus::Stopping => { - println!(" ℹ VM is shutting down, VCpu tasks are exiting."); - } - VMStatus::Stopped => { - println!( - " ℹ VM is stopped, all VCpu tasks have exited. Use 'vm delete {}' to clean up.", - vm_id - ); - } - VMStatus::Loaded => { - println!( - " ℹ VM is ready to start. Use 'vm start {}' to boot.", - vm_id - ); - } - _ => {} - } - - // VCPU Details - println!(); - println!("VCPU Details:"); - - // Count VCpu states for summary - let mut state_counts = std::collections::BTreeMap::new(); - for vcpu in vm.vcpu_list() { - let state = match vcpu.state() { - axvcpu::VCpuState::Free => "Free", - axvcpu::VCpuState::Running => "Running", - axvcpu::VCpuState::Blocked => "Blocked", - axvcpu::VCpuState::Invalid => "Invalid", - axvcpu::VCpuState::Created => "Created", - axvcpu::VCpuState::Ready => "Ready", - }; - *state_counts.entry(state).or_insert(0) += 1; - } - - // Show summary first - let summary: Vec = state_counts - .iter() - .map(|(state, count)| format!("{}: {}", state, count)) - .collect(); - println!(" Summary: {}", summary.join(", ")); - println!(); - - for vcpu in vm.vcpu_list() { - let vcpu_state = match vcpu.state() { - axvcpu::VCpuState::Free => "Free", - axvcpu::VCpuState::Running => "Running", - axvcpu::VCpuState::Blocked => "Blocked", - axvcpu::VCpuState::Invalid => "Invalid", - axvcpu::VCpuState::Created => "Created", - axvcpu::VCpuState::Ready => "Ready", - }; - - if let Some(phys_cpu_set) = vcpu.phys_cpu_set() { - println!( - " VCPU {}: {} (Affinity: {:#x})", - vcpu.id(), - vcpu_state, - phys_cpu_set - ); - } else { - println!(" VCPU {}: {} (No affinity)", vcpu.id(), vcpu_state); - } - } - - // Add note for Suspended VMs - if status == VMStatus::Suspended { - println!(); - println!( - " Note: VCpu tasks are blocked in wait queue and will resume when VM is unpaused." - ); - } - - // Memory Regions - println!(); - println!( - "Memory Regions: ({} region(s), {} total)", - vm.memory_regions().len(), - format_memory_size(total_memory) - ); - for (i, region) in vm.memory_regions().iter().enumerate() { - let region_type = if region.needs_dealloc { - "Allocated" - } else { - "Reserved" - }; - let identical = if region.is_identical() { - " [identical]" - } else { - "" - }; - println!( - " Region {}: GPA={:#x} HVA={:#x} Size={} Type={}{}", - i, - region.gpa, - region.hva, - format_memory_size(region.size()), - region_type, - identical - ); - } - - // Configuration - println!(); - println!("Configuration:"); - vm.with_config(|cfg| { - println!(" BSP Entry: {:#x}", cfg.bsp_entry().as_usize()); - println!(" AP Entry: {:#x}", cfg.ap_entry().as_usize()); - println!(" Interrupt Mode: {:?}", cfg.interrupt_mode()); - - if let Some(dtb_addr) = cfg.image_config().dtb_load_gpa { - println!(" DTB Address: {:#x}", dtb_addr.as_usize()); - } - - // Show kernel info - println!( - " Kernel GPA: {:#x}", - cfg.image_config().kernel_load_gpa.as_usize() - ); - - // Show passthrough devices - if !cfg.pass_through_devices().is_empty() { - println!(); - println!( - " Passthrough Devices: ({} device(s))", - cfg.pass_through_devices().len() - ); - for device in cfg.pass_through_devices() { - println!( - " - {}: GPA[{:#x}~{:#x}] -> HPA[{:#x}~{:#x}] ({})", - device.name, - device.base_gpa, - device.base_gpa + device.length, - device.base_hpa, - device.base_hpa + device.length, - format_memory_size(device.length) - ); - } - } - - // Show passthrough addresses - if !cfg.pass_through_addresses().is_empty() { - println!(); - println!( - " Passthrough Memory Regions: ({} region(s))", - cfg.pass_through_addresses().len() - ); - for pt_addr in cfg.pass_through_addresses() { - println!( - " - GPA[{:#x}~{:#x}] ({})", - pt_addr.base_gpa, - pt_addr.base_gpa + pt_addr.length, - format_memory_size(pt_addr.length) - ); - } - } - - // Show passthrough SPIs (ARM specific) - #[cfg(target_arch = "aarch64")] - { - let spis = cfg.pass_through_spis(); - if !spis.is_empty() { - println!(); - println!(" Passthrough SPIs: {:?}", spis); - } - } - - // Show emulated devices - if !cfg.emu_devices().is_empty() { - println!(); - println!( - " Emulated Devices: ({} device(s))", - cfg.emu_devices().len() - ); - for (idx, device) in cfg.emu_devices().iter().enumerate() { - println!(" {}. {:?}", idx + 1, device); - } - } - }); - - // Devices - println!(); - let mmio_dev_count = vm.get_devices().iter_mmio_dev().count(); - let sysreg_dev_count = vm.get_devices().iter_sys_reg_dev().count(); - println!("Devices:"); - println!(" MMIO Devices: {}", mmio_dev_count); - println!(" SysReg Devices: {}", sysreg_dev_count); - - // Additional Statistics - println!(); - println!("Additional Statistics:"); - println!(" Total Memory Regions: {}", vm.memory_regions().len()); - - // Show VCpu affinity details - println!(); - println!(" VCpu Affinity Details:"); - for (vcpu_id, affinity, pcpu_id) in vm.get_vcpu_affinities_pcpu_ids() { - if let Some(aff) = affinity { - println!( - " VCpu {}: Physical CPU mask {:#x}, PCpu ID {}", - vcpu_id, aff, pcpu_id - ); - } else { - println!( - " VCpu {}: No specific affinity, PCpu ID {}", - vcpu_id, pcpu_id - ); - } - } - }) { - Some(_) => {} - None => { - println!("✗ VM[{}] not found", vm_id); - } - } -} - -/// Build the VM command tree and register it. -pub fn build_vm_cmd(tree: &mut BTreeMap) { - #[cfg(feature = "fs")] - let create_cmd = CommandNode::new("Create a new virtual machine") - .with_handler(vm_create) - .with_usage("vm create [OPTIONS] ...") - .with_option( - OptionDef::new("name", "Virtual machine name") - .with_short('n') - .with_long("name"), - ) - .with_option( - OptionDef::new("cpu", "Number of CPU cores") - .with_short('c') - .with_long("cpu"), - ) - .with_option( - OptionDef::new("memory", "Amount of memory") - .with_short('m') - .with_long("memory"), - ) - .with_flag( - FlagDef::new("force", "Force creation without confirmation") - .with_short('f') - .with_long("force"), - ); - - #[cfg(feature = "fs")] - let start_cmd = CommandNode::new("Start a virtual machine") - .with_handler(vm_start) - .with_usage("vm start [OPTIONS] [VM_ID...]") - .with_flag( - FlagDef::new("detach", "Start in background") - .with_short('d') - .with_long("detach"), - ) - .with_flag( - FlagDef::new("console", "Attach to console") - .with_short('c') - .with_long("console"), - ); - - let stop_cmd = CommandNode::new("Stop a virtual machine") - .with_handler(vm_stop) - .with_usage("vm stop [OPTIONS] ...") - .with_flag( - FlagDef::new("force", "Force stop") - .with_short('f') - .with_long("force"), - ) - .with_flag( - FlagDef::new("graceful", "Graceful shutdown") - .with_short('g') - .with_long("graceful"), - ); - - let restart_cmd = CommandNode::new("Restart a virtual machine") - .with_handler(vm_restart) - .with_usage("vm restart [OPTIONS] ...") - .with_flag( - FlagDef::new("force", "Force restart") - .with_short('f') - .with_long("force"), - ); - - let suspend_cmd = CommandNode::new("Suspend (pause) a running virtual machine") - .with_handler(vm_suspend) - .with_usage("vm suspend ..."); - - let resume_cmd = CommandNode::new("Resume a suspended virtual machine") - .with_handler(vm_resume) - .with_usage("vm resume ..."); - - let delete_cmd = CommandNode::new("Delete a virtual machine") - .with_handler(vm_delete) - .with_usage("vm delete [OPTIONS] ") - .with_flag( - FlagDef::new("force", "Skip confirmation") - .with_short('f') - .with_long("force"), - ) - .with_flag(FlagDef::new("keep-data", "Keep VM data").with_long("keep-data")); - - let list_cmd = CommandNode::new("Show virtual machine lists") - .with_handler(vm_list) - .with_usage("vm list [OPTIONS]") - .with_flag( - FlagDef::new("all", "Show all VMs including stopped ones") - .with_short('a') - .with_long("all"), - ) - .with_option(OptionDef::new("format", "Output format (table, json)").with_long("format")); - - let show_cmd = CommandNode::new("Show detailed VM information") - .with_handler(vm_show) - .with_usage("vm show [OPTIONS] ") - .with_flag( - FlagDef::new("full", "Show full detailed information") - .with_short('f') - .with_long("full"), - ) - .with_flag( - FlagDef::new("config", "Show configuration details") - .with_short('c') - .with_long("config"), - ) - .with_flag( - FlagDef::new("stats", "Show device statistics") - .with_short('s') - .with_long("stats"), - ); - - // main VM command - let mut vm_node = CommandNode::new("Virtual machine management") - .with_handler(vm_help) - .with_usage("vm [options] [args...]") - .add_subcommand( - "help", - CommandNode::new("Show VM help").with_handler(vm_help), - ); - - #[cfg(feature = "fs")] - { - vm_node = vm_node - .add_subcommand("create", create_cmd) - .add_subcommand("start", start_cmd); - } - - vm_node = vm_node - .add_subcommand("stop", stop_cmd) - .add_subcommand("suspend", suspend_cmd) - .add_subcommand("resume", resume_cmd) - .add_subcommand("restart", restart_cmd) - .add_subcommand("delete", delete_cmd) - .add_subcommand("list", list_cmd) - .add_subcommand("show", show_cmd); - - tree.insert("vm".to_string(), vm_node); -} diff --git a/kernel/src/shell/commands/builtin.rs b/kernel/src/shell/commands/builtin.rs new file mode 100644 index 00000000..c71f27ed --- /dev/null +++ b/kernel/src/shell/commands/builtin.rs @@ -0,0 +1,129 @@ +//! Built-in commands +//! +//! Commands that are part of the shell itself (help, exit, clear, log, uname). + +use std::println; + +use super::super::parser::ParsedCommand; +use alloc::collections::BTreeMap; +use alloc::string::{String, ToString}; + +/// Handle the `uname` command - display system information +pub fn do_uname(cmd: &ParsedCommand) { + let show_all = cmd.flags.get("all").unwrap_or(&false); + let show_kernel = cmd.flags.get("kernel-name").unwrap_or(&false); + let show_arch = cmd.flags.get("machine").unwrap_or(&false); + + let arch = option_env!("AX_ARCH").unwrap_or(""); + let platform = option_env!("AX_PLATFORM").unwrap_or(""); + let smp = match option_env!("AX_SMP") { + None | Some("1") => "", + _ => " SMP", + }; + let version = option_env!("CARGO_PKG_VERSION").unwrap_or("0.1.0"); + + if *show_all { + println!( + "ArceOS {ver}{smp} {arch} {plat}", + ver = version, + smp = smp, + arch = arch, + plat = platform, + ); + } else if *show_kernel { + println!("ArceOS"); + } else if *show_arch { + println!("{}", arch); + } else { + println!( + "ArceOS {ver}{smp} {arch} {plat}", + ver = version, + smp = smp, + arch = arch, + plat = platform, + ); + } +} + +/// Handle the `exit` command - exit the shell +pub fn do_exit(cmd: &ParsedCommand) { + let args = &cmd.positional_args; + let exit_code = if args.is_empty() { + 0 + } else { + args[0].parse::().unwrap_or(0) + }; + + println!("Bye~"); + std::process::exit(exit_code); +} + +/// Handle the `log` command - change log level +pub fn do_log(cmd: &ParsedCommand) { + let args = &cmd.positional_args; + + if args.is_empty() { + println!("Current log level: {:?}", log::max_level()); + return; + } + + match args[0].as_str() { + "on" | "enable" => log::set_max_level(log::LevelFilter::Info), + "off" | "disable" => log::set_max_level(log::LevelFilter::Off), + "error" => log::set_max_level(log::LevelFilter::Error), + "warn" => log::set_max_level(log::LevelFilter::Warn), + "info" => log::set_max_level(log::LevelFilter::Info), + "debug" => log::set_max_level(log::LevelFilter::Debug), + "trace" => log::set_max_level(log::LevelFilter::Trace), + level => { + println!("Unknown log level: {}", level); + println!("Available levels: off, error, warn, info, debug, trace"); + return; + } + } + println!("Log level set to: {:?}", log::max_level()); +} + +/// Register built-in commands to the command tree +pub fn register_builtin_commands(tree: &mut BTreeMap) { + use super::super::parser::{CommandNode, FlagDef}; + + // uname Command + tree.insert( + "uname".to_string(), + CommandNode::new("System information") + .with_handler(do_uname) + .with_usage("uname [OPTIONS]") + .with_flag( + FlagDef::new("all", "Show all information") + .with_short('a') + .with_long("all"), + ) + .with_flag( + FlagDef::new("kernel-name", "Show kernel name") + .with_short('s') + .with_long("kernel-name"), + ) + .with_flag( + FlagDef::new("machine", "Show machine architecture") + .with_short('m') + .with_long("machine"), + ), + ); + + // exit Command + tree.insert( + "exit".to_string(), + CommandNode::new("Exit the shell") + .with_handler(do_exit) + .with_usage("exit [EXIT_CODE]"), + ); + + // log Command + tree.insert( + "log".to_string(), + CommandNode::new("Change log level") + .with_handler(do_log) + .with_usage("log [LEVEL]"), + ); +} diff --git a/kernel/src/shell/command/base.rs b/kernel/src/shell/commands/fs.rs similarity index 84% rename from kernel/src/shell/command/base.rs rename to kernel/src/shell/commands/fs.rs index d24b945d..fe2c03df 100644 --- a/kernel/src/shell/command/base.rs +++ b/kernel/src/shell/commands/fs.rs @@ -1,3 +1,7 @@ +//! File system commands +//! +//! Commands for file and directory operations (ls, cat, cd, mkdir, etc.). + use std::collections::BTreeMap; #[cfg(feature = "fs")] use std::fs::{self, File, FileType}; @@ -6,7 +10,7 @@ use std::io::{self, Read, Write}; use std::println; use std::string::{String, ToString}; -use crate::shell::command::{CommandNode, FlagDef, ParsedCommand}; +use super::super::parser::{CommandNode, FlagDef, ParsedCommand}; #[cfg(feature = "fs")] macro_rules! print_err { @@ -18,17 +22,9 @@ macro_rules! print_err { }; } -// Helper function: split whitespace -#[cfg(feature = "fs")] -fn split_whitespace(s: &str) -> (&str, &str) { - let s = s.trim(); - if let Some(pos) = s.find(char::is_whitespace) { - let (first, rest) = s.split_at(pos); - (first, rest.trim()) - } else { - (s, "") - } -} +// ============================================================================ +// Command Handlers +// ============================================================================ #[cfg(feature = "fs")] fn do_ls(cmd: &ParsedCommand) { @@ -286,79 +282,6 @@ fn do_pwd(cmd: &ParsedCommand) { println!("{}", pwd); } -fn do_uname(cmd: &ParsedCommand) { - let show_all = cmd.flags.get("all").unwrap_or(&false); - let show_kernel = cmd.flags.get("kernel-name").unwrap_or(&false); - let show_arch = cmd.flags.get("machine").unwrap_or(&false); - - let arch = option_env!("AX_ARCH").unwrap_or(""); - let platform = option_env!("AX_PLATFORM").unwrap_or(""); - let smp = match option_env!("AX_SMP") { - None | Some("1") => "", - _ => " SMP", - }; - let version = option_env!("CARGO_PKG_VERSION").unwrap_or("0.1.0"); - - if *show_all { - println!( - "ArceOS {ver}{smp} {arch} {plat}", - ver = version, - smp = smp, - arch = arch, - plat = platform, - ); - } else if *show_kernel { - println!("ArceOS"); - } else if *show_arch { - println!("{}", arch); - } else { - println!( - "ArceOS {ver}{smp} {arch} {plat}", - ver = version, - smp = smp, - arch = arch, - plat = platform, - ); - } -} - -fn do_exit(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - let exit_code = if args.is_empty() { - 0 - } else { - args[0].parse::().unwrap_or(0) - }; - - println!("Bye~"); - std::process::exit(exit_code); -} - -fn do_log(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - - if args.is_empty() { - println!("Current log level: {:?}", log::max_level()); - return; - } - - match args[0].as_str() { - "on" | "enable" => log::set_max_level(log::LevelFilter::Info), - "off" | "disable" => log::set_max_level(log::LevelFilter::Off), - "error" => log::set_max_level(log::LevelFilter::Error), - "warn" => log::set_max_level(log::LevelFilter::Warn), - "info" => log::set_max_level(log::LevelFilter::Info), - "debug" => log::set_max_level(log::LevelFilter::Debug), - "trace" => log::set_max_level(log::LevelFilter::Trace), - level => { - println!("Unknown log level: {}", level); - println!("Available levels: off, error, warn, info, debug, trace"); - return; - } - } - println!("Log level set to: {:?}", log::max_level()); -} - #[cfg(feature = "fs")] fn do_mv(cmd: &ParsedCommand) { let args = &cmd.positional_args; @@ -605,7 +528,23 @@ const fn file_perm_to_rwx(mode: u32) -> [u8; 9] { perm } -pub fn build_base_cmd(tree: &mut BTreeMap) { +// Helper function: split whitespace +#[cfg(feature = "fs")] +fn split_whitespace(s: &str) -> (&str, &str) { + let s = s.trim(); + if let Some(pos) = s.find(char::is_whitespace) { + let (first, rest) = s.split_at(pos); + (first, rest.trim()) + } else { + (s, "") + } +} + +// ============================================================================ +// Command Registration +// ============================================================================ + +pub fn register_fs_commands(tree: &mut BTreeMap) { // ls Command #[cfg(feature = "fs")] tree.insert( @@ -709,45 +648,6 @@ pub fn build_base_cmd(tree: &mut BTreeMap) { ), ); - // uname Command - tree.insert( - "uname".to_string(), - CommandNode::new("System information") - .with_handler(do_uname) - .with_usage("uname [OPTIONS]") - .with_flag( - FlagDef::new("all", "Show all information") - .with_short('a') - .with_long("all"), - ) - .with_flag( - FlagDef::new("kernel-name", "Show kernel name") - .with_short('s') - .with_long("kernel-name"), - ) - .with_flag( - FlagDef::new("machine", "Show machine architecture") - .with_short('m') - .with_long("machine"), - ), - ); - - // exit Command - tree.insert( - "exit".to_string(), - CommandNode::new("Exit the shell") - .with_handler(do_exit) - .with_usage("exit [EXIT_CODE]"), - ); - - // log Command - tree.insert( - "log".to_string(), - CommandNode::new("Change log level") - .with_handler(do_log) - .with_usage("log [LEVEL]"), - ); - // touch Command #[cfg(feature = "fs")] tree.insert( diff --git a/kernel/src/shell/commands/mod.rs b/kernel/src/shell/commands/mod.rs new file mode 100644 index 00000000..e93c0bec --- /dev/null +++ b/kernel/src/shell/commands/mod.rs @@ -0,0 +1,239 @@ +//! Command handlers module +//! +//! Provides command implementations for different categories. + +mod builtin; +mod fs; +mod vm; + +use alloc::collections::BTreeMap; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; + +use core::str; + +use crate::std::io::Write; + +use axstd::print; +use axstd::println; + +use super::parser::{CommandNode, CommandParser, ParseError}; + +pub use builtin::register_builtin_commands; +pub use fs::register_fs_commands; +pub use vm::register_vm_commands; + +lazy_static::lazy_static! { + /// Global command tree containing all registered commands + pub static ref COMMAND_TREE: BTreeMap = build_command_tree(); +} + +/// Build the complete command tree by registering all command categories +fn build_command_tree() -> BTreeMap { + let mut tree = BTreeMap::new(); + + register_builtin_commands(&mut tree); + register_fs_commands(&mut tree); + register_vm_commands(&mut tree); + + tree +} + +/// Execute a parsed command +pub fn execute_command(input: &str) -> Result<(), ParseError> { + let parsed = CommandParser::parse(input, &*COMMAND_TREE)?; + + // Find the corresponding command node + let mut current_node = (*COMMAND_TREE).get(&parsed.command_path[0]).ok_or_else(|| { + ParseError::UnknownCommand(parsed.command_path[0].clone()) + })?; + for cmd in &parsed.command_path[1..] { + current_node = current_node.subcommands.get(cmd).ok_or_else(|| { + ParseError::UnknownCommand(cmd.clone()) + })?; + } + + // Execute the command + if let Some(handler) = current_node.handler() { + handler(&parsed); + Ok(()) + } else { + Err(ParseError::NoHandler(parsed.command_path.join(" "))) + } +} + +/// Display help for a specific command +pub fn show_help(command_path: &[String]) -> Result<(), ParseError> { + let mut current_node = (*COMMAND_TREE) + .get(&command_path[0]) + .ok_or_else(|| ParseError::UnknownCommand(command_path[0].clone()))?; + + for cmd in &command_path[1..] { + current_node = current_node + .subcommands + .get(cmd) + .ok_or_else(|| ParseError::UnknownCommand(cmd.clone()))?; + } + + println!("Command: {}", command_path.join(" ")); + println!("Description: {}", current_node.description); + + if let Some(usage) = current_node.usage { + println!("Usage: {}", usage); + } + + if !current_node.options.is_empty() { + println!("\nOptions:"); + for option in ¤t_node.options { + let mut opt_str = String::new(); + if let Some(short) = option.short { + opt_str.push_str(&format!("-{short}")); + } + if let Some(long) = option.long { + if !opt_str.is_empty() { + opt_str.push_str(", "); + } + opt_str.push_str(&format!("--{long}")); + } + if opt_str.is_empty() { + opt_str = option.name.to_string(); + } + + let required_str = if option.required { " (required)" } else { "" }; + println!(" {:<20} {}{}", opt_str, option.description, required_str); + } + } + + if !current_node.flags.is_empty() { + println!("\nFlags:"); + for flag in ¤t_node.flags { + let mut flag_str = String::new(); + if let Some(short) = flag.short { + flag_str.push_str(&format!("-{short}")); + } + if let Some(long) = flag.long { + if !flag_str.is_empty() { + flag_str.push_str(", "); + } + flag_str.push_str(&format!("--{long}")); + } + if flag_str.is_empty() { + flag_str = flag.name.to_string(); + } + + println!(" {:<20} {}", flag_str, flag.description); + } + } + + if !current_node.subcommands.is_empty() { + println!("\nSubcommands:"); + for (name, node) in ¤t_node.subcommands { + println!(" {:<20} {}", name, node.description); + } + } + + Ok(()) +} + +/// Show all available commands +pub fn show_available_commands() { + println!("ArceOS Shell - Available Commands:"); + println!(); + + // Display all top-level commands + for (name, node) in (*COMMAND_TREE).iter() { + println!(" {:<15} {}", name, node.description); + + // Display subcommands + if !node.subcommands.is_empty() { + for (sub_name, sub_node) in &node.subcommands { + println!(" {:<13} {}", sub_name, sub_node.description); + } + } + } + + println!(); + println!("Built-in Commands:"); + println!(" help Show help information"); + println!(" help Show help for a specific command"); + println!(" clear Clear the screen"); + println!(" exit/quit Exit the shell"); + println!(); + println!("Tip: Use 'help ' to see detailed usage of a command"); +} + +/// Handle built-in shell commands (help, exit, clear) +pub fn handle_builtin_commands(input: &str) -> bool { + match input.trim() { + "help" => { + show_available_commands(); + true + } + "exit" | "quit" => { + println!("Goodbye!"); + axstd::process::exit(0); + } + "clear" => { + print!("\x1b[2J\x1b[H"); // ANSI clear screen sequence + axstd::io::stdout().flush().unwrap(); + true + } + _ if input.starts_with("help ") => { + let cmd_parts: Vec = input[5..] + .split_whitespace() + .map(|s| s.to_string()) + .collect(); + if let Err(e) = show_help(&cmd_parts) { + println!("Error: {:?}", e); + } + true + } + _ => false, + } +} + +/// Print the shell prompt +pub fn print_prompt() { + #[cfg(feature = "fs")] + print!("axvisor:{}$ ", axstd::env::current_dir().unwrap()); + #[cfg(not(feature = "fs"))] + print!("axvisor:$ "); + axstd::io::stdout().flush().unwrap(); +} + +/// Execute a command from byte input +pub fn run_cmd_bytes(cmd_bytes: &[u8]) { + match str::from_utf8(cmd_bytes) { + Ok(cmd_str) => { + let trimmed = cmd_str.trim(); + if trimmed.is_empty() { + return; + } + + match execute_command(trimmed) { + Ok(_) => { + // Command executed successfully + } + Err(ParseError::UnknownCommand(cmd)) => { + println!("Error: Unknown command '{}'", cmd); + println!("Type 'help' to see available commands"); + } + Err(ParseError::UnknownOption(opt)) => { + println!("Error: Unknown option '{}'", opt); + } + Err(ParseError::MissingValue(opt)) => { + println!("Error: Option '{}' is missing a value", opt); + } + Err(ParseError::MissingRequiredOption(opt)) => { + println!("Error: Missing required option '{}'", opt); + } + Err(ParseError::NoHandler(cmd)) => { + println!("Error: Command '{}' has no handler function", cmd); + } + } + } + Err(_) => { + println!("Error: Input contains invalid UTF-8 characters"); + } + } +} diff --git a/kernel/src/shell/commands/vm.rs b/kernel/src/shell/commands/vm.rs new file mode 100644 index 00000000..cc2d2543 --- /dev/null +++ b/kernel/src/shell/commands/vm.rs @@ -0,0 +1,680 @@ +//! Virtual machine management commands +//! +//! Commands for managing virtual machines (create, start, stop, list, etc.). + +use alloc::collections::BTreeMap; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; + +#[cfg(feature = "fs")] +use axstd::fs::read_to_string; +use axstd::println; + +use crate::vmm::vm_list; +use crate::vmm::{config::build_vmconfig, start_vm}; +use axvm::config::AxVMCrateConfig; +use axvm::VMStatus; + +use super::super::parser::{CommandNode, FlagDef, OptionDef, ParsedCommand}; + +/// Format memory size in a human-readable way. +fn format_memory_size(bytes: usize) -> String { + if bytes < 1024 { + format!("{}B", bytes) + } else if bytes < 1024 * 1024 { + format!("{}KB", bytes / 1024) + } else if bytes < 1024 * 1024 * 1024 { + format!("{}MB", bytes / (1024 * 1024)) + } else { + format!("{}GB", bytes / (1024 * 1024 * 1024)) + } +} + +// ============================================================================ +// Command Handlers +// ============================================================================ + +fn vm_help(_cmd: &ParsedCommand) { + println!("VM - virtual machine management"); + println!(); + println!("Most commonly used vm commands:"); + println!(" create Create a new virtual machine"); + println!(" start Start a virtual machine"); + println!(" stop Stop a virtual machine"); + println!(" suspend Suspend (pause) a running virtual machine"); + println!(" resume Resume a suspended virtual machine"); + println!(" delete Delete a virtual machine"); + println!(); + println!("Information commands:"); + println!(" list Show table of all VMs"); + println!(" show Show VM details (requires VM_ID)"); + println!(" - Default: basic information"); + println!(" - --full: complete detailed information"); + println!(" - --config: show configuration"); + println!(" - --stats: show statistics"); + println!(); + println!("Use 'vm --help' for more information on a specific command."); +} + +#[cfg(feature = "fs")] +fn vm_create(cmd: &ParsedCommand) { + let args = &cmd.positional_args; + + println!("Positional args: {:?}", args); + + if args.is_empty() { + println!("Error: No VM configuration file specified"); + println!("Usage: vm create [CONFIG_FILE]"); + return; + } + + let initial_vm_count = vm_list::get_vm_list().len(); + + for config_path in args.iter() { + println!("Creating VM from config: {}", config_path); + + // Read file content first + let raw_cfg = match read_to_string(config_path) { + Ok(content) => content, + Err(e) => { + println!("✗ Failed to read config file {}: {:?}", config_path, e); + continue; + } + }; + + // Parse TOML from string content + let config_info: AxVMCrateConfig = match toml::from_str(&raw_cfg) { + Ok(cfg) => cfg, + Err(e) => { + println!("✗ Failed to parse TOML from {}: {:?}", config_path, e); + continue; + } + }; + + match build_vmconfig(config_info) { + Ok(vm_config) => { + match axvm::Vm::new(vm_config) { + Ok(vm) => { + let vm = vm_list::push_vm(vm); + let vm_id = vm.id(); + println!( + "✓ Successfully created VM[{}] from config: {}", + vm_id, config_path + ); + println!("{:?}", vm.status()); + } + Err(e) => { + println!( + "✗ Failed to create VM from {}: {:?}", + config_path, e + ); + } + } + } + Err(e) => { + println!("✗ Failed to build VM config from {}: {:?}", config_path, e); + } + } + } + + // Check the actual number of VMs created + let final_vm_count = vm_list::get_vm_list().len(); + let created_count = final_vm_count - initial_vm_count; + + if created_count > 0 { + println!("Successfully created {} VM(s)", created_count); + println!("Use 'vm start ' to start the created VMs."); + } else { + println!("No VMs were created."); + } +} + +#[cfg(feature = "fs")] +fn vm_start(cmd: &ParsedCommand) { + let args = &cmd.positional_args; + + if args.is_empty() { + // start all VMs + info!("VMM starting, booting all VMs..."); + let mut started_count = 0; + + for vm in vm_list::get_vm_list() { + let vm: vm_list::VMRef = vm; + // Check current status before starting + let status = vm.status(); + if status == VMStatus::Running { + println!("⚠ VM[{}] is already running, skipping", vm.id()); + continue; + } + + if status != VMStatus::Inited && status != VMStatus::Stopped { + println!("⚠ VM[{}] is in {:?} state, cannot start", vm.id(), status); + continue; + } + + // Use vm.id() to get usize VM ID + let vm_id = usize::from(vm.id()); + + // Try to start the VM + match vm.boot() { + Ok(_) => { + println!("✓ VM[{}] started successfully", vm_id); + started_count += 1; + } + Err(e) => { + println!("✗ VM[{}] failed to start: {:?}", vm_id, e); + } + } + } + println!("Started {} VM(s)", started_count); + } else { + // Start specified VMs + for arg in args { + // Try to parse as VM ID or lookup VM name + let arg: &String = arg; + if let Ok(vm_id) = arg.parse::() { + if !start_vm_by_id(vm_id) { + // VM not found, show available VMs + println!("Available VMs:"); + vm_list_simple(); + } + } else { + println!("Error: VM name lookup not implemented. Use VM ID instead."); + println!("Available VMs:"); + vm_list_simple(); + } + } + } +} + +fn start_vm_by_id(vm_id: usize) -> bool { + let vm: vm_list::VMRef = match vm_list::get_vm_by_id(vm_id) { + Some(vm) => vm, + None => { + println!("✗ VM[{}] not found", vm_id); + return false; + } + }; + + // Boot the VM + match vm.boot() { + Ok(_) => { + println!("{:?}", vm.status()); + println!("✓ VM[{}] started successfully", vm_id); + true + } + Err(e) => { + println!("✗ VM[{}] failed to boot: {:?}", vm_id, e); + true + } + } +} + +fn vm_status(cmd: &ParsedCommand) { + let args = &cmd.positional_args; + + // If no arguments, show status of all VMs + if args.is_empty() { + let vm_list = vm_list::get_vm_list(); + if vm_list.is_empty() { + println!("No VMs found."); + return; + } + println!("VM Status:"); + println!("-----------"); + for vm in vm_list { + let vm: vm_list::VMRef = vm; + let vm_id = usize::from(vm.id()); + let name = vm.name(); + let status = vm.status(); + println!("VM[{}] \"{}\": {:?}", vm_id, name, status); + } + return; + } + + // Show status of specified VM(s) + for arg in args { + let arg: &String = arg; + if let Ok(vm_id) = arg.parse::() { + if let Some(vm) = vm_list::get_vm_by_id(vm_id) { + let vm: vm_list::VMRef = vm; + let name = vm.name(); + let status = vm.status(); + println!("VM[{}] \"{}\": {:?}", vm_id, name, status); + } else { + println!("✗ VM[{}] not found", vm_id); + } + } else { + println!("Error: Invalid VM ID: {}", arg); + } + } +} + +fn vm_stop(cmd: &ParsedCommand) { + let args = &cmd.positional_args; + + if args.is_empty() { + println!("Error: No VM specified"); + println!("Usage: vm stop "); + return; + } + + for vm_name in args { + let vm_name: &String = vm_name; + if let Ok(vm_id) = vm_name.parse::() { + stop_vm_by_id(vm_id); + } else { + println!("Error: Invalid VM ID: {}", vm_name); + } + } +} + +fn stop_vm_by_id(vm_id: usize) { + let vm: vm_list::VMRef = match vm_list::get_vm_by_id(vm_id) { + Some(vm) => vm, + None => { + println!("✗ VM[{}] not found", vm_id); + return; + } + }; + + let status = vm.status(); + + // Check if VM can be stopped + match status { + VMStatus::Running => { + println!("Stopping VM[{}]...", vm_id); + } + VMStatus::Stopping => { + println!("⚠ VM[{}] is already stopping", vm_id); + return; + } + VMStatus::Stopped => { + println!("⚠ VM[{}] is already stopped", vm_id); + return; + } + VMStatus::Inited => { + println!("⚠ VM[{}] is not running yet", vm_id); + return; + } + _ => { + println!("⚠ VM[{}] is in {:?} state, cannot stop", vm_id, status); + return; + } + } + + // Call shutdown + match vm.shutdown() { + Ok(_) => { + println!("✓ VM[{}] stop signal sent successfully", vm_id); + println!(" Note: VM status will transition to Stopped"); + } + Err(e) => { + println!("✗ Failed to stop VM[{}]: {:?}", vm_id, e); + } + } +} + +fn vm_delete(cmd: &ParsedCommand) { + let args = &cmd.positional_args; + let force = cmd.flags.get("force").unwrap_or(&false); + + if args.is_empty() { + println!("Error: No VM specified"); + println!("Usage: vm delete [OPTIONS] "); + return; + } + + for arg in args { + let arg: &String = arg; + if let Ok(vm_id) = arg.parse::() { + delete_vm_by_id(vm_id, *force); + } else { + println!("Error: Invalid VM ID: {}", arg); + } + } +} + +fn delete_vm_by_id(vm_id: usize, force: bool) { + // Check if VM exists and get its status + let vm: vm_list::VMRef = match vm_list::get_vm_by_id(vm_id) { + Some(vm) => vm, + None => { + println!("✗ VM[{}] not found", vm_id); + return; + } + }; + + let status = vm.status(); + + // Check if VM is running + match status { + VMStatus::Running => { + if !force { + println!("✗ VM[{}] is currently running", vm_id); + println!(" Use 'vm stop {}' first, or use '--force' to force delete", vm_id); + return; + } + println!("⚠ Force deleting running VM[{}]...", vm_id); + } + VMStatus::Stopping => { + if !force { + println!("⚠ VM[{}] is currently stopping", vm_id); + println!(" Wait for it to stop completely, or use '--force' to force delete"); + return; + } + println!("⚠ Force deleting stopping VM[{}]...", vm_id); + } + VMStatus::Stopped | VMStatus::Inited => { + println!("Deleting VM[{}] (status: {:?})...", vm_id, status); + // Resources will be automatically released when VM is dropped + println!(" ✓ VM resources will be released on drop"); + } + _ => { + println!("⚠ VM[{}] is in {:?} state", vm_id, status); + if !force { + println!(" Use --force to force delete"); + return; + } + println!(" Force deleting..."); + } + } + + // If VM is running, try to stop it first + if matches!(status, VMStatus::Running | VMStatus::Stopping) { + println!(" Sending shutdown signal..."); + match vm.shutdown() { + Ok(_) => { + println!(" ✓ Shutdown signal sent"); + } + Err(e) => { + println!(" ⚠ Warning: Failed to send shutdown signal: {:?}", e); + } + } + } + + // Remove VM from global list + match vm_list::remove_vm(vm_id) { + Some(_) => { + println!("✓ VM[{}] deleted successfully", vm_id); + } + None => { + println!("✗ Failed to remove VM[{}] from list", vm_id); + } + } +} + +#[cfg(feature = "fs")] +fn vm_list_simple() { + let vms = vm_list::get_vm_list(); + println!("ID NAME STATE VCPU MEMORY"); + println!("---- ----------- ------- ---- ------"); + for vm in vms { + let vm: vm_list::VMRef = vm; + let status = vm.status(); + let vcpu_num = vm.vcpu_num(); + let memory_size = vm.memory_size(); + + println!( + "{:<4} {:<11} {:<7} {:<4} {}", + usize::from(vm.id()), + vm.name(), + format!("{:?}", status), + vcpu_num, + format_memory_size(memory_size) + ); + } +} + +fn vm_list(cmd: &ParsedCommand) { + let binding = "table".to_string(); + let format = cmd.options.get("format").unwrap_or(&binding); + + let display_vms = vm_list::get_vm_list(); + + if display_vms.is_empty() { + println!("No virtual machines found."); + return; + } + + if format == "json" { + // JSON output + println!("{{"); + println!(" \"vms\": ["); + for (i, vm) in display_vms.iter().enumerate() { + let vm: &vm_list::VMRef = vm; + let status = vm.status(); + let total_memory = vm.memory_size(); + let vcpu_num = vm.vcpu_num(); + + println!(" {{"); + println!(" \"id\": {},", usize::from(vm.id())); + println!(" \"name\": \"{}\",", vm.name()); + println!(" \"state\": {:?},", status); + println!(" \"vcpu\": {},", vcpu_num); + println!(" \"memory\": \"{}\"", format_memory_size(total_memory)); + + if i < display_vms.len() - 1 { + println!(" }},"); + } else { + println!(" }}"); + } + } + println!(" ]"); + println!("}}"); + } else { + // Table output (default) + println!( + "{:<6} {:<15} {:<12} {:<10} {:<10}", + "VM ID", "NAME", "STATUS", "VCPU", "MEMORY" + ); + println!( + "{:-<6} {:-<15} {:-<12} {:-<10} {:-<10}", + "", "", "", "", "" + ); + + for vm in display_vms { + let vm: vm_list::VMRef = vm; + let status = vm.status(); + let total_memory = vm.memory_size(); + let vcpu_num = vm.vcpu_num(); + + println!( + "{:<6} {:<15} {:<12} {:<10} {}", + usize::from(vm.id()), + vm.name(), + format!("{:?}", status), + vcpu_num, + format_memory_size(total_memory) + ); + } + } +} + +fn vm_show(cmd: &ParsedCommand) { + let args = &cmd.positional_args; + + if args.is_empty() { + println!("Error: No VM specified"); + println!("Usage: vm show "); + println!(); + println!("Use 'vm list' to see all VMs"); + return; + } + + // Show specific VM details + let vm_name: &String = &args[0]; + if let Ok(vm_id) = vm_name.parse::() { + show_vm_details(vm_id); + } else { + println!("Error: Invalid VM ID: {}", vm_name); + } +} + +/// Show VM information +fn show_vm_details(vm_id: usize) { + let vm: vm_list::VMRef = match vm_list::get_vm_by_id(vm_id) { + Some(vm) => vm, + None => { + println!("✗ VM[{}] not found", vm_id); + return; + } + }; + + let status = vm.status(); + + println!("=== VM Details: {} ===", vm_id); + println!(); + + // Basic Information + println!(" VM ID: {}", usize::from(vm.id())); + println!(" Name: {}", vm.name()); + println!(" Status: {:?}", status); + println!(" VCPUs: {}", vm.vcpu_num()); + println!(" Memory: {}", format_memory_size(vm.memory_size())); + + // Add state-specific information + match status { + VMStatus::Inited => { + println!(); + println!(" ℹ VM is ready. Use 'vm start {}' to boot.", vm_id); + } + VMStatus::Running => { + println!(); + println!(" ℹ VM is running."); + } + VMStatus::Stopped => { + println!(); + println!(" ℹ VM is stopped."); + } + _ => {} + } +} + +// ============================================================================ +// Command Registration +// ============================================================================ + +/// Build the VM command tree and register it. +pub fn register_vm_commands(tree: &mut BTreeMap) { + #[cfg(feature = "fs")] + let create_cmd = CommandNode::new("Create a new virtual machine") + .with_handler(vm_create) + .with_usage("vm create [OPTIONS] ...") + .with_option( + OptionDef::new("name", "Virtual machine name") + .with_short('n') + .with_long("name"), + ) + .with_option( + OptionDef::new("cpu", "Number of CPU cores") + .with_short('c') + .with_long("cpu"), + ) + .with_option( + OptionDef::new("memory", "Amount of memory") + .with_short('m') + .with_long("memory"), + ) + .with_flag( + FlagDef::new("force", "Force creation without confirmation") + .with_short('f') + .with_long("force"), + ); + + #[cfg(feature = "fs")] + let start_cmd = CommandNode::new("Start a virtual machine") + .with_handler(vm_start) + .with_usage("vm start [OPTIONS] [VM_ID...]") + .with_flag( + FlagDef::new("detach", "Start in background") + .with_short('d') + .with_long("detach"), + ) + .with_flag( + FlagDef::new("console", "Attach to console") + .with_short('c') + .with_long("console"), + ); + + let status_cmd = CommandNode::new("Stop a virtual machine") + .with_handler(vm_status) + .with_usage("vm stop [OPTIONS] ..."); + + let stop_cmd = CommandNode::new("Stop a virtual machine") + .with_handler(vm_stop) + .with_usage("vm stop [OPTIONS] ...") + .with_flag( + FlagDef::new("force", "Force stop") + .with_short('f') + .with_long("force"), + ) + .with_flag( + FlagDef::new("graceful", "Graceful shutdown") + .with_short('g') + .with_long("graceful"), + ); + + let delete_cmd = CommandNode::new("Delete a virtual machine") + .with_handler(vm_delete) + .with_usage("vm delete [OPTIONS] ") + .with_flag( + FlagDef::new("force", "Force delete without stopping VM first") + .with_short('f') + .with_long("force"), + ); + + let list_cmd = CommandNode::new("Show virtual machine lists") + .with_handler(vm_list) + .with_usage("vm list [OPTIONS]") + .with_flag( + FlagDef::new("all", "Show all VMs including stopped ones") + .with_short('a') + .with_long("all"), + ) + .with_option(OptionDef::new("format", "Output format (table, json)").with_long("format")); + + let show_cmd = CommandNode::new("Show detailed VM information") + .with_handler(vm_show) + .with_usage("vm show [OPTIONS] ") + .with_flag( + FlagDef::new("full", "Show full detailed information") + .with_short('f') + .with_long("full"), + ) + .with_flag( + FlagDef::new("config", "Show configuration details") + .with_short('c') + .with_long("config"), + ) + .with_flag( + FlagDef::new("stats", "Show device statistics") + .with_short('s') + .with_long("stats"), + ); + + // main VM command + let mut vm_node = CommandNode::new("Virtual machine management") + .with_handler(vm_help) + .with_usage("vm [options] [args...]") + .add_subcommand( + "help", + CommandNode::new("Show VM help").with_handler(vm_help), + ); + + #[cfg(feature = "fs")] + { + vm_node = vm_node + .add_subcommand("create", create_cmd) + .add_subcommand("start", start_cmd); + } + + vm_node = vm_node + .add_subcommand("status", status_cmd) + .add_subcommand("stop", stop_cmd) + .add_subcommand("delete", delete_cmd) + .add_subcommand("list", list_cmd) + .add_subcommand("show", show_cmd); + + tree.insert("vm".to_string(), vm_node); +} diff --git a/kernel/src/shell/completion.rs b/kernel/src/shell/completion.rs new file mode 100644 index 00000000..06d981bb --- /dev/null +++ b/kernel/src/shell/completion.rs @@ -0,0 +1,290 @@ +//! Tab completion module for file and directory names +//! +//! Provides intelligent filename and directory name completion +//! when the user presses the TAB key. + +use alloc::string::{String, ToString}; +use alloc::vec::Vec; + +/// Completion result containing possible matches +pub struct CompletionResult { + /// The common prefix of all matches + pub prefix: String, + /// List of all possible completions + pub matches: Vec, + /// The text that should be inserted (suffix to add) + pub insert_text: String, +} + +impl CompletionResult { + /// Create a new completion result + pub fn new(prefix: String, matches: Vec) -> Self { + let insert_text = if matches.len() == 1 { + // For single match, insert the full match + matches[0].clone() + } else { + // For multiple matches, insert the common prefix + prefix.clone() + }; + Self { prefix, matches, insert_text } + } + + /// Check if there's exactly one match + pub fn is_unique(&self) -> bool { + self.matches.len() == 1 + } + + /// Check if there are no matches + pub fn is_empty(&self) -> bool { + self.matches.is_empty() + } +} + +/// Perform tab completion for the given input line and cursor position +/// +/// # Arguments +/// * `line` - The current input line +/// * `cursor_pos` - The current cursor position +/// +/// # Returns +/// * `None` if no completion is possible +/// * `Some(CompletionResult)` with completion suggestions +pub fn complete(line: &str, cursor_pos: usize) -> Option { + // Find the word being completed (word under cursor) + let word_start = find_word_start(line, cursor_pos); + let word_to_complete = &line[word_start..cursor_pos]; + + // Check if we should complete commands or filenames + // If we're at the beginning of the line or after a space, complete commands + // Otherwise, complete filenames + if is_at_command_position(line, word_start) { + complete_command(word_to_complete) + } else { + // For filename completion, we need to extract the path prefix and filename part + // Returns a CompletionResult where insert_text is the full path (path_prefix + filename) + complete_filename(word_to_complete) + } +} + +/// Find the start position of the word under the cursor +pub fn find_word_start(line: &str, cursor_pos: usize) -> usize { + let bytes = line.as_bytes(); + let mut pos = cursor_pos.saturating_sub(1); + + while pos > 0 && !is_whitespace(bytes[pos]) { + pos -= 1; + } + + if is_whitespace(bytes[pos]) { + pos + 1 + } else { + pos + } +} + +/// Check if a byte is a whitespace character +fn is_whitespace(b: u8) -> bool { + b == b' ' || b == b'\t' +} + +/// Check if we're at a position where commands should be completed +/// (either at the start of line or after a pipe) +fn is_at_command_position(line: &str, word_start: usize) -> bool { + let before_word = &line[..word_start]; + let trimmed = before_word.trim(); + + // At the start of the line + if trimmed.is_empty() { + return true; + } + + // After a pipe (simple check) + if trimmed.ends_with('|') { + return true; + } + + false +} + +/// Complete command names +#[cfg(feature = "fs")] +fn complete_command(partial: &str) -> Option { + use super::commands::COMMAND_TREE; + + let partial_lower = partial.to_lowercase(); + let mut matches: Vec = (*COMMAND_TREE) + .keys() + .filter(|cmd: &&String| cmd.starts_with(&partial_lower)) + .cloned() + .collect(); + + if matches.is_empty() { + return None; + } + + matches.sort(); + + let common_prefix = find_common_prefix(&matches); + Some(CompletionResult::new(common_prefix, matches)) +} + +/// Complete command names (no filesystem feature) +#[cfg(not(feature = "fs"))] +fn complete_command(partial: &str) -> Option { + use super::commands::COMMAND_TREE; + + let partial_lower = partial.to_lowercase(); + let mut matches: Vec = (*COMMAND_TREE) + .keys() + .filter(|cmd: &&String| cmd.starts_with(&partial_lower)) + .cloned() + .collect(); + + if matches.is_empty() { + return None; + } + + matches.sort(); + + let common_prefix = find_common_prefix(&matches); + Some(CompletionResult::new(common_prefix, matches)) +} + +/// Complete file and directory names +#[cfg(feature = "fs")] +fn complete_filename(partial: &str) -> Option { + use alloc::string::ToString; + use axstd::fs; + + // Split into directory path and file prefix + let (dir_path, file_prefix, path_prefix) = if let Some(last_slash) = partial.rfind('/') { + // If partial ends with '/', complete everything in that directory + if last_slash == partial.len() - 1 { + // dir_path is everything up to and including the last slash + (&partial[..], "", partial.to_string()) + } else { + // dir_path is everything up to and including the last slash + // file_prefix is everything after the last slash + (&partial[..=last_slash], &partial[last_slash + 1..], partial[..=last_slash].to_string()) + } + } else { + (".", partial, String::new()) + }; + + // Try to read the directory + let entries = match fs::read_dir(dir_path) { + Ok(entries) => entries, + Err(_) => return None, + }; + + let mut matches: Vec = Vec::new(); + + for entry in entries.flatten() { + let name = entry.file_name(); + + // Skip hidden files (starting with '.') unless explicitly requested + if !file_prefix.starts_with('.') && name.starts_with('.') { + continue; + } + + if name.starts_with(file_prefix) { + // Check if it's a directory by file type + let file_type = entry.file_type(); + + // Add '/' suffix for directories + let completed_name = if matches!(file_type, axstd::fs::FileType::Dir) { + format!("{name}/") + } else { + name.clone() + }; + + // For matches, we need to include the path prefix + // e.g., if partial is "/gu" and we find "guest", match should be "/guest/" + let full_match = if !path_prefix.is_empty() && path_prefix != "/" { + format!("{}{}", path_prefix, completed_name) + } else if path_prefix == "/" { + format!("/{}", completed_name) + } else { + completed_name.clone() + }; + + matches.push(full_match); + } + } + + if matches.is_empty() { + return None; + } + + matches.sort(); + + let common_prefix = find_common_prefix(&matches); + Some(CompletionResult::new(common_prefix, matches)) +} + +/// Complete file and directory names (no filesystem support) +#[cfg(not(feature = "fs"))] +fn complete_filename(_partial: &str) -> Option { + // No filesystem support, no filename completion + None +} + +/// Find the common prefix among all strings +fn find_common_prefix(strings: &[String]) -> String { + if strings.is_empty() { + return String::new(); + } + + if strings.len() == 1 { + return strings[0].clone(); + } + + let first = &strings[0]; + let mut end = first.len(); + + for s in &strings[1..] { + let mut new_end = 0; + for (i, (a, b)) in first.bytes().zip(s.bytes()).enumerate() { + if a == b { + new_end = i + 1; + } else { + break; + } + } + end = end.min(new_end); + if end == 0 { + break; + } + } + + first[..end].to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_find_common_prefix() { + assert_eq!(find_common_prefix(&[]), ""); + assert_eq!(find_common_prefix(&["test".into()]), "test"); + assert_eq!(find_common_prefix(&["test".into(), "testing".into()]), "test"); + assert_eq!(find_common_prefix(&["foo".into(), "bar".into()]), ""); + assert_eq!(find_common_prefix(&["file1.txt".into(), "file2.txt".into()]), "file"); + } + + #[test] + fn test_find_word_start() { + assert_eq!(find_word_start("ls test", 6), 3); + assert_eq!(find_word_start("ls test file", 6), 3); + assert_eq!(find_word_start("ls", 2), 0); + assert_eq!(find_word_start("ls test", 7), 5); + } + + #[test] + fn test_is_at_command_position() { + assert!(is_at_command_position("ls", 0)); + assert!(is_at_command_position("| ls", 2)); + assert!(!is_at_command_position("ls file", 3)); + } +} diff --git a/kernel/src/shell/mod.rs b/kernel/src/shell/mod.rs index 89850240..edfa6d03 100644 --- a/kernel/src/shell/mod.rs +++ b/kernel/src/shell/mod.rs @@ -1,225 +1,34 @@ -mod command; - -use std::io::prelude::*; -use std::println; -use std::string::ToString; - -use crate::shell::command::{ - CommandHistory, clear_line_and_redraw, handle_builtin_commands, print_prompt, run_cmd_bytes, +//! AxVisor Shell +//! +//! A command-line shell for AxVisor with support for: +//! - Command history and navigation +//! - File system operations (when fs feature is enabled) +//! - Virtual machine management +//! +//! # Example +//! +//! ```no_run +//! use axvisor_shell::console_init; +//! +//! // Start the shell in blocking mode +//! console_init(); +//! ``` + +#![no_std] + +mod completion; +mod parser; +mod shell; + +mod commands; + +// Re-export shell types and functions +pub use shell::{Shell, console_init, console_init_non_blocking}; + +// Re-export parser types for external use +pub use parser::{ + CommandHistory, CommandNode, CommandParser, FlagDef, OptionDef, ParseError, ParsedCommand, }; -const LF: u8 = b'\n'; -const CR: u8 = b'\r'; -const DL: u8 = b'\x7f'; -const BS: u8 = b'\x08'; -const ESC: u8 = 0x1b; // ESC key - -const MAX_LINE_LEN: usize = 256; - -// Initialize the console shell. -pub fn console_init() { - let mut stdin = std::io::stdin(); - let mut stdout = std::io::stdout(); - let mut history = CommandHistory::new(100); - - let mut buf = [0; MAX_LINE_LEN]; - let mut cursor = 0; // cursor position in buffer - let mut line_len = 0; // actual length of current line - - enum InputState { - Normal, - Escape, - EscapeSeq, - } - - let mut input_state = InputState::Normal; - - println!("Welcome to AxVisor Shell!"); - println!("Type 'help' to see available commands"); - println!("Use UP/DOWN arrows to navigate command history"); - #[cfg(not(feature = "fs"))] - println!("Note: Running with limited features (filesystem support disabled)."); - println!(); - - print_prompt(); - - loop { - let mut temp_buf = [0u8; 1]; - - let ch = match stdin.read(&mut temp_buf) { - Ok(1) => temp_buf[0], - _ => { - continue; - } - }; - - match input_state { - InputState::Normal => { - match ch { - CR | LF => { - println!(); - if line_len > 0 { - let cmd_str = std::str::from_utf8(&buf[..line_len]).unwrap_or(""); - - // Add to history - history.add_command(cmd_str.to_string()); - - // Execute command - if !handle_builtin_commands(cmd_str) { - run_cmd_bytes(&buf[..line_len]); - } - - // reset buffer - buf[..line_len].fill(0); - cursor = 0; - line_len = 0; - } - print_prompt(); - } - BS | DL => { - // backspace: delete character before cursor / DEL key: delete character at cursor - if cursor > 0 { - // move characters after cursor forward - for i in cursor..line_len { - buf[i - 1] = buf[i]; - } - cursor -= 1; - line_len -= 1; - if line_len < buf.len() { - buf[line_len] = 0; - } - - let current_content = - std::str::from_utf8(&buf[..line_len]).unwrap_or(""); - #[cfg(feature = "fs")] - let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); - clear_line_and_redraw(&mut stdout, &prompt, current_content, cursor); - } - } - ESC => { - input_state = InputState::Escape; - } - 0..=31 => { - // ignore other control characters - } - c => { - // insert character - if line_len < MAX_LINE_LEN - 1 { - // move characters after cursor backward to make space for new character - for i in (cursor..line_len).rev() { - buf[i + 1] = buf[i]; - } - buf[cursor] = c; - cursor += 1; - line_len += 1; - - let current_content = - std::str::from_utf8(&buf[..line_len]).unwrap_or(""); - #[cfg(feature = "fs")] - let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); - clear_line_and_redraw(&mut stdout, &prompt, current_content, cursor); - } - } - } - } - InputState::Escape => match ch { - b'[' => { - input_state = InputState::EscapeSeq; - } - _ => { - input_state = InputState::Normal; - } - }, - InputState::EscapeSeq => { - match ch { - b'A' => { - // UP arrow - previous command - if let Some(prev_cmd) = history.previous() { - // clear current buffer - buf[..line_len].fill(0); - - let cmd_bytes = prev_cmd.as_bytes(); - let copy_len = cmd_bytes.len().min(MAX_LINE_LEN - 1); - buf[..copy_len].copy_from_slice(&cmd_bytes[..copy_len]); - cursor = copy_len; - line_len = copy_len; - #[cfg(feature = "fs")] - let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); - clear_line_and_redraw(&mut stdout, &prompt, prev_cmd, cursor); - } - input_state = InputState::Normal; - } - b'B' => { - // DOWN arrow - next command - match history.next() { - Some(next_cmd) => { - // clear current buffer - buf[..line_len].fill(0); - - let cmd_bytes = next_cmd.as_bytes(); - let copy_len = cmd_bytes.len().min(MAX_LINE_LEN - 1); - buf[..copy_len].copy_from_slice(&cmd_bytes[..copy_len]); - cursor = copy_len; - line_len = copy_len; - - #[cfg(feature = "fs")] - let prompt = - format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); - clear_line_and_redraw(&mut stdout, &prompt, next_cmd, cursor); - } - None => { - // clear current line - buf[..line_len].fill(0); - cursor = 0; - line_len = 0; - #[cfg(feature = "fs")] - let prompt = - format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); - clear_line_and_redraw(&mut stdout, &prompt, "", cursor); - } - } - input_state = InputState::Normal; - } - b'C' => { - // RIGHT arrow - move cursor right - if cursor < line_len { - cursor += 1; - stdout.write_all(b"\x1b[C").ok(); - stdout.flush().ok(); - } - input_state = InputState::Normal; - } - b'D' => { - // LEFT arrow - move cursor left - if cursor > 0 { - cursor -= 1; - stdout.write_all(b"\x1b[D").ok(); - stdout.flush().ok(); - } - input_state = InputState::Normal; - } - b'3' => { - // check if this is Delete key sequence (ESC[3~) - // need to read next character to confirm - input_state = InputState::Normal; - // can add additional state to handle complete Delete sequence - } - _ => { - // ignore other escape sequences - input_state = InputState::Normal; - } - } - } - } - } -} +// Re-export commands module +pub use commands::{COMMAND_TREE, execute_command, show_available_commands, show_help}; diff --git a/kernel/src/shell/command/history.rs b/kernel/src/shell/parser/history.rs similarity index 80% rename from kernel/src/shell/command/history.rs rename to kernel/src/shell/parser/history.rs index 9c13fc83..db3d76bc 100644 --- a/kernel/src/shell/command/history.rs +++ b/kernel/src/shell/parser/history.rs @@ -1,6 +1,11 @@ +//! Command history management +//! +//! Provides functionality to store and navigate through command history. + use std::io::prelude::*; use std::{string::String, vec::Vec}; +/// Command history storage and navigation pub struct CommandHistory { history: Vec, current_index: usize, @@ -8,6 +13,7 @@ pub struct CommandHistory { } impl CommandHistory { + /// Create a new command history with the given maximum size pub fn new(max_size: usize) -> Self { Self { history: Vec::new(), @@ -16,6 +22,7 @@ impl CommandHistory { } } + /// Add a command to the history pub fn add_command(&mut self, cmd: String) { if !cmd.trim().is_empty() && self.history.last() != Some(&cmd) { if self.history.len() >= self.max_size { @@ -26,6 +33,7 @@ impl CommandHistory { self.current_index = self.history.len(); } + /// Get the previous command in history #[allow(dead_code)] pub fn previous(&mut self) -> Option<&String> { if self.current_index > 0 { @@ -36,6 +44,7 @@ impl CommandHistory { } } + /// Get the next command in history #[allow(dead_code)] pub fn next(&mut self) -> Option<&String> { if self.current_index < self.history.len() { @@ -51,6 +60,7 @@ impl CommandHistory { } } +/// Clear the current line and redraw it with the given content #[allow(unused_must_use)] pub fn clear_line_and_redraw( stdout: &mut dyn Write, diff --git a/kernel/src/shell/parser/mod.rs b/kernel/src/shell/parser/mod.rs new file mode 100644 index 00000000..a0cb47c0 --- /dev/null +++ b/kernel/src/shell/parser/mod.rs @@ -0,0 +1,11 @@ +//! Command parsing module +//! +//! Provides functionality to parse and structure commands. + +mod history; +mod node; +mod parser; + +pub use history::{CommandHistory, clear_line_and_redraw}; +pub use node::{CommandNode, FlagDef, OptionDef, ParseError, ParsedCommand}; +pub use parser::CommandParser; diff --git a/kernel/src/shell/parser/node.rs b/kernel/src/shell/parser/node.rs new file mode 100644 index 00000000..8b7df812 --- /dev/null +++ b/kernel/src/shell/parser/node.rs @@ -0,0 +1,172 @@ +//! Command node definitions for the command tree +//! +//! Defines the structures used to build the command tree hierarchy. + +use alloc::collections::BTreeMap; +use alloc::string::String; +use alloc::vec::Vec; + +/// A node in the command tree +#[derive(Debug, Clone)] +pub struct CommandNode { + handler: Option, + pub subcommands: BTreeMap, + pub description: &'static str, + pub usage: Option<&'static str>, + #[allow(dead_code)] + pub log_level: log::LevelFilter, + pub options: Vec, + pub flags: Vec, +} + +impl CommandNode { + /// Create a new command node with a description + pub fn new(description: &'static str) -> Self { + Self { + handler: None, + subcommands: BTreeMap::new(), + description, + usage: None, + log_level: log::LevelFilter::Off, + options: Vec::new(), + flags: Vec::new(), + } + } + + /// Set the handler function for this command + pub fn with_handler(mut self, handler: fn(&ParsedCommand)) -> Self { + self.handler = Some(handler); + self + } + + /// Set the usage string for this command + pub fn with_usage(mut self, usage: &'static str) -> Self { + self.usage = Some(usage); + self + } + + /// Set the log level for this command + #[allow(dead_code)] + pub fn with_log_level(mut self, level: log::LevelFilter) -> Self { + self.log_level = level; + self + } + + /// Add an option to this command + pub fn with_option(mut self, option: OptionDef) -> Self { + self.options.push(option); + self + } + + /// Add a flag to this command + pub fn with_flag(mut self, flag: FlagDef) -> Self { + self.flags.push(flag); + self + } + + /// Add a subcommand to this command + pub fn add_subcommand>(mut self, name: S, node: CommandNode) -> Self { + self.subcommands.insert(name.into(), node); + self + } + + /// Get the handler for this command + pub fn handler(&self) -> Option { + self.handler + } +} + +/// Definition of a command option (takes a value) +#[derive(Debug, Clone)] +pub struct OptionDef { + pub name: &'static str, + pub short: Option, + pub long: Option<&'static str>, + pub description: &'static str, + pub required: bool, +} + +impl OptionDef { + /// Create a new option definition + pub fn new(name: &'static str, description: &'static str) -> Self { + Self { + name, + short: None, + long: None, + description, + required: false, + } + } + + /// Set the short flag (e.g., -v) + #[allow(dead_code)] + pub fn with_short(mut self, short: char) -> Self { + self.short = Some(short); + self + } + + /// Set the long flag (e.g., --verbose) + pub fn with_long(mut self, long: &'static str) -> Self { + self.long = Some(long); + self + } + + /// Mark this option as required + #[allow(dead_code)] + pub fn required(mut self) -> Self { + self.required = true; + self + } +} + +/// Definition of a command flag (boolean) +#[derive(Debug, Clone)] +pub struct FlagDef { + pub name: &'static str, + pub short: Option, + pub long: Option<&'static str>, + pub description: &'static str, +} + +impl FlagDef { + /// Create a new flag definition + pub fn new(name: &'static str, description: &'static str) -> Self { + Self { + name, + short: None, + long: None, + description, + } + } + + /// Set the short flag (e.g., -v) + pub fn with_short(mut self, short: char) -> Self { + self.short = Some(short); + self + } + + /// Set the long flag (e.g., --verbose) + pub fn with_long(mut self, long: &'static str) -> Self { + self.long = Some(long); + self + } +} + +/// A parsed command with all arguments and options +#[derive(Debug, Clone)] +pub struct ParsedCommand { + pub command_path: Vec, + pub options: BTreeMap, + pub flags: BTreeMap, + pub positional_args: Vec, +} + +/// Errors that can occur during command parsing +#[derive(Debug)] +pub enum ParseError { + UnknownCommand(String), + UnknownOption(String), + MissingValue(String), + MissingRequiredOption(String), + NoHandler(String), +} diff --git a/kernel/src/shell/parser/parser.rs b/kernel/src/shell/parser/parser.rs new file mode 100644 index 00000000..5526db08 --- /dev/null +++ b/kernel/src/shell/parser/parser.rs @@ -0,0 +1,241 @@ +//! Command parser implementation +//! +//! Parses command strings into structured command objects. + +use super::node::{CommandNode, ParseError, ParsedCommand}; +use alloc::collections::BTreeMap; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; + +/// Command parser +pub struct CommandParser; + +impl CommandParser { + /// Parse a command string into a structured command + pub fn parse( + input: &str, + command_tree: &BTreeMap, + ) -> Result { + let tokens = Self::tokenize(input); + if tokens.is_empty() { + return Err(ParseError::UnknownCommand("empty command".to_string())); + } + + // Find the command path + let (command_path, command_node, remaining_tokens) = + Self::find_command(&tokens, command_tree)?; + + // Parse the arguments + let (options, flags, positional_args) = Self::parse_args(remaining_tokens, command_node)?; + + // Validate required options + Self::validate_required_options(command_node, &options)?; + + Ok(ParsedCommand { + command_path, + options, + flags, + positional_args, + }) + } + + /// Split input string into tokens, handling quotes and escapes + fn tokenize(input: &str) -> Vec { + let mut tokens = Vec::new(); + let mut current_token = String::new(); + let mut in_quotes = false; + let mut escape_next = false; + + for ch in input.chars() { + if escape_next { + current_token.push(ch); + escape_next = false; + } else if ch == '\\' { + escape_next = true; + } else if ch == '"' { + in_quotes = !in_quotes; + } else if ch.is_whitespace() && !in_quotes { + if !current_token.is_empty() { + tokens.push(current_token.clone()); + current_token.clear(); + } + } else { + current_token.push(ch); + } + } + + if !current_token.is_empty() { + tokens.push(current_token); + } + + tokens + } + + /// Find the command node for the given tokens + fn find_command<'a>( + tokens: &'a [String], + command_tree: &'a BTreeMap, + ) -> Result<(Vec, &'a CommandNode, &'a [String]), ParseError> { + let mut current_node = command_tree + .get(&tokens[0]) + .ok_or_else(|| ParseError::UnknownCommand(tokens[0].clone()))?; + + let mut command_path = vec![tokens[0].clone()]; + let mut token_index = 1; + + // Traverse to find the deepest command node + while token_index < tokens.len() { + if let Some(subcommand) = current_node.subcommands.get(&tokens[token_index]) { + current_node = subcommand; + command_path.push(tokens[token_index].clone()); + token_index += 1; + } else { + break; + } + } + + Ok((command_path, current_node, &tokens[token_index..])) + } + + /// Parse arguments into options, flags, and positional args + #[allow(clippy::type_complexity)] + fn parse_args( + tokens: &[String], + command_node: &CommandNode, + ) -> Result< + ( + BTreeMap, + BTreeMap, + Vec, + ), + ParseError, + > { + let mut options = BTreeMap::new(); + let mut flags = BTreeMap::new(); + let mut positional_args = Vec::new(); + let mut i = 0; + + while i < tokens.len() { + let token = &tokens[i]; + + if let Some(name) = token.strip_prefix("--") { + // Long options/flags + if let Some(eq_pos) = name.find('=') { + // --option=value format + let (opt_name, value) = name.split_at(eq_pos); + let value = &value[1..]; // Skip '=' + if Self::is_option(opt_name, command_node) { + options.insert(opt_name.to_string(), value.to_string()); + } else { + return Err(ParseError::UnknownOption(format!("--{opt_name}"))); + } + } else if Self::is_flag(name, command_node) { + flags.insert(name.to_string(), true); + } else if Self::is_option(name, command_node) { + // --option value format + if i + 1 >= tokens.len() { + return Err(ParseError::MissingValue(format!("--{name}"))); + } + options.insert(name.to_string(), tokens[i + 1].clone()); + i += 1; // Skip value + } else { + return Err(ParseError::UnknownOption(format!("--{name}"))); + } + } else if token.starts_with('-') && token.len() > 1 { + // Short options/flags + let chars: Vec = token[1..].chars().collect(); + for (j, &ch) in chars.iter().enumerate() { + if Self::is_short_flag(ch, command_node) { + flags.insert( + Self::get_flag_name_by_short(ch, command_node) + .unwrap() + .to_string(), + true, + ); + } else if Self::is_short_option(ch, command_node) { + let opt_name = Self::get_option_name_by_short(ch, command_node).unwrap(); + if j == chars.len() - 1 && i + 1 < tokens.len() { + // Last character and there is a next token as value + options.insert(opt_name.to_string(), tokens[i + 1].clone()); + i += 1; // Skip value + } else { + return Err(ParseError::MissingValue(format!("-{ch}"))); + } + } else { + return Err(ParseError::UnknownOption(format!("-{ch}"))); + } + } + } else { + // Positional arguments + positional_args.push(token.clone()); + } + i += 1; + } + + Ok((options, flags, positional_args)) + } + + fn is_option(name: &str, node: &CommandNode) -> bool { + node.options + .iter() + .any(|opt| (opt.long == Some(name)) || opt.name == name) + } + + fn is_flag(name: &str, node: &CommandNode) -> bool { + node.flags + .iter() + .any(|flag| (flag.long == Some(name)) || flag.name == name) + } + + fn is_short_option(ch: char, node: &CommandNode) -> bool { + node.options.iter().any(|opt| opt.short == Some(ch)) + } + + fn is_short_flag(ch: char, node: &CommandNode) -> bool { + node.flags.iter().any(|flag| flag.short == Some(ch)) + } + + fn get_option_name_by_short(ch: char, node: &CommandNode) -> Option<&str> { + node.options + .iter() + .find(|opt| opt.short == Some(ch)) + .map(|opt| opt.name) + } + + fn get_flag_name_by_short(ch: char, node: &CommandNode) -> Option<&str> { + node.flags + .iter() + .find(|flag| flag.short == Some(ch)) + .map(|flag| flag.name) + } + + fn validate_required_options( + node: &CommandNode, + options: &BTreeMap, + ) -> Result<(), ParseError> { + for option in &node.options { + if option.required && !options.contains_key(option.name) { + return Err(ParseError::MissingRequiredOption(option.name.to_string())); + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parser::node::{CommandNode, FlagDef}; + + #[test] + fn test_tokenize() { + let tokens = CommandParser::tokenize("hello world"); + assert_eq!(tokens, vec!["hello", "world"]); + + let tokens = CommandParser::tokenize("hello \"world test\""); + assert_eq!(tokens, vec!["hello", "world test"]); + + let tokens = CommandParser::tokenize("hello\\ world"); + assert_eq!(tokens, vec!["hello world"]); + } +} diff --git a/kernel/src/shell/shell.rs b/kernel/src/shell/shell.rs new file mode 100644 index 00000000..78112e7f --- /dev/null +++ b/kernel/src/shell/shell.rs @@ -0,0 +1,385 @@ +//! Shell core implementation +//! +//! Provides the main shell functionality including input handling, +//! command execution, and state management. + +use std::io::prelude::*; +use std::print; +use std::println; + +use alloc::string::{String, ToString}; + +use super::commands::{handle_builtin_commands, print_prompt, run_cmd_bytes}; +use super::completion; +use super::parser::{CommandHistory, clear_line_and_redraw}; + +const LF: u8 = b'\n'; +const CR: u8 = b'\r'; +const DL: u8 = b'\x7f'; +const BS: u8 = b'\x08'; +const TAB: u8 = b'\t'; // TAB key for completion +const ESC: u8 = 0x1b; // ESC key + +const MAX_LINE_LEN: usize = 256; + +/// Shell state that can be stored and restored +pub struct Shell { + stdin: std::io::Stdin, + stdout: std::io::Stdout, + history: CommandHistory, + buf: [u8; MAX_LINE_LEN], + cursor: usize, + line_len: usize, + input_state: InputState, + initialized: bool, +} + +/// Input state for handling escape sequences +#[derive(Clone, Copy)] +enum InputState { + Normal, + Escape, + EscapeSeq, +} + +impl Shell { + /// Create a new shell instance + pub fn new() -> Self { + Self { + stdin: std::io::stdin(), + stdout: std::io::stdout(), + history: CommandHistory::new(100), + buf: [0; MAX_LINE_LEN], + cursor: 0, + line_len: 0, + input_state: InputState::Normal, + initialized: false, + } + } + + /// Initialize the shell (print welcome message) + pub fn init(&mut self) { + if self.initialized { + return; + } + + println!("Welcome to AxVisor Shell!"); + println!("Type 'help' to see available commands"); + println!("Use UP/DOWN arrows to navigate command history"); + println!("Press TAB to autocomplete commands and filenames"); + println!("Note: Only ASCII characters are supported for input"); + #[cfg(not(feature = "fs"))] + println!("Note: Running with limited features (filesystem support disabled)."); + println!(); + + print_prompt(); + self.initialized = true; + } + + /// Process one character of input. + /// Returns true if a command was executed (for potential scheduling decisions). + pub fn process_char(&mut self) -> bool { + if !self.initialized { + self.init(); + } + + let mut temp_buf = [0u8; 1]; + + let ch = match self.stdin.read(&mut temp_buf) { + Ok(1) => temp_buf[0], + _ => return false, + }; + + self.process_input(ch) + } + + fn process_input(&mut self, ch: u8) -> bool { + let mut command_executed = false; + + match self.input_state { + InputState::Normal => match ch { + CR | LF => { + println!(); + if self.line_len > 0 { + let cmd_str = std::str::from_utf8(&self.buf[..self.line_len]).unwrap_or(""); + + // Add to history + self.history.add_command(cmd_str.to_string()); + + // Execute command + if !handle_builtin_commands(cmd_str) { + run_cmd_bytes(&self.buf[..self.line_len]); + } + + command_executed = true; + + // reset buffer + self.buf[..self.line_len].fill(0); + self.cursor = 0; + self.line_len = 0; + } + print_prompt(); + } + BS | DL => { + // backspace: delete character before cursor / DEL key: delete character at cursor + if self.cursor > 0 { + // move characters after cursor forward + for i in self.cursor..self.line_len { + self.buf[i - 1] = self.buf[i]; + } + self.cursor -= 1; + self.line_len -= 1; + if self.line_len < self.buf.len() { + self.buf[self.line_len] = 0; + } + + let current_content = + std::str::from_utf8(&self.buf[..self.line_len]).unwrap_or(""); + #[cfg(feature = "fs")] + let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); + #[cfg(not(feature = "fs"))] + let prompt = "axvisor:$ ".to_string(); + clear_line_and_redraw( + &mut self.stdout, + &prompt, + current_content, + self.cursor, + ); + } + } + TAB => { + // Tab completion + let current_content = + std::str::from_utf8(&self.buf[..self.line_len]).unwrap_or(""); + + if let Some(result) = completion::complete(current_content, self.cursor) { + let result: &completion::CompletionResult = &result; + let is_unique = result.is_unique(); + let matches_count = result.matches.len(); + + // If there are multiple matches, always show all options first + if !is_unique && matches_count > 1 { + println!(); + // Strip the common prefix from matches for cleaner display + let display_prefix: &str = &result.prefix; + for (i, match_name) in result.matches.iter().enumerate() { + let match_name: &String = match_name; + let display_name: &str = if match_name.starts_with(display_prefix) { + &match_name[display_prefix.len()..] + } else { + match_name.as_str() + }; + print!("{} ", display_name); // Add explicit spacing + if (i + 1) % 3 == 0 { + println!(); + } + } + if result.matches.len() % 3 != 0 { + println!(); + } + print_prompt(); + let current_content = + std::str::from_utf8(&self.buf[..self.line_len]).unwrap_or(""); + print!("{}", current_content); + self.stdout.flush().ok(); + } else if is_unique && matches_count > 0 { + // Single match - insert the full match + let text_to_insert = &result.matches[0]; + let word_start = completion::find_word_start(current_content, self.cursor); + let current_word = ¤t_content[word_start..self.cursor]; + + // Calculate what we need to add (match minus what's already typed) + let to_add = if text_to_insert.starts_with(current_word) { + &text_to_insert[current_word.len()..] + } else { + text_to_insert + }; + + if !to_add.is_empty() { + let to_add_bytes = to_add.as_bytes(); + let insert_len = to_add_bytes.len().min(MAX_LINE_LEN - self.line_len - 1); + + if insert_len > 0 { + // Move existing characters to make space + for i in (self.cursor..self.line_len).rev() { + self.buf[i + insert_len] = self.buf[i]; + } + + // Insert completion + self.buf[self.cursor..self.cursor + insert_len] + .copy_from_slice(&to_add_bytes[..insert_len]); + + self.cursor += insert_len; + self.line_len += insert_len; + + // Redraw + let new_content = + std::str::from_utf8(&self.buf[..self.line_len]).unwrap_or(""); + #[cfg(feature = "fs")] + let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); + #[cfg(not(feature = "fs"))] + let prompt = "axvisor:$ ".to_string(); + clear_line_and_redraw(&mut self.stdout, &prompt, new_content, self.cursor); + } + } + } + } + } + ESC => { + self.input_state = InputState::Escape; + } + 0..=31 => { + // ignore other control characters (already handled: LF, CR, BS, DL, TAB, ESC) + } + c @ 32..=126 => { + // insert ASCII printable character + if self.line_len < MAX_LINE_LEN - 1 { + // move characters after cursor backward to make space for new character + for i in (self.cursor..self.line_len).rev() { + self.buf[i + 1] = self.buf[i]; + } + self.buf[self.cursor] = c; + self.cursor += 1; + self.line_len += 1; + + let current_content = + std::str::from_utf8(&self.buf[..self.line_len]).unwrap_or(""); + #[cfg(feature = "fs")] + let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); + #[cfg(not(feature = "fs"))] + let prompt = "axvisor:$ ".to_string(); + clear_line_and_redraw( + &mut self.stdout, + &prompt, + current_content, + self.cursor, + ); + } + } + _ => { + // Non-ASCII or DEL characters - ignore for now + // In a full implementation, we would handle multi-byte UTF-8 sequences + } + }, + InputState::Escape => match ch { + b'[' => { + self.input_state = InputState::EscapeSeq; + } + _ => { + self.input_state = InputState::Normal; + } + }, + InputState::EscapeSeq => match ch { + b'A' => { + // UP arrow - previous command + if let Some(prev_cmd) = self.history.previous() { + let prev_cmd: &String = prev_cmd; + // clear current buffer + self.buf[..self.line_len].fill(0); + + let cmd_bytes: &[u8] = prev_cmd.as_bytes(); + let copy_len = cmd_bytes.len().min(MAX_LINE_LEN - 1); + self.buf[..copy_len].copy_from_slice(&cmd_bytes[..copy_len]); + self.cursor = copy_len; + self.line_len = copy_len; + #[cfg(feature = "fs")] + let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); + #[cfg(not(feature = "fs"))] + let prompt = "axvisor:$ ".to_string(); + clear_line_and_redraw(&mut self.stdout, &prompt, prev_cmd, self.cursor); + } + self.input_state = InputState::Normal; + } + b'B' => { + // DOWN arrow - next command + match self.history.next() { + Some(next_cmd) => { + let next_cmd: &String = next_cmd; + // clear current buffer + self.buf[..self.line_len].fill(0); + + let cmd_bytes: &[u8] = next_cmd.as_bytes(); + let copy_len = cmd_bytes.len().min(MAX_LINE_LEN - 1); + self.buf[..copy_len].copy_from_slice(&cmd_bytes[..copy_len]); + self.cursor = copy_len; + self.line_len = copy_len; + + #[cfg(feature = "fs")] + let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); + #[cfg(not(feature = "fs"))] + let prompt = "axvisor:$ ".to_string(); + clear_line_and_redraw(&mut self.stdout, &prompt, next_cmd, self.cursor); + } + None => { + // clear current line + self.buf[..self.line_len].fill(0); + self.cursor = 0; + self.line_len = 0; + #[cfg(feature = "fs")] + let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); + #[cfg(not(feature = "fs"))] + let prompt = "axvisor:$ ".to_string(); + clear_line_and_redraw(&mut self.stdout, &prompt, "", self.cursor); + } + } + self.input_state = InputState::Normal; + } + b'C' => { + // RIGHT arrow - move cursor right + if self.cursor < self.line_len { + self.cursor += 1; + self.stdout.write_all(b"\x1b[C").ok(); + self.stdout.flush().ok(); + } + self.input_state = InputState::Normal; + } + b'D' => { + // LEFT arrow - move cursor left + if self.cursor > 0 { + self.cursor -= 1; + self.stdout.write_all(b"\x1b[D").ok(); + self.stdout.flush().ok(); + } + self.input_state = InputState::Normal; + } + b'3' => { + // check if this is Delete key sequence (ESC[3~) + // need to read next character to confirm + self.input_state = InputState::Normal; + } + _ => { + // ignore other escape sequences + self.input_state = InputState::Normal; + } + }, + } + + command_executed + } + + /// Run the shell as a blocking loop (original behavior) + pub fn run(&mut self) { + self.init(); + loop { + self.process_char(); + } + } +} + +impl Default for Shell { + fn default() -> Self { + Self::new() + } +} + +/// Initialize the console shell with blocking behavior (backward compatible). +pub fn console_init() { + let mut shell = Shell::new(); + shell.run(); +} + +/// Non-blocking initialization that returns a Shell instance. +/// This allows the caller to control when to process input. +pub fn console_init_non_blocking() -> Shell { + Shell::new() +} diff --git a/kernel/src/vmm/config.rs b/kernel/src/vmm/config.rs index c4673acd..fb565f57 100644 --- a/kernel/src/vmm/config.rs +++ b/kernel/src/vmm/config.rs @@ -1,5 +1,4 @@ -use std::string::ToString; - +use alloc::string::ToString; use alloc::vec::Vec; use axvm::{ AxVMConfig, CpuId, diff --git a/kernel/src/vmm/fdt/create.rs b/kernel/src/vmm/fdt/create.rs deleted file mode 100644 index 0c8b5c32..00000000 --- a/kernel/src/vmm/fdt/create.rs +++ /dev/null @@ -1,469 +0,0 @@ -use alloc::{ - string::{String, ToString}, - vec::Vec, -}; -use core::ptr::NonNull; - -use axaddrspace::GuestPhysAddr; -use axvm::{VMMemoryRegion, config::AxVMCrateConfig}; -use fdt_parser::{Fdt, Node}; -use memory_addr::MemoryAddr; -use vm_fdt::{FdtWriter, FdtWriterNode}; - -use crate::vmm::{VMRef, images::load_vm_image_from_memory}; - -// use crate::vmm::fdt::print::{print_fdt, print_guest_fdt}; -/// Generate guest FDT and return DTB data -/// -/// # Parameters -/// * `fdt` - Source FDT data -/// * `passthrough_device_names` - Passthrough device name list -/// * `crate_config` - VM creation configuration -/// -/// # Return Value -/// Returns the generated DTB data -pub fn crate_guest_fdt( - fdt: &Fdt, - passthrough_device_names: &[String], - crate_config: &AxVMCrateConfig, -) -> Vec { - let mut fdt_writer = FdtWriter::new().unwrap(); - // Track the level of the previously processed node for level change handling - let mut previous_node_level = 0; - // Maintain a stack of FDT nodes to correctly start and end nodes - let mut node_stack: Vec = Vec::new(); - let phys_cpu_ids = crate_config - .base - .phys_cpu_ids - .clone() - .expect("ERROR: phys_cpu_ids is None"); - - let all_nodes: Vec = fdt.all_nodes().collect(); - - for (index, node) in all_nodes.iter().enumerate() { - let node_path = super::build_node_path(&all_nodes, index); - let node_action = determine_node_action(node, &node_path, passthrough_device_names); - - match node_action { - NodeAction::RootNode => { - node_stack.push(fdt_writer.begin_node("").unwrap()); - } - NodeAction::CpuNode => { - let need = need_cpu_node(&phys_cpu_ids, node, &node_path); - if need { - handle_node_level_change( - &mut fdt_writer, - &mut node_stack, - node.level, - previous_node_level, - ); - node_stack.push(fdt_writer.begin_node(node.name()).unwrap()); - } else { - continue; - } - } - NodeAction::Skip => { - continue; - } - _ => { - trace!( - "Found exact passthrough device node: {}, path: {}", - node.name(), - node_path - ); - handle_node_level_change( - &mut fdt_writer, - &mut node_stack, - node.level, - previous_node_level, - ); - node_stack.push(fdt_writer.begin_node(node.name()).unwrap()); - } - } - - previous_node_level = node.level; - - // Copy all properties of the node - for prop in node.propertys() { - fdt_writer.property(prop.name, prop.raw_value()).unwrap(); - } - } - - // End all unclosed nodes - while let Some(node) = node_stack.pop() { - previous_node_level -= 1; - fdt_writer.end_node(node).unwrap(); - } - assert_eq!(previous_node_level, 0); - - fdt_writer.finish().unwrap() -} - -/// Node processing action enumeration -enum NodeAction { - /// Skip node, not included in guest FDT - Skip, - /// Root node - RootNode, - /// CPU node - CpuNode, - /// Include node as passthrough device node - IncludeAsPassthroughDevice, - /// Include node as child node of passthrough device - IncludeAsChildNode, - /// Include node as ancestor node of passthrough device - IncludeAsAncestorNode, -} - -/// Determine node processing action -fn determine_node_action( - node: &Node, - node_path: &str, - passthrough_device_names: &[String], -) -> NodeAction { - if node.name() == "/" { - // Special handling for root node - NodeAction::RootNode - } else if node.name().starts_with("memory") { - // Skip memory nodes, will add them later - NodeAction::Skip - } else if node_path.starts_with("/cpus") { - NodeAction::CpuNode - } else if passthrough_device_names.contains(&node_path.to_string()) { - // Fully matched passthrough device node - NodeAction::IncludeAsPassthroughDevice - } - // Check if the node is a descendant of a passthrough device (by path inclusion and level validation) - else if is_descendant_of_passthrough_device(node_path, node.level, passthrough_device_names) { - NodeAction::IncludeAsChildNode - } - // Check if the node is an ancestor of a passthrough device (by path inclusion and level validation) - else if is_ancestor_of_passthrough_device(node_path, passthrough_device_names) { - NodeAction::IncludeAsAncestorNode - } else { - NodeAction::Skip - } -} - -/// Determine if node is a descendant of passthrough device -/// When node path contains a path from passthrough_device_names and is longer than it, it is its descendant node -/// Also use node_level as validation condition -fn is_descendant_of_passthrough_device( - node_path: &str, - node_level: usize, - passthrough_device_names: &[String], -) -> bool { - for passthrough_path in passthrough_device_names { - // Check if the current node is a descendant of a passthrough device - if node_path.starts_with(passthrough_path) && node_path.len() > passthrough_path.len() { - // Ensure it is a true descendant path (separated by /) - if passthrough_path == "/" || node_path.chars().nth(passthrough_path.len()) == Some('/') - { - // Use level relationship for validation: the level of a descendant node should be higher than its parent - // Note: The level of the root node is 1, its direct child node level is 2, and so on - let expected_parent_level = passthrough_path.matches('/').count(); - let current_node_level = node_level; - - // If passthrough_path is the root node "/", then its child node level should be 2 - // Otherwise, the child node level should be higher than the parent node level - if (passthrough_path == "/" && current_node_level >= 2) - || (passthrough_path != "/" && current_node_level > expected_parent_level) - { - return true; - } - } - } - } - false -} - -/// Handle node level changes to ensure correct FDT structure -fn handle_node_level_change( - fdt_writer: &mut FdtWriter, - node_stack: &mut Vec, - current_level: usize, - previous_level: usize, -) { - if current_level <= previous_level { - for _ in current_level..=previous_level { - if let Some(end_node) = node_stack.pop() { - fdt_writer.end_node(end_node).unwrap(); - } - } - } -} - -/// Determine if node is an ancestor of passthrough device -fn is_ancestor_of_passthrough_device(node_path: &str, passthrough_device_names: &[String]) -> bool { - for passthrough_path in passthrough_device_names { - // Check if the current node is an ancestor of a passthrough device - if passthrough_path.starts_with(node_path) && passthrough_path.len() > node_path.len() { - // Ensure it is a true ancestor path (separated by /) - let next_char = passthrough_path.chars().nth(node_path.len()).unwrap_or(' '); - if next_char == '/' || node_path == "/" { - return true; - } - } - } - false -} - -/// Determine if CPU node is needed -fn need_cpu_node(phys_cpu_ids: &[usize], node: &Node, node_path: &str) -> bool { - let mut should_include_node = false; - - if !node_path.starts_with("/cpus/cpu@") { - should_include_node = true; - } else if let Some(mut cpu_reg) = node.reg() - && let Some(reg_entry) = cpu_reg.next() - { - let cpu_address = reg_entry.address as usize; - debug!( - "Checking CPU node {} with address 0x{:x}", - node.name(), - cpu_address - ); - // Check if this CPU address is in the configured phys_cpu_ids - if phys_cpu_ids.contains(&cpu_address) { - should_include_node = true; - debug!( - "CPU node {} with address 0x{:x} is in phys_cpu_ids, including in guest FDT", - node.name(), - cpu_address - ); - } else { - debug!( - "CPU node {} with address 0x{:x} is NOT in phys_cpu_ids, skipping", - node.name(), - cpu_address - ); - } - } - should_include_node -} - -/// Add memory node -fn add_memory_node(new_memory: &[VMMemoryRegion], new_fdt: &mut FdtWriter) { - let mut new_value: Vec = Vec::new(); - for mem in new_memory { - let gpa = mem.gpa.as_usize() as u64; - let size = mem.size() as u64; - new_value.push((gpa >> 32) as u32); - new_value.push((gpa & 0xFFFFFFFF) as u32); - new_value.push((size >> 32) as u32); - new_value.push((size & 0xFFFFFFFF) as u32); - } - info!("Adding memory node with value: 0x{new_value:x?}"); - new_fdt - .property_array_u32("reg", new_value.as_ref()) - .unwrap(); - new_fdt.property_string("device_type", "memory").unwrap(); -} - -pub fn update_fdt(fdt_src: NonNull, dtb_size: usize, vm: VMRef) { - let mut new_fdt = FdtWriter::new().unwrap(); - let mut previous_node_level = 0; - let mut node_stack: Vec = Vec::new(); - - let fdt_bytes = unsafe { core::slice::from_raw_parts(fdt_src.as_ptr(), dtb_size) }; - let fdt = Fdt::from_bytes(fdt_bytes) - .map_err(|e| format!("Failed to parse FDT: {e:#?}")) - .expect("Failed to parse FDT"); - - for node in fdt.all_nodes() { - if node.name() == "/" { - node_stack.push(new_fdt.begin_node("").unwrap()); - } else if node.name().starts_with("memory") { - // Skip memory nodes, will add them later - continue; - } else { - handle_node_level_change( - &mut new_fdt, - &mut node_stack, - node.level, - previous_node_level, - ); - // Start new node - node_stack.push(new_fdt.begin_node(node.name()).unwrap()); - } - - previous_node_level = node.level; - - if node.name() == "chosen" { - for prop in node.propertys() { - if prop.name.starts_with("linux,initrd-") { - info!( - "Skipping property: {}, belonging to node: {}", - prop.name, - node.name() - ); - } else if prop.name == "bootargs" { - let bootargs_str = prop.str(); - let modified_bootargs = bootargs_str.replace(" ro ", " rw "); - - if modified_bootargs != bootargs_str { - info!( - "Modifying bootargs: {} -> {}", - bootargs_str, modified_bootargs - ); - } - - new_fdt - .property_string(prop.name, &modified_bootargs) - .unwrap(); - } else { - debug!( - "Find property: {}, belonging to node: {}", - prop.name, - node.name() - ); - new_fdt.property(prop.name, prop.raw_value()).unwrap(); - } - } - } else { - for prop in node.propertys() { - new_fdt.property(prop.name, prop.raw_value()).unwrap(); - } - } - } - - // End all unclosed nodes, and add memory nodes at appropriate positions - while let Some(node) = node_stack.pop() { - previous_node_level -= 1; - new_fdt.end_node(node).unwrap(); - - // add memory node - if previous_node_level == 1 { - let memory_regions = vm.memory_regions(); - debug!("Adding memory node with regions: {memory_regions:?}"); - let memory_node = new_fdt.begin_node("memory").unwrap(); - add_memory_node(&memory_regions, &mut new_fdt); - new_fdt.end_node(memory_node).unwrap(); - } - } - - assert_eq!(previous_node_level, 0); - - info!("Updating FDT memory successfully"); - - let new_fdt_bytes = new_fdt.finish().unwrap(); - - // crate::vmm::fdt::print::print_guest_fdt(new_fdt_bytes.as_slice()); - let vm_clone = vm.clone(); - let dest_addr = calculate_dtb_load_addr(vm, new_fdt_bytes.len()); - info!( - "New FDT will be loaded at {:x}, size: 0x{:x}", - dest_addr, - new_fdt_bytes.len() - ); - // Load the updated FDT into VM - load_vm_image_from_memory(&new_fdt_bytes, dest_addr, vm_clone) - .expect("Failed to load VM images"); -} - -fn calculate_dtb_load_addr(vm: VMRef, fdt_size: usize) -> GuestPhysAddr { - const MB: usize = 1024 * 1024; - - // Get main memory from VM memory regions outside the closure - let main_memory = vm - .memory_regions() - .first() - .cloned() - .expect("VM must have at least one memory region"); - - vm.with_config(|config| { - let dtb_addr = if let Some(addr) = config.image_config.dtb_load_gpa - && !main_memory.is_identical() - { - // If dtb_load_gpa is already set, use the original value - addr - } else { - // If dtb_load_gpa is None, calculate based on memory size and FDT size - let main_memory_size = main_memory.size().min(512 * MB); - let addr = (main_memory.gpa + main_memory_size - fdt_size).align_down(2 * MB); - if fdt_size > main_memory_size { - error!("DTB size is larger than available memory"); - } - addr - }; - config.image_config.dtb_load_gpa = Some(dtb_addr); - dtb_addr - }) -} - -pub fn update_cpu_node(fdt: &Fdt, host_fdt: &Fdt, crate_config: &AxVMCrateConfig) -> Vec { - let mut new_fdt = FdtWriter::new().unwrap(); - let mut previous_node_level = 0; - let mut node_stack: Vec = Vec::new(); - let phys_cpu_ids = crate_config - .base - .phys_cpu_ids - .clone() - .expect("ERROR: phys_cpu_ids is None"); - - // Collect all nodes from both FDTs - let fdt_all_nodes: Vec = fdt.all_nodes().collect(); - let host_fdt_all_nodes: Vec = host_fdt.all_nodes().collect(); - - for (index, node) in fdt_all_nodes.iter().enumerate() { - let node_path = super::build_node_path(&fdt_all_nodes, index); - - if node.name() == "/" { - node_stack.push(new_fdt.begin_node("").unwrap()); - } else if node_path.starts_with("/cpus") { - // Skip CPU nodes from fdt, we'll process them from host_fdt later - continue; - } else { - // For all other nodes, include them from fdt as-is without filtering - handle_node_level_change( - &mut new_fdt, - &mut node_stack, - node.level, - previous_node_level, - ); - node_stack.push(new_fdt.begin_node(node.name()).unwrap()); - } - - previous_node_level = node.level; - - // Copy all properties of the node (for non-CPU nodes) - for prop in node.propertys() { - new_fdt.property(prop.name, prop.raw_value()).unwrap(); - } - } - - // Process all CPU nodes from host_fdt - for (index, node) in host_fdt_all_nodes.iter().enumerate() { - let node_path = super::build_node_path(&host_fdt_all_nodes, index); - - if node_path.starts_with("/cpus") { - // For CPU nodes, apply filtering based on host_fdt nodes - let need = need_cpu_node(&phys_cpu_ids, node, &node_path); - if need { - handle_node_level_change( - &mut new_fdt, - &mut node_stack, - node.level, - previous_node_level, - ); - node_stack.push(new_fdt.begin_node(node.name()).unwrap()); - - // Copy properties from host CPU node - for prop in node.propertys() { - new_fdt.property(prop.name, prop.raw_value()).unwrap(); - } - - previous_node_level = node.level; - } - } - } - - // End all unclosed nodes - while let Some(node) = node_stack.pop() { - previous_node_level -= 1; - new_fdt.end_node(node).unwrap(); - } - assert_eq!(previous_node_level, 0); - - new_fdt.finish().unwrap() -} diff --git a/kernel/src/vmm/fdt/device.rs b/kernel/src/vmm/fdt/device.rs deleted file mode 100644 index 8b5d9ece..00000000 --- a/kernel/src/vmm/fdt/device.rs +++ /dev/null @@ -1,508 +0,0 @@ -//! Device passthrough and dependency analysis for FDT processing. - -use alloc::{ - collections::{BTreeMap, BTreeSet}, - string::{String, ToString}, - vec::Vec, -}; -use axvm::config::AxVMConfig; -use fdt_parser::{Fdt, Node}; - -/// Return the collection of all passthrough devices in the configuration file and newly added devices found -pub fn find_all_passthrough_devices(vm_cfg: &mut AxVMConfig, fdt: &Fdt) -> Vec { - let initial_device_count = vm_cfg.pass_through_devices().len(); - - // Pre-build node cache, store all nodes by path to improve lookup performance - let node_cache: BTreeMap> = build_optimized_node_cache(fdt); - - // Get the list of configured device names - let initial_device_names: Vec = vm_cfg - .pass_through_devices() - .iter() - .map(|dev| dev.name.clone()) - .collect(); - - // Phase 1: Discover descendant nodes of all passthrough devices in the configuration file - // Build a set of configured devices, using BTreeSet to improve lookup efficiency - let mut configured_device_names: BTreeSet = - initial_device_names.iter().cloned().collect(); - - // Used to store newly discovered related device names - let mut additional_device_names = Vec::new(); - - // Phase 1: Process initial devices and their descendant nodes - // Note: Directly use device paths instead of device names - for device_name in &initial_device_names { - // Get all descendant node paths for this device - let descendant_paths = get_descendant_nodes_by_path(&node_cache, device_name); - trace!( - "Found {} descendant paths for {}", - descendant_paths.len(), - device_name - ); - - for descendant_path in descendant_paths { - if !configured_device_names.contains(&descendant_path) { - trace!("Found descendant device: {descendant_path}"); - configured_device_names.insert(descendant_path.clone()); - - additional_device_names.push(descendant_path.clone()); - } else { - trace!("Device already exists: {descendant_path}"); - } - } - } - - info!( - "Phase 1 completed: Found {} new descendant device names", - additional_device_names.len() - ); - - // Phase 2: Discover dependency nodes for all existing devices (including descendant devices) - let mut dependency_device_names = Vec::new(); - // Use a work queue of device names, including initial devices and descendant device names - let mut devices_to_process: Vec = configured_device_names.iter().cloned().collect(); - let mut processed_devices: BTreeSet = BTreeSet::new(); - - // Build phandle mapping table - let phandle_map = build_phandle_map(fdt); - - // Use work queue to recursively find all dependent devices - while let Some(device_node_path) = devices_to_process.pop() { - // Avoid processing the same device repeatedly - if processed_devices.contains(&device_node_path) { - continue; - } - processed_devices.insert(device_node_path.clone()); - - trace!("Analyzing dependencies for device: {device_node_path}"); - - // Find direct dependencies of the current device - let dependencies = find_device_dependencies(&device_node_path, &phandle_map, &node_cache); - trace!( - "Found {} dependencies: {:?}", - dependencies.len(), - dependencies - ); - for dep_node_name in dependencies { - // Check if dependency is already in configuration - if !configured_device_names.contains(&dep_node_name) { - trace!("Found new dependency device: {dep_node_name}"); - dependency_device_names.push(dep_node_name.clone()); - - // Add dependency device name to work queue to further find its dependencies - devices_to_process.push(dep_node_name.clone()); - configured_device_names.insert(dep_node_name.clone()); - } - } - } - - info!( - "Phase 2 completed: Found {} new dependency device names", - dependency_device_names.len() - ); - - // Phase 3: Find all excluded devices and remove them from the list - // Convert Vec> to Vec - let excluded_device_path: Vec = vm_cfg - .excluded_devices() - .iter() - .flatten() - .cloned() - .collect(); - let mut all_excludes_devices = excluded_device_path.clone(); - let mut process_excludeds: BTreeSet = excluded_device_path.iter().cloned().collect(); - - for device_path in &excluded_device_path { - // Get all descendant node paths for this device - let descendant_paths = get_descendant_nodes_by_path(&node_cache, device_path); - info!( - "Found {} descendant paths for {}", - descendant_paths.len(), - device_path - ); - - for descendant_path in descendant_paths { - if !process_excludeds.contains(&descendant_path) { - trace!("Found descendant device: {descendant_path}"); - process_excludeds.insert(descendant_path.clone()); - - all_excludes_devices.push(descendant_path.clone()); - } else { - trace!("Device already exists: {descendant_path}"); - } - } - } - info!("Found excluded devices: {all_excludes_devices:?}"); - - // Merge all device name lists - let mut all_device_names = initial_device_names.clone(); - all_device_names.extend(additional_device_names); - all_device_names.extend(dependency_device_names); - - // Remove excluded devices from the final list - if !all_excludes_devices.is_empty() { - info!( - "Removing {} excluded devices from the list", - all_excludes_devices.len() - ); - let excluded_set: BTreeSet = all_excludes_devices.into_iter().collect(); - - // Filter out excluded devices - all_device_names.retain(|device_name| { - let should_keep = !excluded_set.contains(device_name); - if !should_keep { - info!("Excluding device: {device_name}"); - } - should_keep - }); - } - - // Phase 4: remove root node from the list - all_device_names.retain(|device_name| device_name != "/"); - - let final_device_count = all_device_names.len(); - info!( - "Passthrough devices analysis completed. Total devices: {} (added: {})", - final_device_count, - final_device_count - initial_device_count - ); - - // Print final device list - for (i, device_name) in all_device_names.iter().enumerate() { - trace!("Final passthrough device[{i}]: {device_name}"); - } - - all_device_names -} - -/// Build the full path of a node based on node level relationships -/// Build the path by traversing all nodes and constructing paths based on level relationships to avoid path conflicts for nodes with the same name -pub fn build_node_path(all_nodes: &[Node], target_index: usize) -> String { - let mut path_stack: Vec = Vec::new(); - - for node in all_nodes.iter().take(target_index + 1) { - let level = node.level; - - if level == 1 { - path_stack.clear(); - if node.name() != "/" { - path_stack.push(node.name().to_string()); - } - } else { - while path_stack.len() >= level - 1 { - path_stack.pop(); - } - path_stack.push(node.name().to_string()); - } - } - - // Build the full path of the current node - if path_stack.is_empty() || (path_stack.len() == 1 && path_stack[0] == "/") { - "/".to_string() - } else { - "/".to_string() + &path_stack.join("/") - } -} - -/// Build a simplified node cache table, traverse all nodes once and group by full path -/// Use level relationships to directly build paths, avoiding path conflicts for nodes with the same name -pub fn build_optimized_node_cache<'a>(fdt: &'a Fdt) -> BTreeMap>> { - let mut node_cache: BTreeMap>> = BTreeMap::new(); - - let all_nodes: Vec = fdt.all_nodes().collect(); - - for (index, node) in all_nodes.iter().enumerate() { - let node_path = build_node_path(&all_nodes, index); - if let Some(existing_nodes) = node_cache.get(&node_path) - && !existing_nodes.is_empty() - { - error!( - "Duplicate node path found: {} for node '{}' at level {}, existing node: '{}'", - node_path, - node.name(), - node.level, - existing_nodes[0].name() - ); - } - - trace!( - "Adding node to cache: {} (level: {}, index: {})", - node_path, node.level, index - ); - node_cache.entry(node_path).or_default().push(node.clone()); - } - - debug!( - "Built simplified node cache with {} unique device paths", - node_cache.len() - ); - node_cache -} - -/// Build a mapping table from phandle to node information, optimized version using fdt-parser convenience methods -/// Use full path instead of node name -/// Use level relationships to directly build paths, avoiding path conflicts for nodes with the same name -fn build_phandle_map(fdt: &Fdt) -> BTreeMap)> { - let mut phandle_map = BTreeMap::new(); - - let all_nodes: Vec = fdt.all_nodes().collect(); - - for (index, node) in all_nodes.iter().enumerate() { - let node_path = build_node_path(&all_nodes, index); - - // Collect node properties - let mut phandle = None; - let mut cells_map = BTreeMap::new(); - for prop in node.propertys() { - match prop.name { - "phandle" | "linux,phandle" => { - phandle = Some(prop.u32()); - } - "#address-cells" - | "#size-cells" - | "#clock-cells" - | "#reset-cells" - | "#gpio-cells" - | "#interrupt-cells" - | "#power-domain-cells" - | "#thermal-sensor-cells" - | "#phy-cells" - | "#dma-cells" - | "#sound-dai-cells" - | "#mbox-cells" - | "#pwm-cells" - | "#iommu-cells" => { - cells_map.insert(prop.name.to_string(), prop.u32()); - } - _ => {} - } - } - - // If phandle is found, store it together with the node's full path - if let Some(ph) = phandle { - phandle_map.insert(ph, (node_path, cells_map)); - } - } - phandle_map -} - -/// Parse properties containing phandle references intelligently based on #*-cells properties -/// Supports multiple formats: -/// - Single phandle: -/// - phandle+specifier: -/// - Multiple phandle references: -fn parse_phandle_property_with_cells( - prop_data: &[u8], - prop_name: &str, - phandle_map: &BTreeMap)>, -) -> Vec<(u32, Vec)> { - let mut results = Vec::new(); - - debug!( - "Parsing property '{}' with cells info, data length: {} bytes", - prop_name, - prop_data.len() - ); - - if prop_data.is_empty() || prop_data.len() % 4 != 0 { - warn!( - "Property '{}' data length ({} bytes) is invalid", - prop_name, - prop_data.len() - ); - return results; - } - - let u32_values: Vec = prop_data - .chunks(4) - .map(|chunk| u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]])) - .collect(); - - let mut i = 0; - while i < u32_values.len() { - let potential_phandle = u32_values[i]; - - // Check if it's a valid phandle - if let Some((device_name, cells_info)) = phandle_map.get(&potential_phandle) { - // Determine the number of cells required based on property name - let cells_count = get_cells_count_for_property(prop_name, cells_info); - trace!( - "Property '{prop_name}' requires {cells_count} cells for device '{device_name}'" - ); - - // Check if there's enough data - if i + cells_count < u32_values.len() { - let specifiers: Vec = u32_values[i + 1..=i + cells_count].to_vec(); - debug!( - "Parsed phandle reference: phandle={potential_phandle:#x}, specifiers={specifiers:?}" - ); - results.push((potential_phandle, specifiers)); - i += cells_count + 1; // Skip phandle and all specifiers - } else { - warn!( - "Property:{} not enough data for phandle {:#x}, expected {} cells but only {} values remaining", - prop_name, - potential_phandle, - cells_count, - u32_values.len() - i - 1 - ); - break; - } - } else { - // If not a valid phandle, skip this value - i += 1; - } - } - - results -} - -/// Determine the required number of cells based on property name and target node's cells information -fn get_cells_count_for_property(prop_name: &str, cells_info: &BTreeMap) -> usize { - let cells_property = match prop_name { - "clocks" | "assigned-clocks" => "#clock-cells", - "resets" => "#reset-cells", - "power-domains" => "#power-domain-cells", - "phys" => "#phy-cells", - "interrupts" | "interrupts-extended" => "#interrupt-cells", - "gpios" => "#gpio-cells", - _ if prop_name.ends_with("-gpios") || prop_name.ends_with("-gpio") => "#gpio-cells", - "dmas" => "#dma-cells", - "thermal-sensors" => "#thermal-sensor-cells", - "sound-dai" => "#sound-dai-cells", - "mboxes" => "#mbox-cells", - "pwms" => "#pwm-cells", - _ => { - debug!("Unknown property '{prop_name}', defaulting to 0 cell"); - return 0; - } - }; - - cells_info.get(cells_property).copied().unwrap_or(0) as usize -} - -/// Generic phandle property parsing function -/// Parse phandle references according to cells information with correct block size -/// Support single phandle and multiple phandle+specifier formats -/// Return full path instead of node name -fn parse_phandle_property( - prop_data: &[u8], - prop_name: &str, - phandle_map: &BTreeMap)>, -) -> Vec { - let mut dependencies = Vec::new(); - - let phandle_refs = parse_phandle_property_with_cells(prop_data, prop_name, phandle_map); - - for (phandle, specifiers) in phandle_refs { - if let Some((device_path, _cells_info)) = phandle_map.get(&phandle) { - let spec_info = if !specifiers.is_empty() { - format!(" (specifiers: {specifiers:?})") - } else { - String::new() - }; - debug!( - "Found {prop_name} dependency: phandle={phandle:#x}, device={device_path}{spec_info}" - ); - dependencies.push(device_path.clone()); - } - } - - dependencies -} - -/// Device property classifier - used to identify properties that require special handling -struct DevicePropertyClassifier; - -impl DevicePropertyClassifier { - /// Phandle properties that require special handling - includes all properties that need dependency resolution - const PHANDLE_PROPERTIES: &'static [&'static str] = &[ - "clocks", - "power-domains", - "phys", - "resets", - "dmas", - "thermal-sensors", - "mboxes", - "assigned-clocks", - "interrupt-parent", - "phy-handle", - "msi-parent", - "memory-region", - "syscon", - "regmap", - "iommus", - "interconnects", - "nvmem-cells", - "sound-dai", - "pinctrl-0", - "pinctrl-1", - "pinctrl-2", - "pinctrl-3", - "pinctrl-4", - ]; - - /// Determine if it's a phandle property that requires handling - fn is_phandle_property(prop_name: &str) -> bool { - Self::PHANDLE_PROPERTIES.contains(&prop_name) - || prop_name.ends_with("-supply") - || prop_name == "gpios" - || prop_name.ends_with("-gpios") - || prop_name.ends_with("-gpio") - || (prop_name.contains("cells") && !prop_name.starts_with("#") && prop_name.len() >= 4) - } -} - -/// Find device dependencies -fn find_device_dependencies( - device_node_path: &str, - phandle_map: &BTreeMap)>, - node_cache: &BTreeMap>, // Add node_cache parameter -) -> Vec { - let mut dependencies = Vec::new(); - - // Directly find nodes from node_cache, avoiding traversing all nodes - if let Some(nodes) = node_cache.get(device_node_path) { - // Traverse all properties of nodes to find dependencies - for node in nodes { - for prop in node.propertys() { - // Determine if it's a phandle property that needs to be processed - if DevicePropertyClassifier::is_phandle_property(prop.name) { - let mut prop_deps = - parse_phandle_property(prop.raw_value(), prop.name, phandle_map); - dependencies.append(&mut prop_deps); - } - } - } - } - - dependencies -} - -/// Get all descendant nodes based on parent node path (including child nodes, grandchild nodes, etc.) -/// Find all descendant nodes by looking up nodes with parent node path as prefix in node_cache -fn get_descendant_nodes_by_path<'a>( - node_cache: &'a BTreeMap>>, - parent_path: &str, -) -> Vec { - let mut descendant_paths = Vec::new(); - - // Special handling if parent path is root path - let search_prefix = if parent_path == "/" { - "/".to_string() - } else { - parent_path.to_string() + "/" - }; - - // Traverse node_cache, find all nodes with parent path as prefix - for path in node_cache.keys() { - // Check if path has parent path as prefix (and is not the parent path itself) - if path.starts_with(&search_prefix) && path.len() > search_prefix.len() { - // This is a descendant node path, add to results - descendant_paths.push(path.clone()); - } - } - - descendant_paths -} diff --git a/kernel/src/vmm/fdt/mod.rs b/kernel/src/vmm/fdt/mod.rs deleted file mode 100644 index b76da293..00000000 --- a/kernel/src/vmm/fdt/mod.rs +++ /dev/null @@ -1,122 +0,0 @@ -//! FDT (Flattened Device Tree) processing module for AxVisor. -//! -//! This module provides functionality for parsing and processing device tree blobs, -//! including CPU configuration, passthrough device detection, and FDT generation. - -mod create; -mod device; -mod parser; -mod print; - -use alloc::collections::BTreeMap; -use alloc::vec::Vec; -use axvm::config::{AxVMConfig, AxVMCrateConfig}; -use fdt_parser::Fdt; -use lazyinit::LazyInit; -use spin::Mutex; - -pub use parser::*; -// pub use print::print_fdt; -pub use create::*; -pub use device::build_node_path; - -use crate::vmm::config::{config, get_vm_dtb_arc}; - -// DTB cache for generated device trees -static GENERATED_DTB_CACHE: LazyInit>>> = LazyInit::new(); - -/// Initialize the DTB cache -pub fn init_dtb_cache() { - GENERATED_DTB_CACHE.init_once(Mutex::new(BTreeMap::new())); -} - -/// Get reference to the DTB cache -pub fn dtb_cache() -> &'static Mutex>> { - GENERATED_DTB_CACHE.get().unwrap() -} - -/// Generate guest FDT cache the result -/// # Return Value -/// Returns the generated DTB data and stores it in the global cache -pub fn crate_guest_fdt_with_cache(dtb_data: Vec, crate_config: &AxVMCrateConfig) { - // Store data in global cache - let mut cache_lock = dtb_cache().lock(); - cache_lock.insert(crate_config.base.id, dtb_data); -} - -/// Handle all FDT-related operations for aarch64 architecture -pub fn handle_fdt_operations(vm_config: &mut AxVMConfig, vm_create_config: &AxVMCrateConfig) { - let host_fdt_bytes = get_host_fdt(); - let host_fdt = Fdt::from_bytes(host_fdt_bytes) - .map_err(|e| format!("Failed to parse FDT: {e:#?}")) - .expect("Failed to parse FDT"); - set_phys_cpu_sets(vm_config, &host_fdt, vm_create_config); - - if let Some(provided_dtb) = get_developer_provided_dtb(vm_config, vm_create_config) { - info!("VM[{}] found DTB , parsing...", vm_config.id()); - update_provided_fdt(&provided_dtb, host_fdt_bytes, vm_create_config); - } else { - info!( - "VM[{}] DTB not found, generating based on the configuration file.", - vm_config.id() - ); - setup_guest_fdt_from_vmm(host_fdt_bytes, vm_config, vm_create_config); - } - - // Overlay VM config with the given DTB. - if let Some(dtb_arc) = get_vm_dtb_arc(vm_config) { - let dtb = dtb_arc.as_ref(); - parse_passthrough_devices_address(vm_config, dtb); - parse_vm_interrupt(vm_config, dtb); - } else { - error!( - "VM[{}] DTB not found in memory, skipping...", - vm_config.id() - ); - } -} - -pub fn get_developer_provided_dtb( - vm_cfg: &AxVMConfig, - crate_config: &AxVMCrateConfig, -) -> Option> { - match crate_config.kernel.image_location.as_deref() { - Some("memory") => { - let vm_imags = config::get_memory_images() - .iter() - .find(|&v| v.id == vm_cfg.id())?; - - if let Some(dtb) = vm_imags.dtb { - info!("DTB file in memory, size: 0x{:x}", dtb.len()); - return Some(dtb.to_vec()); - } - } - #[cfg(feature = "fs")] - Some("fs") => { - use axerrno::ax_err_type; - use std::io::{BufReader, Read}; - if let Some(dtb_path) = &crate_config.kernel.dtb_path { - let (dtb_file, dtb_size) = - crate::vmm::images::fs::open_image_file(dtb_path).unwrap(); - info!("DTB file in fs, size: 0x{:x}", dtb_size); - - let mut file = BufReader::new(dtb_file); - let mut dtb_buffer = vec![0; dtb_size]; - - file.read_exact(&mut dtb_buffer) - .map_err(|err| { - ax_err_type!( - Io, - format!("Failed in reading from file {}, err {:?}", dtb_path, err) - ) - }) - .unwrap(); - return Some(dtb_buffer); - } - } - _ => unimplemented!( - "Check your \"image_location\" in config.toml, \"memory\" and \"fs\" are supported,\n." - ), - } - None -} diff --git a/kernel/src/vmm/fdt/parser.rs b/kernel/src/vmm/fdt/parser.rs deleted file mode 100644 index 5eb81388..00000000 --- a/kernel/src/vmm/fdt/parser.rs +++ /dev/null @@ -1,397 +0,0 @@ -//! FDT parsing and processing functionality. - -use alloc::{string::ToString, vec::Vec}; -use axvm::config::{AxVMConfig, AxVMCrateConfig, PassThroughDeviceConfig}; -use fdt_parser::{Fdt, FdtHeader, PciRange, PciSpace}; - -use crate::vmm::fdt::crate_guest_fdt_with_cache; -use crate::vmm::fdt::create::update_cpu_node; - -pub fn get_host_fdt() -> &'static [u8] { - const FDT_VALID_MAGIC: u32 = 0xd00d_feed; - let bootarg: usize = std::os::arceos::modules::axhal::dtb::get_bootarg(); - let header = unsafe { - core::slice::from_raw_parts(bootarg as *const u8, core::mem::size_of::()) - }; - let fdt_header = FdtHeader::from_bytes(header) - .map_err(|e| format!("Failed to parse FDT header: {e:#?}")) - .unwrap(); - - if fdt_header.magic.get() != FDT_VALID_MAGIC { - error!( - "FDT magic is invalid, expected {:#x}, got {:#x}", - FDT_VALID_MAGIC, - fdt_header.magic.get() - ); - } - - unsafe { core::slice::from_raw_parts(bootarg as *const u8, fdt_header.total_size()) } -} - -pub fn setup_guest_fdt_from_vmm( - fdt_bytes: &[u8], - vm_cfg: &mut AxVMConfig, - crate_config: &AxVMCrateConfig, -) { - let fdt = Fdt::from_bytes(fdt_bytes) - .map_err(|e| format!("Failed to parse FDT: {e:#?}")) - .expect("Failed to parse FDT"); - - // Call the modified function and get the returned device name list - let passthrough_device_names = super::device::find_all_passthrough_devices(vm_cfg, &fdt); - - let dtb_data = super::create::crate_guest_fdt(&fdt, &passthrough_device_names, crate_config); - crate_guest_fdt_with_cache(dtb_data, crate_config); -} - -pub fn set_phys_cpu_sets(vm_cfg: &mut AxVMConfig, fdt: &Fdt, crate_config: &AxVMCrateConfig) { - // Find and parse CPU information from host DTB - let host_cpus: Vec<_> = fdt.find_nodes("/cpus/cpu").collect(); - info!("Found {} host CPU nodes", &host_cpus.len()); - - let phys_cpu_ids = crate_config - .base - .phys_cpu_ids - .as_ref() - .expect("ERROR: phys_cpu_ids not found in config.toml"); - - // Collect all CPU node information into Vec to avoid using iterators multiple times - let cpu_nodes_info: Vec<_> = host_cpus - .iter() - .filter_map(|cpu_node| { - if let Some(mut cpu_reg) = cpu_node.reg() { - if let Some(r) = cpu_reg.next() { - info!( - "CPU node: {}, phys_cpu_id: 0x{:x}", - cpu_node.name(), - r.address - ); - Some((cpu_node.name().to_string(), r.address as usize)) - } else { - None - } - } else { - None - } - }) - .collect(); - // Create mapping from phys_cpu_id to physical CPU index - // Collect all unique CPU addresses, maintaining the order of appearance in the device tree - let mut unique_cpu_addresses = Vec::new(); - for (_, cpu_address) in &cpu_nodes_info { - if !unique_cpu_addresses.contains(cpu_address) { - unique_cpu_addresses.push(*cpu_address); - } else { - panic!("Duplicate CPU address found"); - } - } - - // Assign index to each CPU address in the device tree and print detailed information - for (index, &cpu_address) in unique_cpu_addresses.iter().enumerate() { - // Find all CPU nodes using this address - for (cpu_name, node_address) in &cpu_nodes_info { - if *node_address == cpu_address { - debug!( - " CPU node: {cpu_name}, address: 0x{cpu_address:x}, assigned index: {index}" - ); - break; // Print each address only once - } - } - } - - // Calculate phys_cpu_sets based on phys_cpu_ids in vcpu_mappings - let mut new_phys_cpu_sets = Vec::new(); - for phys_cpu_id in phys_cpu_ids { - // Find the index corresponding to phys_cpu_id in unique_cpu_addresses - if let Some(cpu_index) = unique_cpu_addresses - .iter() - .position(|&addr| addr == *phys_cpu_id) - { - let cpu_mask = 1usize << cpu_index; // Convert index to mask bit - new_phys_cpu_sets.push(cpu_mask); - debug!( - "vCPU {} with phys_cpu_id 0x{:x} mapped to CPU index {} (mask: 0x{:x})", - vm_cfg.id(), - phys_cpu_id, - cpu_index, - cpu_mask - ); - } else { - error!( - "vCPU {} with phys_cpu_id 0x{:x} not found in device tree!", - vm_cfg.id(), - phys_cpu_id - ); - } - } - - // Update phys_cpu_sets in VM configuration (if VM configuration supports setting) - info!("Calculated phys_cpu_sets: {new_phys_cpu_sets:?}"); - - vm_cfg - .phys_cpu_ls_mut() - .set_guest_cpu_sets(new_phys_cpu_sets); - - debug!( - "vcpu_mappings: {:?}", - vm_cfg.phys_cpu_ls_mut().get_vcpu_affinities_pcpu_ids() - ); -} - -/// Add address mapping configuration for a device -fn add_device_address_config( - vm_cfg: &mut AxVMConfig, - node_name: &str, - base_address: usize, - size: usize, - index: usize, - prefix: Option<&str>, -) { - // Only process devices with address information - if size == 0 { - return; - } - - // Create a device configuration for each address segment - let device_name = if index == 0 { - match prefix { - Some(p) => format!("{node_name}-{p}"), - None => node_name.to_string(), - } - } else { - match prefix { - Some(p) => format!("{node_name}-{p}-region{index}"), - None => format!("{node_name}-region{index}"), - } - }; - - // Add new device configuration - let pt_dev = axvm::config::PassThroughDeviceConfig { - name: device_name, - base_gpa: base_address, - base_hpa: base_address, - length: size, - irq_id: 0, - }; - vm_cfg.add_pass_through_device(pt_dev); -} - -/// Add ranges property configuration for PCIe devices -fn add_pci_ranges_config(vm_cfg: &mut AxVMConfig, node_name: &str, range: &PciRange, index: usize) { - let base_address = range.cpu_address as usize; - let size = range.size as usize; - - // Only process devices with address information - if size == 0 { - return; - } - - // Create a device configuration for each address segment - let prefix = match range.space { - PciSpace::Configuration => "config", - PciSpace::IO => "io", - PciSpace::Memory32 => "mem32", - PciSpace::Memory64 => "mem64", - }; - - let device_name = if index == 0 { - format!("{node_name}-{prefix}") - } else { - format!("{node_name}-{prefix}-region{index}") - }; - - // Add new device configuration - let pt_dev = axvm::config::PassThroughDeviceConfig { - name: device_name, - base_gpa: base_address, - base_hpa: base_address, - length: size, - irq_id: 0, - }; - vm_cfg.add_pass_through_device(pt_dev); - - trace!( - "Added PCIe passthrough device {}: base=0x{:x}, size=0x{:x}, space={:?}", - node_name, base_address, size, range.space - ); -} - -pub fn parse_passthrough_devices_address(vm_cfg: &mut AxVMConfig, dtb: &[u8]) { - let devices = vm_cfg.pass_through_devices().to_vec(); - if !devices.is_empty() && devices[0].length != 0 { - for (index, device) in devices.iter().enumerate() { - add_device_address_config( - vm_cfg, - &device.name, - device.base_gpa, - device.length, - index, - None, - ); - } - } else { - let fdt = Fdt::from_bytes(dtb) - .expect("Failed to parse DTB image, perhaps the DTB is invalid or corrupted"); - - // Clear existing passthrough device configurations - vm_cfg.clear_pass_through_devices(); - - // Traverse all device tree nodes - for node in fdt.all_nodes() { - // Skip root node - if node.name() == "/" || node.name().starts_with("memory") { - continue; - } - - let node_name = node.name().to_string(); - - // Check if it's a PCIe device node - if node_name.starts_with("pcie@") || node_name.contains("pci") { - // Process PCIe device's ranges property - if let Some(pci) = node.clone().into_pci() - && let Ok(ranges) = pci.ranges() - { - for (index, range) in ranges.enumerate() { - add_pci_ranges_config(vm_cfg, &node_name, &range, index); - } - } - - // Process PCIe device's reg property (ECAM space) - if let Some(reg_iter) = node.reg() { - for (index, reg) in reg_iter.enumerate() { - let base_address = reg.address as usize; - let size = reg.size.unwrap_or(0); - - add_device_address_config( - vm_cfg, - &node_name, - base_address, - size, - index, - Some("ecam"), - ); - } - } - } else { - // Get device's reg property (process regular devices) - if let Some(reg_iter) = node.reg() { - // Process all address segments of the device - for (index, reg) in reg_iter.enumerate() { - // Get device's address and size information - let base_address = reg.address as usize; - let size = reg.size.unwrap_or(0); - - add_device_address_config( - vm_cfg, - &node_name, - base_address, - size, - index, - None, - ); - } - } - } - } - trace!( - "All passthrough devices: {:#x?}", - vm_cfg.pass_through_devices() - ); - debug!( - "Finished parsing passthrough devices, total: {}", - vm_cfg.pass_through_devices().len() - ); - } -} - -pub fn parse_vm_interrupt(vm_cfg: &mut AxVMConfig, dtb: &[u8]) { - const GIC_PHANDLE: usize = 1; - let fdt = Fdt::from_bytes(dtb) - .expect("Failed to parse DTB image, perhaps the DTB is invalid or corrupted"); - - for node in fdt.all_nodes() { - let name = node.name(); - - if name.starts_with("memory") { - continue; - } - // Skip the interrupt controller, as we will use vGIC - // TODO: filter with compatible property and parse its phandle from DT; maybe needs a second pass? - else if name.starts_with("interrupt-controller") - || name.starts_with("intc") - || name.starts_with("its") - { - info!("skipping node {name} to use vGIC"); - continue; - } - - // Collect all GIC_SPI interrupts and add them to vGIC - if let Some(interrupts) = node.interrupts() { - // TODO: skip non-GIC interrupt - if let Some(parent) = node.interrupt_parent() { - trace!("node: {}, intr parent: {}", name, parent.node.name()); - if let Some(phandle) = parent.node.phandle() { - if phandle.as_usize() != GIC_PHANDLE { - debug!( - "node: {}, intr parent: {}, phandle: 0x{:x} is not GIC!", - name, - parent.node.name(), - phandle.as_usize() - ); - } - } else { - warn!( - "node: {}, intr parent: {} no phandle!", - name, - parent.node.name(), - ); - } - } else { - warn!("node: {name} no interrupt parent!"); - } - - for interrupt in interrupts { - // - for (k, v) in interrupt.enumerate() { - match k { - 0 => { - if v == 0 { - trace!("node: {name}, GIC_SPI"); - } else { - debug!("node: {name}, intr type: {v}, not GIC_SPI, not supported!"); - break; - } - } - 1 => { - trace!("node: {name}, interrupt id: 0x{v:x}"); - vm_cfg.add_pass_through_spi(v); - } - 2 => { - trace!("node: {name}, interrupt mode: 0x{v:x}"); - } - _ => { - warn!("unknown interrupt property {k}:0x{v:x}") - } - } - } - } - } - } - - vm_cfg.add_pass_through_device(PassThroughDeviceConfig { - name: "Fake Node".to_string(), - base_gpa: 0x0, - base_hpa: 0x0, - length: 0x20_0000, - irq_id: 0, - }); -} - -pub fn update_provided_fdt(provided_dtb: &[u8], host_dtb: &[u8], crate_config: &AxVMCrateConfig) { - let provided_fdt = Fdt::from_bytes(provided_dtb) - .expect("Failed to parse DTB image, perhaps the DTB is invalid or corrupted"); - let host_fdt = Fdt::from_bytes(host_dtb) - .expect("Failed to parse DTB image, perhaps the DTB is invalid or corrupted"); - let provided_dtb_data = update_cpu_node(&provided_fdt, &host_fdt, crate_config); - crate_guest_fdt_with_cache(provided_dtb_data, crate_config); -} diff --git a/kernel/src/vmm/fdt/print.rs b/kernel/src/vmm/fdt/print.rs deleted file mode 100644 index 29f63f35..00000000 --- a/kernel/src/vmm/fdt/print.rs +++ /dev/null @@ -1,135 +0,0 @@ -//! FDT parsing and processing functionality. - -use fdt_parser::{Fdt, FdtHeader}; - -#[allow(dead_code)] -pub fn print_fdt(fdt_addr: usize) { - const FDT_VALID_MAGIC: u32 = 0xd00d_feed; - let header = unsafe { - core::slice::from_raw_parts(fdt_addr as *const u8, core::mem::size_of::()) - }; - let fdt_header = FdtHeader::from_bytes(header) - .map_err(|e| format!("Failed to parse FDT header: {e:#?}")) - .unwrap(); - - if fdt_header.magic.get() != FDT_VALID_MAGIC { - error!( - "FDT magic is invalid, expected {:#x}, got {:#x}", - FDT_VALID_MAGIC, - fdt_header.magic.get() - ); - return; - } - - let fdt_bytes = - unsafe { core::slice::from_raw_parts(fdt_addr as *const u8, fdt_header.total_size()) }; - - let fdt = Fdt::from_bytes(fdt_bytes) - .map_err(|e| format!("Failed to parse FDT: {e:#?}")) - .expect("Failed to parse FDT"); - - // Statistics of node count and level distribution - let mut node_count = 0; - let mut level_counts = alloc::collections::BTreeMap::new(); - let mut max_level = 0; - - info!("=== FDT Node Information Statistics ==="); - - // Traverse all nodes once for statistics (following optimization strategy) - for node in fdt.all_nodes() { - node_count += 1; - - // Count nodes by level - *level_counts.entry(node.level).or_insert(0) += 1; - - // Record maximum level - if node.level > max_level { - max_level = node.level; - } - - // Count property numbers - let node_properties_count = node.propertys().count(); - - trace!( - "Node[{}]: {} (Level: {}, Properties: {})", - node_count, - node.name(), - node.level, - node_properties_count - ); - - for prop in node.propertys() { - trace!( - "Properties: {}, Raw_value: {:x?}", - prop.name, - prop.raw_value() - ); - } - } - - info!("=== FDT Statistics Results ==="); - info!("Total node count: {node_count}"); - info!("FDT total size: {} bytes", fdt_header.total_size()); - info!("Maximum level depth: {max_level}"); - - info!("Node distribution by level:"); - for (level, count) in level_counts { - let percentage = (count as f32 / node_count as f32) * 100.0; - info!(" Level {level}: {count} nodes ({percentage:.1}%)"); - } -} - -#[allow(dead_code)] -pub fn print_guest_fdt(fdt_bytes: &[u8]) { - let fdt = Fdt::from_bytes(fdt_bytes) - .map_err(|e| format!("Failed to parse FDT: {e:#?}")) - .expect("Failed to parse FDT"); - // Statistics of node count and level distribution - let mut node_count = 0; - let mut level_counts = alloc::collections::BTreeMap::new(); - let mut max_level = 0; - - info!("=== FDT Node Information Statistics ==="); - - // Traverse all nodes once for statistics (following optimization strategy) - for node in fdt.all_nodes() { - node_count += 1; - - // Count nodes by level - *level_counts.entry(node.level).or_insert(0) += 1; - - // Record maximum level - if node.level > max_level { - max_level = node.level; - } - - // Count property numbers - let node_properties_count = node.propertys().count(); - - info!( - "Node[{}]: {} (Level: {}, Properties: {})", - node_count, - node.name(), - node.level, - node_properties_count - ); - - for prop in node.propertys() { - info!( - "Properties: {}, Raw_value: {:x?}", - prop.name, - prop.raw_value() - ); - } - } - - info!("=== FDT Statistics Results ==="); - info!("Total node count: {node_count}"); - info!("Maximum level depth: {max_level}"); - - info!("Node distribution by level:"); - for (level, count) in level_counts { - let percentage = (count as f32 / node_count as f32) * 100.0; - info!(" Level {level}: {count} nodes ({percentage:.1}%)"); - } -} diff --git a/kernel/src/vmm/images/mod.rs b/kernel/src/vmm/images/mod.rs index 98e10547..62b5f494 100644 --- a/kernel/src/vmm/images/mod.rs +++ b/kernel/src/vmm/images/mod.rs @@ -4,7 +4,7 @@ use axvm::GuestPhysAddr; use axvm::config::{AxVMCrateConfig, VMImageConfig, VMImagesConfig}; use axvmconfig::ImageLocation; -use crate::vmm::config::config::MemoryImage; +use crate::config::config::MemoryImage; mod linux; @@ -71,8 +71,73 @@ fn memory_image(config: &AxVMCrateConfig) -> &'static MemoryImage { #[cfg(feature = "fs")] pub mod fs { use super::*; + use alloc::vec::Vec; - pub fn load_images_fs(_config: &AxVMCrateConfig) -> anyhow::Result { - todo!() + pub fn load_images_fs(config: &AxVMCrateConfig) -> anyhow::Result { + // Load kernel image + let kernel = load_image_file(&config.kernel.kernel_path)?; + let kernel = VMImageConfig { + gpa: config.kernel.kernel_load_addr.map(GuestPhysAddr::from), + data: kernel, + }; + + // Load BIOS image if configured + let bios = if let Some(bios_path) = &config.kernel.bios_path { + let bios_data = load_image_file(bios_path)?; + Some(VMImageConfig { + gpa: config.kernel.bios_load_addr.map(GuestPhysAddr::from), + data: bios_data, + }) + } else { + None + }; + + // Load DTB image if configured + let dtb = if let Some(dtb_path) = &config.kernel.dtb_path { + let dtb_data = load_image_file(dtb_path)?; + Some(VMImageConfig { + gpa: config.kernel.dtb_load_addr.map(GuestPhysAddr::from), + data: dtb_data, + }) + } else { + None + }; + + // Load ramdisk image if configured + let ramdisk = if let Some(ramdisk_path) = &config.kernel.ramdisk_path { + let ramdisk_data = load_image_file(ramdisk_path)?; + Some(VMImageConfig { + gpa: config.kernel.ramdisk_load_addr.map(GuestPhysAddr::from), + data: ramdisk_data, + }) + } else { + None + }; + + Ok(VMImagesConfig { + kernel, + bios, + dtb, + ramdisk, + }) + } + + fn load_image_file(path: &str) -> anyhow::Result> { + use axstd::io::Read; + + let mut file = axstd::fs::File::open(path) + .map_err(|e| anyhow::anyhow!("Failed to open image file '{}': {}", path, e))?; + + let metadata = file + .metadata() + .map_err(|e| anyhow::anyhow!("Failed to get metadata for '{}': {}", path, e))?; + + let file_size = metadata.len() as usize; + let mut buffer = Vec::with_capacity(file_size); + + file.read_to_end(&mut buffer) + .map_err(|e| anyhow::anyhow!("Failed to read image file '{}': {}", path, e))?; + + Ok(buffer) } } diff --git a/kernel/src/vmm/mod.rs b/kernel/src/vmm/mod.rs index 95801907..1387ec36 100644 --- a/kernel/src/vmm/mod.rs +++ b/kernel/src/vmm/mod.rs @@ -6,7 +6,7 @@ pub mod images; // pub mod timer; pub mod vm_list; -use axvm::AxVMConfig; +use axvm::{AxVMConfig, VmId}; /// Initialize the VMM. /// @@ -25,12 +25,12 @@ pub fn start_preconfigured_vms() -> anyhow::Result<()> { Ok(()) } -pub fn start_vm(config: AxVMConfig) -> anyhow::Result<()> { +pub fn start_vm(config: AxVMConfig) -> anyhow::Result { debug!("Starting guest VM `{}`", config.name()); let vm = axvm::Vm::new(config)?; let vm = vm_list::push_vm(vm); vm.boot()?; - Ok(()) + Ok(vm.id()) } pub fn wait_for_all_vms_exit() { diff --git a/kernel/src/vmm/vcpus.rs b/kernel/src/vmm/vcpus.rs index 22cb47fe..666d029a 100644 --- a/kernel/src/vmm/vcpus.rs +++ b/kernel/src/vmm/vcpus.rs @@ -240,7 +240,7 @@ pub(crate) fn notify_all_vcpus(vm_id: usize) { /// /// This should be called after all VCpu threads have exited to avoid resource leaks. /// It will join all VCpu tasks to ensure they are fully cleaned up. -pub(crate) fn cleanup_vm_vcpus(vm_id: usize) { +pub fn cleanup_vm_vcpus(vm_id: usize) { if let Some(vm_vcpus) = VM_VCPU_TASK_WAIT_QUEUE.remove(&vm_id) { let task_count = vm_vcpus.vcpu_task_list.len(); diff --git a/kernel/src/vmm/vm_list.rs b/kernel/src/vmm/vm_list.rs index 2a878a04..33f39ff1 100644 --- a/kernel/src/vmm/vm_list.rs +++ b/kernel/src/vmm/vm_list.rs @@ -1,5 +1,4 @@ use alloc::{collections::BTreeMap, sync::Arc, vec::Vec}; - use spin::Mutex; pub type VMRef = Arc; diff --git a/modules/axfs/src/fs/fatfs.rs b/modules/axfs/src/fs/fatfs.rs index da0b6f11..b62d25cb 100644 --- a/modules/axfs/src/fs/fatfs.rs +++ b/modules/axfs/src/fs/fatfs.rs @@ -4,8 +4,8 @@ use core::cell::OnceCell; use axfs_vfs::{VfsDirEntry, VfsError, VfsNodePerm, VfsResult}; use axfs_vfs::{VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType, VfsOps}; -use spin::Mutex; use fatfs::{Dir, File, LossyOemCpConverter, NullTimeProvider, Read, Seek, SeekFrom, Write}; +use spin::Mutex; use crate::dev::{Disk, Partition}; diff --git a/modules/axvm b/modules/axvm index bce19d92..b4b88e06 160000 --- a/modules/axvm +++ b/modules/axvm @@ -1 +1 @@ -Subproject commit bce19d92ee18ee6e38d480acbd6c90bc958a1681 +Subproject commit b4b88e06703b20b2f3b3eef409fafbf191cea679 From 419292298a66c78498c730b10a2527a1eca18fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 29 Jan 2026 14:10:03 +0800 Subject: [PATCH 09/19] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8=20cargo-bins?= =?UTF-8?q?=20=E5=AE=89=E8=A3=85=20ostool=EF=BC=8C=E7=AE=80=E5=8C=96?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test-board.yml | 6 ++++-- .github/workflows/test-qemu.yml | 14 ++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test-board.yml b/.github/workflows/test-board.yml index 52e7d515..58efad2e 100644 --- a/.github/workflows/test-board.yml +++ b/.github/workflows/test-board.yml @@ -31,8 +31,10 @@ jobs: with: submodules: "recursive" - - name: Install dependencies - run: cargo +stable install ostool --version ^0.8 + - uses: cargo-bins/cargo-binstall@main + + - name: Install ostool + run: cargo binstall ostool - name: Run tests run: | diff --git a/.github/workflows/test-qemu.yml b/.github/workflows/test-qemu.yml index 136e43b9..a52177f6 100644 --- a/.github/workflows/test-qemu.yml +++ b/.github/workflows/test-qemu.yml @@ -20,10 +20,10 @@ jobs: # vmconfigs: configs/vms/arceos-riscv64-qemu-smp1.toml # vmconfigs_name: ArceOS # vmimage_name: qemu_arceos_riscv64 - - arch: x86_64 - vmconfigs: configs/vms/nimbos-x86_64-qemu-smp1.toml - vmconfigs_name: NimbOS - vmimage_name: qemu_x86_64_nimbos + # - arch: x86_64 + # vmconfigs: configs/vms/nimbos-x86_64-qemu-smp1.toml + # vmconfigs_name: NimbOS + # vmimage_name: qemu_x86_64_nimbos fail-fast: false runs-on: - self-hosted @@ -35,8 +35,10 @@ jobs: with: submodules: "recursive" - - name: Install dependencies - run: cargo +stable install ostool --version ^0.8 + - uses: cargo-bins/cargo-binstall@main + + - name: Install ostool + run: cargo binstall ostool - name: Download images and patch vm configs run: | From 71d84c5f6521e69da8c03265831eed144159c93b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 29 Jan 2026 14:41:56 +0800 Subject: [PATCH 10/19] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20ostool=20?= =?UTF-8?q?=E5=AE=89=E8=A3=85=E6=96=B9=E5=BC=8F=EF=BC=8C=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=20cargo=20+stable=20=E5=AE=89=E8=A3=85=E6=8C=87=E5=AE=9A?= =?UTF-8?q?=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test-board.yml | 3 ++- .github/workflows/test-qemu.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-board.yml b/.github/workflows/test-board.yml index 58efad2e..3e346787 100644 --- a/.github/workflows/test-board.yml +++ b/.github/workflows/test-board.yml @@ -34,7 +34,8 @@ jobs: - uses: cargo-bins/cargo-binstall@main - name: Install ostool - run: cargo binstall ostool + # run: cargo binstall ostool + run: cargo +stable install ostool --version ^0.8 - name: Run tests run: | diff --git a/.github/workflows/test-qemu.yml b/.github/workflows/test-qemu.yml index a52177f6..b1c15412 100644 --- a/.github/workflows/test-qemu.yml +++ b/.github/workflows/test-qemu.yml @@ -38,7 +38,8 @@ jobs: - uses: cargo-bins/cargo-binstall@main - name: Install ostool - run: cargo binstall ostool + # run: cargo binstall ostool + run: cargo +stable install ostool --version ^0.8 - name: Download images and patch vm configs run: | From e6b846e08eb244747b08d0e1276c4f51f2929622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 29 Jan 2026 15:47:12 +0800 Subject: [PATCH 11/19] =?UTF-8?q?fix:=20=E5=B0=86=20Rust=20=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E9=93=BE=E9=80=9A=E9=81=93=E6=9B=B4=E6=96=B0=E4=B8=BA?= =?UTF-8?q?=20nightly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 4d58874b..e551fb71 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] profile = "minimal" -channel = "nightly-2025-12-12" +channel = "nightly" components = ["rust-src", "llvm-tools", "rustfmt", "clippy"] targets = ["x86_64-unknown-none", "riscv64gc-unknown-none-elf", "aarch64-unknown-none-softfloat"] From fc057a58ee1e8eb8127d52a2a08204bca898d47c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 29 Jan 2026 16:47:07 +0800 Subject: [PATCH 12/19] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20derive=5Fmor?= =?UTF-8?q?e=20=E5=92=8C=20unicode-xid=20=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 ++ modules/axvm | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 756f83ba..5d21588e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1143,6 +1143,7 @@ dependencies = [ "bitmap-allocator", "cfg-if", "cpumask", + "derive_more", "fdt-edit", "kspin", "lazyinit", @@ -2013,6 +2014,7 @@ dependencies = [ "quote", "rustc_version", "syn 2.0.111", + "unicode-xid", ] [[package]] diff --git a/modules/axvm b/modules/axvm index b4b88e06..fb0b01ad 160000 --- a/modules/axvm +++ b/modules/axvm @@ -1 +1 @@ -Subproject commit b4b88e06703b20b2f3b3eef409fafbf191cea679 +Subproject commit fb0b01ad150e7ca83b9e9852d542b4b5b9cceea2 From a7f0a12d9b6bcd0391bc3b762daa8878e486780e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 29 Jan 2026 17:03:15 +0800 Subject: [PATCH 13/19] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E8=A7=A3=E6=9E=90=E5=92=8C=E6=89=A7=E8=A1=8C=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 1 - kernel/src/shell/commands/mod.rs | 15 +++--- kernel/src/shell/commands/vm.rs | 54 +++++++++---------- kernel/src/shell/completion.rs | 30 ++++++----- kernel/src/shell/mod.rs | 6 +-- kernel/src/shell/parser/history.rs | 2 +- kernel/src/shell/parser/mod.rs | 4 +- .../parser/{parser.rs => parser_impl.rs} | 0 kernel/src/shell/{shell.rs => shell_impl.rs} | 35 +++++++----- modules/axvm | 2 +- 10 files changed, 77 insertions(+), 72 deletions(-) rename kernel/src/shell/parser/{parser.rs => parser_impl.rs} (100%) rename kernel/src/shell/{shell.rs => shell_impl.rs} (91%) diff --git a/Cargo.lock b/Cargo.lock index 5d21588e..d4968a96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1151,7 +1151,6 @@ dependencies = [ "memory_addr", "page_table_entry", "page_table_multiarch", - "percpu", "ranges-ext", "raw-cpuid 11.6.0", "spin 0.10.0", diff --git a/kernel/src/shell/commands/mod.rs b/kernel/src/shell/commands/mod.rs index e93c0bec..75a7d05b 100644 --- a/kernel/src/shell/commands/mod.rs +++ b/kernel/src/shell/commands/mod.rs @@ -41,16 +41,17 @@ fn build_command_tree() -> BTreeMap { /// Execute a parsed command pub fn execute_command(input: &str) -> Result<(), ParseError> { - let parsed = CommandParser::parse(input, &*COMMAND_TREE)?; + let parsed = CommandParser::parse(input, &COMMAND_TREE)?; // Find the corresponding command node - let mut current_node = (*COMMAND_TREE).get(&parsed.command_path[0]).ok_or_else(|| { - ParseError::UnknownCommand(parsed.command_path[0].clone()) - })?; + let mut current_node = (*COMMAND_TREE) + .get(&parsed.command_path[0]) + .ok_or_else(|| ParseError::UnknownCommand(parsed.command_path[0].clone()))?; for cmd in &parsed.command_path[1..] { - current_node = current_node.subcommands.get(cmd).ok_or_else(|| { - ParseError::UnknownCommand(cmd.clone()) - })?; + current_node = current_node + .subcommands + .get(cmd) + .ok_or_else(|| ParseError::UnknownCommand(cmd.clone()))?; } // Execute the command diff --git a/kernel/src/shell/commands/vm.rs b/kernel/src/shell/commands/vm.rs index cc2d2543..70487609 100644 --- a/kernel/src/shell/commands/vm.rs +++ b/kernel/src/shell/commands/vm.rs @@ -2,18 +2,19 @@ //! //! Commands for managing virtual machines (create, start, stop, list, etc.). -use alloc::collections::BTreeMap; -use alloc::string::{String, ToString}; -use alloc::vec::Vec; +use alloc::{ + collections::BTreeMap, + string::{String, ToString}, +}; #[cfg(feature = "fs")] use axstd::fs::read_to_string; use axstd::println; +use crate::vmm::config::build_vmconfig; use crate::vmm::vm_list; -use crate::vmm::{config::build_vmconfig, start_vm}; -use axvm::config::AxVMCrateConfig; use axvm::VMStatus; +use axvm::config::AxVMCrateConfig; use super::super::parser::{CommandNode, FlagDef, OptionDef, ParsedCommand}; @@ -92,25 +93,20 @@ fn vm_create(cmd: &ParsedCommand) { }; match build_vmconfig(config_info) { - Ok(vm_config) => { - match axvm::Vm::new(vm_config) { - Ok(vm) => { - let vm = vm_list::push_vm(vm); - let vm_id = vm.id(); - println!( - "✓ Successfully created VM[{}] from config: {}", - vm_id, config_path - ); - println!("{:?}", vm.status()); - } - Err(e) => { - println!( - "✗ Failed to create VM from {}: {:?}", - config_path, e - ); - } + Ok(vm_config) => match axvm::Vm::new(vm_config) { + Ok(vm) => { + let vm = vm_list::push_vm(vm); + let vm_id = vm.id(); + println!( + "✓ Successfully created VM[{}] from config: {}", + vm_id, config_path + ); + println!("{:?}", vm.status()); } - } + Err(e) => { + println!("✗ Failed to create VM from {}: {:?}", config_path, e); + } + }, Err(e) => { println!("✗ Failed to build VM config from {}: {:?}", config_path, e); } @@ -352,7 +348,10 @@ fn delete_vm_by_id(vm_id: usize, force: bool) { VMStatus::Running => { if !force { println!("✗ VM[{}] is currently running", vm_id); - println!(" Use 'vm stop {}' first, or use '--force' to force delete", vm_id); + println!( + " Use 'vm stop {}' first, or use '--force' to force delete", + vm_id + ); return; } println!("⚠ Force deleting running VM[{}]...", vm_id); @@ -468,10 +467,7 @@ fn vm_list(cmd: &ParsedCommand) { "{:<6} {:<15} {:<12} {:<10} {:<10}", "VM ID", "NAME", "STATUS", "VCPU", "MEMORY" ); - println!( - "{:-<6} {:-<15} {:-<12} {:-<10} {:-<10}", - "", "", "", "", "" - ); + println!("{:-<6} {:-<15} {:-<12} {:-<10} {:-<10}", "", "", "", "", ""); for vm in display_vms { let vm: vm_list::VMRef = vm; @@ -596,7 +592,7 @@ pub fn register_vm_commands(tree: &mut BTreeMap) { .with_short('c') .with_long("console"), ); - + let status_cmd = CommandNode::new("Stop a virtual machine") .with_handler(vm_status) .with_usage("vm stop [OPTIONS] ..."); diff --git a/kernel/src/shell/completion.rs b/kernel/src/shell/completion.rs index 06d981bb..00845b35 100644 --- a/kernel/src/shell/completion.rs +++ b/kernel/src/shell/completion.rs @@ -12,21 +12,12 @@ pub struct CompletionResult { pub prefix: String, /// List of all possible completions pub matches: Vec, - /// The text that should be inserted (suffix to add) - pub insert_text: String, } impl CompletionResult { /// Create a new completion result pub fn new(prefix: String, matches: Vec) -> Self { - let insert_text = if matches.len() == 1 { - // For single match, insert the full match - matches[0].clone() - } else { - // For multiple matches, insert the common prefix - prefix.clone() - }; - Self { prefix, matches, insert_text } + Self { prefix, matches } } /// Check if there's exactly one match @@ -35,6 +26,7 @@ impl CompletionResult { } /// Check if there are no matches + #[allow(dead_code)] pub fn is_empty(&self) -> bool { self.matches.is_empty() } @@ -161,11 +153,15 @@ fn complete_filename(partial: &str) -> Option { // If partial ends with '/', complete everything in that directory if last_slash == partial.len() - 1 { // dir_path is everything up to and including the last slash - (&partial[..], "", partial.to_string()) + (partial, "", partial.to_string()) } else { // dir_path is everything up to and including the last slash // file_prefix is everything after the last slash - (&partial[..=last_slash], &partial[last_slash + 1..], partial[..=last_slash].to_string()) + ( + &partial[..=last_slash], + &partial[last_slash + 1..], + partial[..=last_slash].to_string(), + ) } } else { (".", partial, String::new()) @@ -268,9 +264,15 @@ mod tests { fn test_find_common_prefix() { assert_eq!(find_common_prefix(&[]), ""); assert_eq!(find_common_prefix(&["test".into()]), "test"); - assert_eq!(find_common_prefix(&["test".into(), "testing".into()]), "test"); + assert_eq!( + find_common_prefix(&["test".into(), "testing".into()]), + "test" + ); assert_eq!(find_common_prefix(&["foo".into(), "bar".into()]), ""); - assert_eq!(find_common_prefix(&["file1.txt".into(), "file2.txt".into()]), "file"); + assert_eq!( + find_common_prefix(&["file1.txt".into(), "file2.txt".into()]), + "file" + ); } #[test] diff --git a/kernel/src/shell/mod.rs b/kernel/src/shell/mod.rs index edfa6d03..48d5cc61 100644 --- a/kernel/src/shell/mod.rs +++ b/kernel/src/shell/mod.rs @@ -14,16 +14,14 @@ //! console_init(); //! ``` -#![no_std] - mod completion; mod parser; -mod shell; +mod shell_impl; mod commands; // Re-export shell types and functions -pub use shell::{Shell, console_init, console_init_non_blocking}; +pub use shell_impl::{Shell, console_init, console_init_non_blocking}; // Re-export parser types for external use pub use parser::{ diff --git a/kernel/src/shell/parser/history.rs b/kernel/src/shell/parser/history.rs index db3d76bc..63173ce1 100644 --- a/kernel/src/shell/parser/history.rs +++ b/kernel/src/shell/parser/history.rs @@ -46,7 +46,7 @@ impl CommandHistory { /// Get the next command in history #[allow(dead_code)] - pub fn next(&mut self) -> Option<&String> { + pub fn next_command(&mut self) -> Option<&String> { if self.current_index < self.history.len() { self.current_index += 1; if self.current_index < self.history.len() { diff --git a/kernel/src/shell/parser/mod.rs b/kernel/src/shell/parser/mod.rs index a0cb47c0..ae809c38 100644 --- a/kernel/src/shell/parser/mod.rs +++ b/kernel/src/shell/parser/mod.rs @@ -4,8 +4,8 @@ mod history; mod node; -mod parser; +mod parser_impl; pub use history::{CommandHistory, clear_line_and_redraw}; pub use node::{CommandNode, FlagDef, OptionDef, ParseError, ParsedCommand}; -pub use parser::CommandParser; +pub use parser_impl::CommandParser; diff --git a/kernel/src/shell/parser/parser.rs b/kernel/src/shell/parser/parser_impl.rs similarity index 100% rename from kernel/src/shell/parser/parser.rs rename to kernel/src/shell/parser/parser_impl.rs diff --git a/kernel/src/shell/shell.rs b/kernel/src/shell/shell_impl.rs similarity index 91% rename from kernel/src/shell/shell.rs rename to kernel/src/shell/shell_impl.rs index 78112e7f..443fca8d 100644 --- a/kernel/src/shell/shell.rs +++ b/kernel/src/shell/shell_impl.rs @@ -164,17 +164,15 @@ impl Shell { let display_prefix: &str = &result.prefix; for (i, match_name) in result.matches.iter().enumerate() { let match_name: &String = match_name; - let display_name: &str = if match_name.starts_with(display_prefix) { - &match_name[display_prefix.len()..] - } else { - match_name.as_str() - }; + let display_name: &str = match_name + .strip_prefix(display_prefix) + .unwrap_or(match_name.as_str()); print!("{} ", display_name); // Add explicit spacing if (i + 1) % 3 == 0 { println!(); } } - if result.matches.len() % 3 != 0 { + if !result.matches.len().is_multiple_of(3) { println!(); } print_prompt(); @@ -185,19 +183,23 @@ impl Shell { } else if is_unique && matches_count > 0 { // Single match - insert the full match let text_to_insert = &result.matches[0]; - let word_start = completion::find_word_start(current_content, self.cursor); + let word_start = + completion::find_word_start(current_content, self.cursor); let current_word = ¤t_content[word_start..self.cursor]; // Calculate what we need to add (match minus what's already typed) let to_add = if text_to_insert.starts_with(current_word) { - &text_to_insert[current_word.len()..] + text_to_insert + .strip_prefix(current_word) + .unwrap_or(text_to_insert) } else { text_to_insert }; if !to_add.is_empty() { let to_add_bytes = to_add.as_bytes(); - let insert_len = to_add_bytes.len().min(MAX_LINE_LEN - self.line_len - 1); + let insert_len = + to_add_bytes.len().min(MAX_LINE_LEN - self.line_len - 1); if insert_len > 0 { // Move existing characters to make space @@ -214,12 +216,19 @@ impl Shell { // Redraw let new_content = - std::str::from_utf8(&self.buf[..self.line_len]).unwrap_or(""); + std::str::from_utf8(&self.buf[..self.line_len]) + .unwrap_or(""); #[cfg(feature = "fs")] - let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); + let prompt = + format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); #[cfg(not(feature = "fs"))] let prompt = "axvisor:$ ".to_string(); - clear_line_and_redraw(&mut self.stdout, &prompt, new_content, self.cursor); + clear_line_and_redraw( + &mut self.stdout, + &prompt, + new_content, + self.cursor, + ); } } } @@ -292,7 +301,7 @@ impl Shell { } b'B' => { // DOWN arrow - next command - match self.history.next() { + match self.history.next_command() { Some(next_cmd) => { let next_cmd: &String = next_cmd; // clear current buffer diff --git a/modules/axvm b/modules/axvm index fb0b01ad..c826ffdb 160000 --- a/modules/axvm +++ b/modules/axvm @@ -1 +1 @@ -Subproject commit fb0b01ad150e7ca83b9e9852d542b4b5b9cceea2 +Subproject commit c826ffdbc68c7fee91c22b0397d5d94fa185b494 From 0b314c8a8993adbc080e74733bf0b4d0b9106844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 29 Jan 2026 17:35:51 +0800 Subject: [PATCH 14/19] =?UTF-8?q?fix:=20=E7=A7=BB=E9=99=A4=E4=B8=8D?= =?UTF-8?q?=E5=BF=85=E8=A6=81=E7=9A=84=E4=BE=9D=E8=B5=96=E5=B9=B6=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=AD=90=E6=A8=A1=E5=9D=97=E5=BC=95=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 1 - modules/arm_vcpu | 2 +- modules/axvm | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4968a96..637d75ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -243,7 +243,6 @@ dependencies = [ "axvm-types", "log", "numeric-enum-macro", - "percpu", "spin 0.10.0", ] diff --git a/modules/arm_vcpu b/modules/arm_vcpu index 84042ae5..5b7e40db 160000 --- a/modules/arm_vcpu +++ b/modules/arm_vcpu @@ -1 +1 @@ -Subproject commit 84042ae5fdbfe6d4c9ea4076d8caf5d48ca55d19 +Subproject commit 5b7e40dbf67d760e202114ba9431e81c2b45f9e7 diff --git a/modules/axvm b/modules/axvm index c826ffdb..afd3ff04 160000 --- a/modules/axvm +++ b/modules/axvm @@ -1 +1 @@ -Subproject commit c826ffdbc68c7fee91c22b0397d5d94fa185b494 +Subproject commit afd3ff04aff59dba6a58020c2baa69353c815457 From 2747b18c81e14eb15d1150a55b70b71948ab9b84 Mon Sep 17 00:00:00 2001 From: szy <673586548@qq.com> Date: Fri, 30 Jan 2026 13:43:28 +0800 Subject: [PATCH 15/19] feat: use buddy-slab-allocator (#356) * use new buddy-slab-allocator * delete unused dependence * update lock and dma32 page * lock allocator in axalloc --------- Co-authored-by: szy --- Cargo.lock | 560 ++++++++++-------- Cargo.toml | 5 +- configs/vms/arceos-aarch64-e2000-smp2.toml | 6 +- configs/vms/arceos-aarch64-qemu-smp1.toml | 6 +- configs/vms/arceos-aarch64-rk3568-smp2.toml | 6 +- configs/vms/arceos-aarch64-tac_e400-smp1.toml | 6 +- configs/vms/linux-aarch64-e2000-smp2.toml | 6 +- modules/arm_vcpu | 2 +- modules/axalloc/Cargo.toml | 27 +- modules/axalloc/src/lib.rs | 365 ++++++++---- modules/axalloc/src/page.rs | 49 +- modules/axalloc/src/tracking.rs | 88 +++ modules/axruntime/src/lib.rs | 4 +- modules/axvm | 2 +- 14 files changed, 710 insertions(+), 422 deletions(-) create mode 100644 modules/axalloc/src/tracking.rs diff --git a/Cargo.lock b/Cargo.lock index 637d75ab..290c3163 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,7 +57,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "once_cell", "version_check", ] @@ -72,7 +72,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "version_check", - "zerocopy 0.8.31", + "zerocopy 0.8.33", ] [[package]] @@ -187,7 +187,7 @@ name = "arceos_api" version = "0.2.0" source = "git+https://github.com/arceos-org/arceos.git?tag=dev-251216#16096568f5ae6ad2d687eff2927a4cb69cef8133" dependencies = [ - "axalloc 0.2.0 (git+https://github.com/arceos-org/arceos.git?tag=dev-251216)", + "axalloc 0.3.0", "axconfig", "axdriver", "axerrno 0.1.2", @@ -203,9 +203,9 @@ dependencies = [ [[package]] name = "arm-gic-driver" -version = "0.15.9" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5487b0a69ebddf2f8affd1e0d32a875fc6213b3a15e3315f9d7beb34b174d457" +checksum = "ad71090ed958939b87f6c99e0acd3c476b3a0dbd54f2e2d9aa1ca1f40dc4417e" dependencies = [ "aarch64-cpu 10.0.0", "bitflags 2.10.0", @@ -289,7 +289,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -329,24 +329,29 @@ dependencies = [ [[package]] name = "axalloc" version = "0.2.0" +source = "git+https://github.com/arceos-org/arceos.git?tag=dev-251216#16096568f5ae6ad2d687eff2927a4cb69cef8133" dependencies = [ + "allocator", "axerrno 0.1.2", - "buddy_system_allocator", + "cfg-if", "kspin", "log", + "memory_addr", + "strum 0.27.2", ] [[package]] name = "axalloc" -version = "0.2.0" -source = "git+https://github.com/arceos-org/arceos.git?tag=dev-251216#16096568f5ae6ad2d687eff2927a4cb69cef8133" +version = "0.3.0" dependencies = [ - "allocator", - "axerrno 0.1.2", - "cfg-if", + "axbacktrace", + "axerrno 0.2.2", + "buddy-slab-allocator", + "kernel_guard", "kspin", "log", "memory_addr", + "percpu", "strum 0.27.2", ] @@ -370,9 +375,9 @@ dependencies = [ [[package]] name = "axconfig-gen" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf14099a96dbb925e39a44c4f25555f1e24516194452a84a943aa7eda62383d" +checksum = "e8021a26bbd0b7e0760e28ded5dba2082fda8224c7cfd457ab370ff851626452" dependencies = [ "clap", "toml_edit 0.22.27", @@ -380,14 +385,14 @@ dependencies = [ [[package]] name = "axconfig-macros" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86a6711b80fbd1dc4c1d8e2fb77a5de6fc8e2eb2b96bbd0420ca8af370eb74c" +checksum = "4b2decc5437a10ddb659f0fbd819b9308362ea1d11455ddb5a1c47ea3973920d" dependencies = [ "axconfig-gen", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -458,7 +463,7 @@ version = "0.2.0" source = "git+https://github.com/arceos-org/arceos.git?tag=dev-251216#16096568f5ae6ad2d687eff2927a4cb69cef8133" dependencies = [ "arm-gic-driver", - "axalloc 0.2.0 (git+https://github.com/arceos-org/arceos.git?tag=dev-251216)", + "axalloc 0.3.0", "axconfig", "axdriver_base 0.1.2 (git+https://github.com/arceos-org/axdriver_crates.git?tag=dev-v01)", "axdriver_block 0.1.2 (git+https://github.com/arceos-org/axdriver_crates.git?tag=dev-v01)", @@ -572,7 +577,7 @@ name = "axfeat" version = "0.2.0" source = "git+https://github.com/arceos-org/arceos.git?tag=dev-251216#16096568f5ae6ad2d687eff2927a4cb69cef8133" dependencies = [ - "axalloc 0.2.0 (git+https://github.com/arceos-org/arceos.git?tag=dev-251216)", + "axalloc 0.2.0", "axbacktrace", "axdriver", "axfs", @@ -629,7 +634,7 @@ name = "axhal" version = "0.2.0" source = "git+https://github.com/arceos-org/arceos.git?tag=dev-251216#16096568f5ae6ad2d687eff2927a4cb69cef8133" dependencies = [ - "axalloc 0.2.0 (git+https://github.com/arceos-org/arceos.git?tag=dev-251216)", + "axalloc 0.3.0", "axconfig", "axcpu", "axplat", @@ -667,7 +672,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -719,7 +724,7 @@ name = "axmm" version = "0.2.0" source = "git+https://github.com/arceos-org/arceos.git?tag=dev-251216#16096568f5ae6ad2d687eff2927a4cb69cef8133" dependencies = [ - "axalloc 0.2.0 (git+https://github.com/arceos-org/arceos.git?tag=dev-251216)", + "axalloc 0.3.0", "axconfig", "axerrno 0.1.2", "axhal", @@ -787,7 +792,7 @@ dependencies = [ "serde", "somehal", "spin 0.10.0", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", ] [[package]] @@ -845,7 +850,7 @@ source = "git+https://github.com/arceos-org/axplat_crates.git?tag=dev-v03#0df071 dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -926,7 +931,7 @@ dependencies = [ name = "axruntime" version = "0.1.0" dependencies = [ - "axalloc 0.2.0 (git+https://github.com/arceos-org/arceos.git?tag=dev-251216)", + "axalloc 0.3.0", "axconfig", "axdisplay", "axdriver", @@ -1044,9 +1049,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", @@ -1098,9 +1103,9 @@ dependencies = [ "prettyplease", "quote", "spin 0.9.8", - "syn 2.0.111", + "syn 2.0.114", "timer_list", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", ] [[package]] @@ -1120,7 +1125,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1153,7 +1158,7 @@ dependencies = [ "ranges-ext", "raw-cpuid 11.6.0", "spin 0.10.0", - "thiserror 2.0.17", + "thiserror 2.0.18", "timer_list", "vm-allocator", "x86_vcpu", @@ -1179,7 +1184,7 @@ dependencies = [ "schemars", "serde", "serde_repr", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", ] [[package]] @@ -1205,7 +1210,7 @@ checksum = "e585a01076fee271c5aabcf36212acb349fb3e638561d842fffa8ca013f4fdd8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1320,16 +1325,16 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] -name = "buddy_system_allocator" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a0108968a3a2dab95b089c0fc3f1afa7759aa5ebe6f1d86d206d6f7ba726eb" +name = "buddy-slab-allocator" +version = "0.1.0" +source = "git+https://github.com/arceos-hypervisor/buddy-slab-allocator.git?branch=main#60fc7e076b4d623828e8a9b83e1649ee4c379469" dependencies = [ - "spin 0.9.8", + "cfg-if", + "log", ] [[package]] @@ -1455,7 +1460,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1469,7 +1474,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1489,9 +1494,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.50" +version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" dependencies = [ "find-msvc-tools", "shlex", @@ -1511,9 +1516,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "js-sys", @@ -1524,9 +1529,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -1534,9 +1539,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -1553,14 +1558,14 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "colorchoice" @@ -1570,11 +1575,11 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" -version = "3.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1701,7 +1706,7 @@ checksum = "70272a03a2cef15589bac05d3d15c023752f5f8f2da8be977d983a9d9e6250fb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1775,6 +1780,7 @@ dependencies = [ "crossterm_winapi", "derive_more", "document-features", + "futures-core", "mio", "parking_lot", "rustix 1.1.3", @@ -1819,7 +1825,7 @@ checksum = "9a49d5cd78b1c748184d41407b14a58af8403c13328ff2b9f49b0a418c24e3ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1893,6 +1899,16 @@ dependencies = [ "darling_macro 0.21.3", ] +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + [[package]] name = "darling_core" version = "0.20.11" @@ -1904,7 +1920,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1917,7 +1933,20 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", ] [[package]] @@ -1928,7 +1957,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1939,7 +1968,18 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.111", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.114", ] [[package]] @@ -1971,7 +2011,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1980,7 +2020,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2039,7 +2079,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2066,7 +2106,7 @@ dependencies = [ "aarch64-cpu-ext", "cfg-if", "spin 0.10.0", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2155,7 +2195,7 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2167,7 +2207,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2188,7 +2228,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2199,7 +2239,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2220,7 +2260,7 @@ dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2291,7 +2331,7 @@ checksum = "ba8f5038f5845165d06fe1453fe4130ad546d3314818bbda57e208e7b0cffe08" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2334,26 +2374,25 @@ checksum = "4e7b19f67663e8368d5a07165a1c348b5a761afe5d130e982a0ed8859aca37c2" dependencies = [ "heapless 0.9.2", "log", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "filetime" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.60.2", ] [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "fitimage" @@ -2370,15 +2409,15 @@ dependencies = [ "md5", "serde", "sha1", - "thiserror 2.0.17", + "thiserror 2.0.18", "vm-fdt", ] [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" dependencies = [ "crc32fast", "libz-sys", @@ -2483,7 +2522,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2528,9 +2567,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -2580,9 +2619,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -2777,7 +2816,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.12", + "h2 0.4.13", "http 1.4.0", "http-body 1.0.1", "httparse", @@ -2996,9 +3035,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -3028,15 +3067,15 @@ dependencies = [ [[package]] name = "instability" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6778b0196eefee7df739db78758e5cf9b37412268bfa5650bfeed028aed20d9c" +checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d" dependencies = [ - "darling 0.20.11", + "darling 0.23.0", "indoc", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3063,9 +3102,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", @@ -3088,15 +3127,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" dependencies = [ "jiff-static", "log", @@ -3107,13 +3146,13 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3132,18 +3171,18 @@ dependencies = [ "schemars", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", "tower", "tower-http", ] [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -3158,7 +3197,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3170,7 +3209,7 @@ dependencies = [ "bitflags 2.10.0", "prettyplease", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3239,19 +3278,19 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.178" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libredox" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall 0.6.0", + "redox_syscall 0.7.0", ] [[package]] @@ -3308,7 +3347,7 @@ checksum = "e5cec0ec4228b4853bb129c84dbf093a27e6c7a20526da046defc334a1b017f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3512,13 +3551,13 @@ checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" [[package]] name = "network-interface" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e79101e6efcffacab279462884a7eebf65ea5f4ac2cc727b60c715a9aa04722" +checksum = "4ddcb8865ad3d9950f22f42ffa0ef0aecbfbf191867b3122413602b0a360b2a6" dependencies = [ "cc", "libc", - "thiserror 2.0.17", + "thiserror 2.0.18", "winapi", ] @@ -3671,7 +3710,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3703,9 +3742,9 @@ dependencies = [ [[package]] name = "ostool" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6606e47adb53299e44254202d83481ce0bf6624eb8c3f8fca6ee4241c37e7a9" +checksum = "b5243f6531ef2442252a2774f688f9f87f979bbc39db478cda4d27f8efaadb97" dependencies = [ "anyhow", "byte-unit", @@ -3716,6 +3755,7 @@ dependencies = [ "cursive", "env_logger", "fitimage", + "futures", "indicatif", "jkconfig", "log", @@ -3733,7 +3773,7 @@ dependencies = [ "tar", "tftpd", "tokio", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", "uboot-shell", "ureq", ] @@ -3747,7 +3787,7 @@ dependencies = [ "bitflags 2.10.0", "log", "num-align", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -3826,7 +3866,7 @@ dependencies = [ "log", "pci_types", "rdif-pcie", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -3855,7 +3895,7 @@ checksum = "8a9f4cc54a2e471ff72f1499461ba381ad4eae9cbd60d29c258545b995e406e0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3904,8 +3944,8 @@ dependencies = [ "prettyplease", "quote", "spin 0.10.0", - "syn 2.0.111", - "thiserror 2.0.17", + "syn 2.0.114", + "thiserror 2.0.18", ] [[package]] @@ -3917,7 +3957,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3940,9 +3980,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "portable-atomic-util" @@ -3974,7 +4014,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.8.31", + "zerocopy 0.8.33", ] [[package]] @@ -3984,7 +4024,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4015,14 +4055,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -4061,7 +4101,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2 0.6.1", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -4082,7 +4122,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -4141,7 +4181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -4161,7 +4201,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -4170,14 +4210,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -4194,7 +4234,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e350c92fb797d3d5f1ce45686618a922130cdd617c76a5a706f504e8b3d5904" dependencies = [ "heapless 0.9.2", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -4246,7 +4286,7 @@ dependencies = [ "async-trait", "paste", "rdif-def", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -4260,7 +4300,7 @@ dependencies = [ "futures", "rdif-base", "spin_on", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -4278,7 +4318,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c238eb44d86fabc99028adc973f896ce2202aeb6184cc8b89863f2d157d7ca06" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -4289,7 +4329,7 @@ checksum = "170ec813e6cf4d1e5fa53fa8fed0fadc7aaab96683d4f1d44c602a6109931eb4" dependencies = [ "cfg-if", "rdif-base", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -4300,7 +4340,7 @@ checksum = "60c6e8dea6d432b2c03bc3f4238dc59a276aacac6f688a937351e7a313918738" dependencies = [ "pci_types", "rdif-base", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -4317,7 +4357,7 @@ dependencies = [ "rdif-pcie", "rdrive-macros", "spin 0.10.0", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -4328,7 +4368,7 @@ checksum = "eab3105c9af32e901a2adc7d920b39ff8b6ee0f6f0b7dfdeaf18f306ec12606f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4342,9 +4382,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" dependencies = [ "bitflags 2.10.0", ] @@ -4366,7 +4406,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4475,7 +4515,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.4.12", + "h2 0.4.13", "http 1.4.0", "http-body 1.0.1", "http-body-util", @@ -4517,7 +4557,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -4544,7 +4584,7 @@ checksum = "e8c4aa1ea1af6dcc83a61be12e8189f9b293c3ba5a487778a4cd89fb060fdbbc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4586,9 +4626,9 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.45" +version = "0.7.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" dependencies = [ "bitvec", "bytecheck", @@ -4604,9 +4644,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.45" +version = "0.7.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" dependencies = [ "proc-macro2", "quote", @@ -4650,9 +4690,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.39.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35affe401787a9bd846712274d97654355d21b2a2c092a3139aabe31e9022282" +checksum = "61f703d19852dbf87cbc513643fa81428361eb6940f1ac14fd58155d295a3eb0" dependencies = [ "arrayvec", "borsh", @@ -4666,9 +4706,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" @@ -4713,9 +4753,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "log", "once_cell", @@ -4737,9 +4777,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "web-time", "zeroize", @@ -4747,9 +4787,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "ring", "rustls-pki-types", @@ -4773,9 +4813,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "sbi-rt" @@ -4803,9 +4843,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" dependencies = [ "dyn-clone", "ref-cast", @@ -4816,14 +4856,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301858a4023d78debd2353c7426dc486001bddc91ae31a76fb1f55132f7e2633" +checksum = "4908ad288c5035a8eb12cfdf0d49270def0a268ee162b75eeee0f85d155a7c45" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4939,7 +4979,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4950,20 +4990,20 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "serde_json" -version = "1.0.146" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "217ca874ae0207aac254aa02c957ded05585a90892cc8d87f9e5fa49669dadd8" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -4985,7 +5025,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5089,10 +5129,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -5126,7 +5167,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c73e0ca8c566478040487791c9f488f86c5aec846ca1ab18484be8a1d8c55cd" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -5188,7 +5229,7 @@ dependencies = [ "serde", "smccc", "spin 0.10.0", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", "url", ] @@ -5265,7 +5306,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5277,7 +5318,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5312,9 +5353,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -5344,7 +5385,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5408,9 +5449,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", "getrandom 0.3.4", @@ -5436,11 +5477,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -5451,25 +5492,25 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "time" -version = "0.3.44" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" dependencies = [ "deranged", "itoa", @@ -5477,22 +5518,22 @@ dependencies = [ "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" dependencies = [ "num-conv", "time-core", @@ -5549,9 +5590,9 @@ checksum = "8d2d250f87fb3fb6f225c907cf54381509f47b40b74b1d1f12d2dccbc915bdfe" [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -5572,7 +5613,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5597,9 +5638,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -5622,9 +5663,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.10+spec-1.1.0" +version = "0.9.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" dependencies = [ "indexmap", "serde_core", @@ -5702,9 +5743,9 @@ checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -5786,7 +5827,7 @@ dependencies = [ "lenient_semver", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5840,14 +5881,14 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4064ed685c487dbc25bd3f0e9548f2e34bab9d18cefc700f9ec2dba74ba1138e" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "unicase" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" @@ -5933,9 +5974,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -6004,10 +6045,10 @@ dependencies = [ [[package]] name = "vm-allocator" version = "0.1.3" -source = "git+https://github.com/rust-vmm/vm-allocator.git#080ea204ee2b8f4a25a25b6502b22d7e365aa948" +source = "git+https://github.com/rust-vmm/vm-allocator.git#f89a04511f68ba81a48832edcc2ebf4b8fb793b0" dependencies = [ "libc", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -6039,18 +6080,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -6061,11 +6102,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -6074,9 +6116,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6084,31 +6126,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -6126,9 +6168,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" dependencies = [ "rustls-pki-types", ] @@ -6176,7 +6218,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -6187,7 +6229,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -6477,9 +6519,9 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "writeable" @@ -6559,7 +6601,7 @@ dependencies = [ "paste", "raw-cpuid 11.6.0", "spin 0.9.8", - "thiserror 2.0.17", + "thiserror 2.0.18", "x86", "x86_64", "x86_vlapic", @@ -6615,7 +6657,7 @@ dependencies = [ "sha2", "tar", "tokio", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", ] [[package]] @@ -6637,7 +6679,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure", ] @@ -6653,11 +6695,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ - "zerocopy-derive 0.8.31", + "zerocopy-derive 0.8.33", ] [[package]] @@ -6668,18 +6710,18 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -6699,7 +6741,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure", ] @@ -6739,5 +6781,11 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] + +[[package]] +name = "zmij" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" diff --git a/Cargo.toml b/Cargo.toml index ac2f4e6b..821efc0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ axstd = {git = "https://github.com/arceos-org/arceos.git", tag = "dev-251216", f "smp", # "page-alloc-64g", ]} -axalloc = {git = "https://github.com/arceos-org/arceos.git", tag = "dev-251216"} +axalloc = {path = "modules/axalloc"} axconfig = {git = "https://github.com/arceos-org/arceos.git", tag = "dev-251216"} axdisplay = {git = "https://github.com/arceos-org/arceos.git", tag = "dev-251216"} axdriver = {git = "https://github.com/arceos-org/arceos.git", tag = "dev-251216"} @@ -83,6 +83,7 @@ page_table_entry = {version = "0.5", features = ["arm-el2"]} page_table_multiarch = "0.5" rdif-intc = "0.12" rdrive = "0.18" +axbacktrace = "0.1" # percpu "0.2.1" can not compile on aarch64 percpu = {version = "=0.2.0", features = ["arm-el2"]} @@ -114,7 +115,7 @@ x86_vlapic = {path = "modules/x86_vlapic"} path = "modules/axdevice" [patch."https://github.com/arceos-org/arceos"] -# axalloc = {path = "modules/axalloc"} axconfig = {path = "modules/axconfig"} axruntime = {path = "modules/axruntime"} axfs = {path = "modules/axfs"} +axalloc = {path = "modules/axalloc"} diff --git a/configs/vms/arceos-aarch64-e2000-smp2.toml b/configs/vms/arceos-aarch64-e2000-smp2.toml index 4f04ffa7..1663836e 100644 --- a/configs/vms/arceos-aarch64-e2000-smp2.toml +++ b/configs/vms/arceos-aarch64-e2000-smp2.toml @@ -16,16 +16,16 @@ phys_cpu_ids = [0x201, 0x100] # [kernel] # The entry point of the kernel image. -entry_point = 0x20_2008_0000 +#entry_point = 0x20_2008_0000 # The location of image: "memory" | "fs". # Load from file system. image_location = "fs" # The load address of the kernel image. -kernel_load_addr = 0x20_2008_0000 +#kernel_load_addr = 0x20_2008_0000 ## The file path of the kernel image. kernel_path = "/guest/arceos/phytiumpi" ## The file path of the device tree blob (DTB). -dtb_load_addr = 0x20_2000_0000 +#dtb_load_addr = 0x20_2000_0000 #dtb_path = "/path/to/axvisor/configs/vms/arceos-aarch64-e2000_smp2.dtb" # Memory regions with format (`base_paddr`, `size`, `flags`, `map_type`). # For `map_type`, 0 means `MAP_ALLOC`, 1 means `MAP_IDENTICAL`. diff --git a/configs/vms/arceos-aarch64-qemu-smp1.toml b/configs/vms/arceos-aarch64-qemu-smp1.toml index fdd3becc..473f6683 100644 --- a/configs/vms/arceos-aarch64-qemu-smp1.toml +++ b/configs/vms/arceos-aarch64-qemu-smp1.toml @@ -17,18 +17,18 @@ phys_cpu_ids = [0] # [kernel] # The entry point of the kernel image. -entry_point = 0x8020_0000 +#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/arceos-aarch64-dyn-smp1.bin" # The load address of the kernel image. -kernel_load_addr = 0x8020_0000 +#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 = 0x8000_0000 +#dtb_load_addr = 0x8000_0000 ## The file path of the ramdisk image. # ramdisk_path = "" diff --git a/configs/vms/arceos-aarch64-rk3568-smp2.toml b/configs/vms/arceos-aarch64-rk3568-smp2.toml index 5f2b9002..8fca554d 100644 --- a/configs/vms/arceos-aarch64-rk3568-smp2.toml +++ b/configs/vms/arceos-aarch64-rk3568-smp2.toml @@ -17,16 +17,16 @@ phys_cpu_ids = [0x00, 0x100] # [kernel] # The entry point of the kernel image. -entry_point = 0x7008_0000 +#entry_point = 0x7008_0000 # The location of image: "memory" | "fs". # Load from memory. image_location = "fs" # The load address of the kernel image. -kernel_load_addr = 0x7008_0000 +#kernel_load_addr = 0x7008_0000 ## The file path of the kernel image. kernel_path = "/userdata/rootfs_overlay/guest/arceos/roc-rk3568-pc" ## The file path of the device tree blob (DTB). -dtb_load_addr = 0x7000_0000 +#dtb_load_addr = 0x7000_0000 #dtb_path = "/path/arceos-aarch64-rk3568_smp2.dtb" # Memory regions with format (`base_paddr`, `size`, `flags`, `map_type`). # For `map_type`, 0 means `MAP_ALLOC`, 1 means `MAP_IDENTICAL`. diff --git a/configs/vms/arceos-aarch64-tac_e400-smp1.toml b/configs/vms/arceos-aarch64-tac_e400-smp1.toml index 159ea607..60e8d0ae 100644 --- a/configs/vms/arceos-aarch64-tac_e400-smp1.toml +++ b/configs/vms/arceos-aarch64-tac_e400-smp1.toml @@ -17,16 +17,16 @@ phys_cpu_ids = [0x200] # [kernel] # The entry point of the kernel image. -entry_point = 0x20_2008_0000 +#entry_point = 0x20_2008_0000 # The location of image: "memory" | "fs". # Load from file system. image_location = "memory" # The load address of the kernel image. -kernel_load_addr = 0x20_2008_0000 +#kernel_load_addr = 0x20_2008_0000 ## The file path of the kernel image. kernel_path = "/path/to/arceos_aarch64-dyn_smp1.bin" ## The file path of the device tree blob (DTB). -dtb_load_addr = 0x20_2000_0000 +#dtb_load_addr = 0x20_2000_0000 #dtb_path = "/path/to/arceos-aarch64-tac_e400-smp1.dtb" # Memory regions with format (`base_paddr`, `size`, `flags`, `map_type`). # For `map_type`, 0 means `MAP_ALLOC`, 1 means `MAP_IDENTICAL`. diff --git a/configs/vms/linux-aarch64-e2000-smp2.toml b/configs/vms/linux-aarch64-e2000-smp2.toml index 84d2ab00..8b07c639 100644 --- a/configs/vms/linux-aarch64-e2000-smp2.toml +++ b/configs/vms/linux-aarch64-e2000-smp2.toml @@ -17,16 +17,16 @@ phys_cpu_ids = [0x200, 0x00] # [kernel] # The entry point of the kernel image. -entry_point = 0x20_4008_0000 +#entry_point = 0x20_4008_0000 # The location of image: "memory" | "fs". # Load from file system. image_location = "fs" # The load address of the kernel image. -kernel_load_addr = 0x20_4008_0000 +#kernel_load_addr = 0x20_4008_0000 ## The file path of the kernel image. kernel_path = "/guest/linux/phytiumpi" ## The file path of the device tree blob (DTB). -dtb_load_addr = 0x20_4000_0000 +#dtb_load_addr = 0x20_4000_0000 #dtb_path = "/path/to/axvisor/configs/vms/linux-aarch64-e2000_smp2.dtb" # Memory regions with format (`base_paddr`, `size`, `flags`, `map_type`). # For `map_type`, 0 means `MAP_ALLOC`, 1 means `MAP_IDENTICAL`. diff --git a/modules/arm_vcpu b/modules/arm_vcpu index 5b7e40db..84042ae5 160000 --- a/modules/arm_vcpu +++ b/modules/arm_vcpu @@ -1 +1 @@ -Subproject commit 5b7e40dbf67d760e202114ba9431e81c2b45f9e7 +Subproject commit 84042ae5fdbfe6d4c9ea4076d8caf5d48ca55d19 diff --git a/modules/axalloc/Cargo.toml b/modules/axalloc/Cargo.toml index 7fae668d..28d5990a 100644 --- a/modules/axalloc/Cargo.toml +++ b/modules/axalloc/Cargo.toml @@ -1,22 +1,23 @@ [package] name = "axalloc" -authors.workspace = true +version.workspace = true edition.workspace = true +authors = ["Yuekai Jia "] +description = "Axvisor global memory allocator" license.workspace = true -version = "0.2.0" +repository = "https://github.com/arceos-org/arceos/tree/main/modules/axalloc" [features] -buddy = [] default = [] -level-1 = [] -page-alloc-4g = [] # Support up to 4G memory capacity -page-alloc-64g = [] # Support up to 64G memory capacity -slab = [] -tlsf = [] - +tracking = ["dep:percpu", "dep:axbacktrace", "buddy-slab-allocator/tracking"] [dependencies] -buddy_system_allocator = "0.11" -log = "0.4" -kspin = "0.1" -axerrno = "0.1" \ No newline at end of file +buddy-slab-allocator = {git = "https://github.com/arceos-hypervisor/buddy-slab-allocator.git", branch = "main", features = ["log"] } +axbacktrace = { workspace = true, optional = true } +axerrno.workspace = true +kspin.workspace = true +kernel_guard.workspace = true +log.workspace = true +memory_addr.workspace = true +percpu = { workspace = true, optional = true } +strum = { version = "0.27.2", default-features = false, features = ["derive"] } \ No newline at end of file diff --git a/modules/axalloc/src/lib.rs b/modules/axalloc/src/lib.rs index a40f565c..98382196 100644 --- a/modules/axalloc/src/lib.rs +++ b/modules/axalloc/src/lib.rs @@ -1,11 +1,4 @@ -#![no_std] - -//! [ArceOS](https://github.com/arceos-org/arceos) global memory allocator. -//! -//! It provides [`GlobalAllocator`], which implements the trait -//! [`core::alloc::GlobalAlloc`]. A static global variable of type -//! [`GlobalAllocator`] is defined with the `#[global_allocator]` attribute, to -//! be registered as the standard library’s default allocator. +//! The Axvisor memory allocator. #![no_std] @@ -13,31 +6,82 @@ extern crate log; extern crate alloc; -// mod page; +use core::{ + alloc::{GlobalAlloc, Layout}, + fmt, + ptr::NonNull, +}; -use buddy_system_allocator::Heap; -use core::alloc::{GlobalAlloc, Layout}; -use core::ptr::NonNull; +use buddy_slab_allocator::{AllocResult, PageAllocator}; use kspin::SpinNoIrq; +use strum::{IntoStaticStr, VariantArray}; + +pub use buddy_slab_allocator::AddrTranslator; +// Page size can be configured from here const PAGE_SIZE: usize = 0x1000; -const MIN_HEAP_SIZE: usize = 0x8000; // 32 K -// pub use page::GlobalPage; +mod page; +pub use page::GlobalPage; + +#[cfg(feature = "tracking")] +mod tracking; +#[cfg(feature = "tracking")] +pub use tracking::*; + +/// Kinds of memory usage for tracking. +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, VariantArray, IntoStaticStr)] +pub enum UsageKind { + /// Heap allocations made by kernel Rust code. + RustHeap, + /// Virtual memory, usually used for user space. + VirtMem, + /// Page cache for file systems. + PageCache, + /// Page tables. + PageTable, + /// DMA memory. + Dma, + /// Memory used by [`GlobalPage`]. + Global, +} + +/// Statistics of memory usages. +#[derive(Clone, Copy)] +pub struct Usages([usize; UsageKind::VARIANTS.len()]); + +impl Usages { + const fn new() -> Self { + Self([0; UsageKind::VARIANTS.len()]) + } + + fn alloc(&mut self, kind: UsageKind, size: usize) { + self.0[kind as usize] += size; + } + + fn dealloc(&mut self, kind: UsageKind, size: usize) { + self.0[kind as usize] -= size; + } +} + +impl fmt::Debug for Usages { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut d = f.debug_struct("UsageStats"); + for &kind in UsageKind::VARIANTS { + d.field(kind.into(), &self.0[kind as usize]); + } + d.finish() + } +} /// The global allocator used by ArceOS. /// -/// It combines a [`ByteAllocator`] and a [`PageAllocator`] into a simple -/// two-level allocator: firstly tries allocate from the byte allocator, if -/// there is no memory, asks the page allocator for more memory and adds it to -/// the byte allocator. -/// -/// Currently, [`TlsfByteAllocator`] is used as the byte allocator, while -/// [`BitmapPageAllocator`] is used as the page allocator. -/// -/// [`TlsfByteAllocator`]: allocator::TlsfByteAllocator +/// This is an adapter around the allocator::GlobalAllocator that provides +/// compatibility with the original axalloc API. pub struct GlobalAllocator { - palloc: SpinNoIrq>, + inner: SpinNoIrq>, + usages: SpinNoIrq, } impl Default for GlobalAllocator { @@ -50,138 +94,230 @@ impl GlobalAllocator { /// Creates an empty [`GlobalAllocator`]. pub const fn new() -> Self { Self { - palloc: SpinNoIrq::new(Heap::empty()), + inner: SpinNoIrq::new(buddy_slab_allocator::GlobalAllocator::::new()), + usages: SpinNoIrq::new(Usages::new()), } } + /// Configure the address translator used by the underlying allocator so + /// that it can reason about physical address ranges (e.g. low-memory + /// regions below 4GiB). + pub fn set_addr_translator( + &self, + translator: &'static dyn buddy_slab_allocator::AddrTranslator, + ) { + self.inner.lock().set_addr_translator(translator); + } + /// Returns the name of the allocator. pub const fn name(&self) -> &'static str { - "buddy" + "buddy-slab-allocator" } /// Initializes the allocator with the given region. - /// - /// It firstly adds the whole region to the page allocator, then allocates - /// a small region (32 KB) to initialize the byte allocator. Therefore, - /// the given region must be larger than 32 KB. pub fn init(&self, start_vaddr: usize, size: usize) { - assert!(size > MIN_HEAP_SIZE); - debug!( - "initialize global allocator at: [{:#x}, {:#x})", - start_vaddr, - start_vaddr + size + info!( + "Initialize global memory allocator, start_vaddr: {}, size: {}", + start_vaddr, size ); - unsafe { self.palloc.lock().init(start_vaddr, size) }; + if let Err(e) = self.inner.lock().init(start_vaddr, size) { + panic!("Failed to initialize allocator: {:?}", e); + } } /// Add the given region to the allocator. - /// - /// It will add the whole region to the byte allocator. - pub fn add_memory(&self, start_vaddr: usize, size: usize) { - unsafe { self.palloc.lock().add_to_heap(start_vaddr, size) } + pub fn add_memory(&self, start_vaddr: usize, size: usize) -> AllocResult { + info!( + "Add memory region, start_vaddr: {}, size: {}", + start_vaddr, size + ); + self.inner.lock().add_memory(start_vaddr, size) } /// Allocate arbitrary number of bytes. Returns the left bound of the /// allocated region. - /// - /// It firstly tries to allocate from the byte allocator. If there is no - /// memory, it asks the page allocator for more memory and adds it to the - /// byte allocator. - pub fn alloc(&self, layout: Layout) -> Result, ()> { - // single-level allocator: only use the byte allocator. - let mut balloc = self.palloc.lock(); - balloc.alloc(layout) + pub fn alloc(&self, layout: Layout) -> AllocResult> { + let result = self.inner.lock().alloc(layout); + if let Ok(_ptr) = result { + self.usages.lock().alloc(UsageKind::RustHeap, layout.size()); + } + result } /// Gives back the allocated region to the byte allocator. - /// - /// The region should be allocated by [`alloc`], and `align_pow2` should be - /// the same as the one used in [`alloc`]. Otherwise, the behavior is - /// undefined. - /// - /// [`alloc`]: GlobalAllocator::alloc pub fn dealloc(&self, pos: NonNull, layout: Layout) { - self.palloc.lock().dealloc(pos, layout) + self.usages + .lock() + .dealloc(UsageKind::RustHeap, layout.size()); + self.inner.lock().dealloc(pos, layout); } /// Allocates contiguous pages. - /// - /// It allocates `num_pages` pages from the page allocator. - /// - /// `align_pow2` must be a power of 2, and the returned region bound will be - /// aligned to it. - pub fn alloc_pages(&self, num_pages: usize, align_pow2: usize) -> Result { - // single-level allocator: allocate from the byte allocator. - let mut balloc = self.palloc.lock(); - let layout = Layout::from_size_align(num_pages * PAGE_SIZE, align_pow2).unwrap(); - let ptr = balloc.alloc(layout)?; - Ok(ptr.as_ptr() as usize) + pub fn alloc_pages( + &self, + num_pages: usize, + alignment: usize, + kind: UsageKind, + ) -> AllocResult { + let result = self.inner.lock().alloc_pages(num_pages, alignment); + if let Ok(_addr) = result { + let size = num_pages * PAGE_SIZE; + self.usages.lock().alloc(kind, size); + } + result + } + + /// Allocates contiguous low-memory pages (physical address < 4GiB). + pub fn alloc_dma32_pages( + &self, + num_pages: usize, + alignment: usize, + kind: UsageKind, + ) -> AllocResult { + let result = self.inner.lock().alloc_dma32_pages(num_pages, alignment); + if let Ok(_addr) = result { + let size = num_pages * PAGE_SIZE; + self.usages.lock().alloc(kind, size); + } + result } /// Allocates contiguous pages starting from the given address. - /// - /// It allocates `num_pages` pages from the page allocator starting from the - /// given address. - /// - /// `align_pow2` must be a power of 2, and the returned region bound will be - /// aligned to it. pub fn alloc_pages_at( &self, start: usize, num_pages: usize, - align_pow2: usize, - ) -> Result { - unimplemented!("level-1 allocator does not support alloc_pages_at") + alignment: usize, + kind: UsageKind, + ) -> AllocResult { + let result = self + .inner + .lock() + .alloc_pages_at(start, num_pages, alignment); + if let Ok(_addr) = result { + let size = num_pages * PAGE_SIZE; + self.usages.lock().alloc(kind, size); + } + result } /// Gives back the allocated pages starts from `pos` to the page allocator. - /// - /// The pages should be allocated by [`alloc_pages`], and `align_pow2` - /// should be the same as the one used in [`alloc_pages`]. Otherwise, the - /// behavior is undefined. - /// - /// [`alloc_pages`]: GlobalAllocator::alloc_pages - pub fn dealloc_pages(&self, pos: usize, num_pages: usize) { - // single-level allocator: deallocate to the byte allocator. - let mut balloc = self.palloc.lock(); - let layout = Layout::from_size_align(num_pages * PAGE_SIZE, PAGE_SIZE).unwrap(); - let ptr = NonNull::new(pos as *mut u8).unwrap(); - balloc.dealloc(ptr, layout); + pub fn dealloc_pages(&self, pos: usize, num_pages: usize, kind: UsageKind) { + let size = num_pages * PAGE_SIZE; + self.usages.lock().dealloc(kind, size); + self.inner.lock().dealloc_pages(pos, num_pages); + } + + /// Returns the number of allocated bytes in the byte allocator. + #[cfg(feature = "tracking")] + pub fn used_bytes(&self) -> usize { + let stats = self.inner.lock().get_stats(); + stats.heap_bytes + stats.slab_bytes + } + + /// Returns the number of available bytes in the byte allocator. + #[cfg(feature = "tracking")] + pub fn available_bytes(&self) -> usize { + // The new allocator doesn't have this exact method, so we approximate + let stats = self.inner.lock().get_stats(); + stats.free_pages * PAGE_SIZE + } + + /// Returns the number of allocated pages in the page allocator. + #[cfg(feature = "tracking")] + pub fn used_pages(&self) -> usize { + let stats = self.inner.lock().get_stats(); + stats.used_pages + } + + /// Returns the number of available pages in the page allocator. + #[cfg(feature = "tracking")] + pub fn available_pages(&self) -> usize { + let stats = self.inner.lock().get_stats(); + stats.free_pages } /// Returns the number of allocated bytes in the byte allocator. + #[cfg(not(feature = "tracking"))] pub fn used_bytes(&self) -> usize { - self.palloc.lock().stats_alloc_actual() + 0 } /// Returns the number of available bytes in the byte allocator. + #[cfg(not(feature = "tracking"))] pub fn available_bytes(&self) -> usize { - let g = self.palloc.lock(); - g.stats_total_bytes() - g.stats_alloc_actual() + 0 } /// Returns the number of allocated pages in the page allocator. + #[cfg(not(feature = "tracking"))] pub fn used_pages(&self) -> usize { - self.used_bytes().div_ceil(PAGE_SIZE) + 0 } /// Returns the number of available pages in the page allocator. + #[cfg(not(feature = "tracking"))] pub fn available_pages(&self) -> usize { - self.available_bytes().div_ceil(PAGE_SIZE) + 0 + } + + /// Returns the usage statistics of the allocator. + pub fn usages(&self) -> Usages { + *self.usages.lock() } } unsafe impl GlobalAlloc for GlobalAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - if let Ok(ptr) = GlobalAllocator::alloc(self, layout) { - ptr.as_ptr() - } else { - alloc::alloc::handle_alloc_error(layout) + let inner = move || { + if let Ok(ptr) = GlobalAllocator::alloc(self, layout) { + ptr.as_ptr() + } else { + alloc::alloc::handle_alloc_error(layout) + } + }; + + #[cfg(feature = "tracking")] + { + tracking::with_state(|state| match state { + None => inner(), + Some(state) => { + let ptr = inner(); + let generation = state.generation; + state.generation += 1; + state.map.insert( + ptr as usize, + tracking::AllocationInfo { + layout, + backtrace: axbacktrace::Backtrace::capture(), + generation, + }, + ); + ptr + } + }) } + + #[cfg(not(feature = "tracking"))] + inner() } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - GlobalAllocator::dealloc(self, NonNull::new(ptr).expect("dealloc null ptr"), layout) + let ptr = NonNull::new(ptr).expect("dealloc null ptr"); + let inner = || GlobalAllocator::dealloc(self, ptr, layout); + + #[cfg(feature = "tracking")] + tracking::with_state(|state| match state { + None => inner(), + Some(state) => { + let address = ptr.as_ptr() as usize; + state.map.remove(&address); + inner() + } + }); + + #[cfg(not(feature = "tracking"))] + inner(); } } @@ -202,26 +338,43 @@ pub fn global_allocator() -> &'static GlobalAllocator { /// /// This function should be called only once, and before any allocation. pub fn global_init(start_vaddr: usize, size: usize) { - debug!( - "initialize global allocator at: [{:#x}, {:#x})", - start_vaddr, - start_vaddr + size - ); GLOBAL_ALLOCATOR.init(start_vaddr, size); + info!("global allocator initialized"); +} + +struct FnAddrTranslator { + func: fn(memory_addr::VirtAddr) -> memory_addr::PhysAddr, +} + +impl buddy_slab_allocator::AddrTranslator for FnAddrTranslator { + fn virt_to_phys(&self, va: usize) -> Option { + Some((self.func)(memory_addr::VirtAddr::from(va)).as_usize()) + } +} + +static mut GLOBAL_ADDR_TRANSLATOR: FnAddrTranslator = FnAddrTranslator { + func: |_| memory_addr::PhysAddr::from(0usize), +}; + +pub fn configure_addr_translator(func: fn(memory_addr::VirtAddr) -> memory_addr::PhysAddr) { + unsafe { + GLOBAL_ADDR_TRANSLATOR.func = func; + let translator: &'static FnAddrTranslator = &*(&raw const GLOBAL_ADDR_TRANSLATOR); + GLOBAL_ALLOCATOR.set_addr_translator(translator); + } } /// Add the given memory region to the global allocator. -/// + /// Users should ensure that the region is valid and not being used by others, /// so that the allocated memory is also valid. /// /// It's similar to [`global_init`], but can be called multiple times. -pub fn global_add_memory(start_vaddr: usize, size: usize) -> Result<(), ()> { +pub fn global_add_memory(start_vaddr: usize, size: usize) -> AllocResult { debug!( "add a memory region to global allocator: [{:#x}, {:#x})", start_vaddr, start_vaddr + size ); - GLOBAL_ALLOCATOR.add_memory(start_vaddr, size); - Ok(()) + GLOBAL_ALLOCATOR.add_memory(start_vaddr, size) } diff --git a/modules/axalloc/src/page.rs b/modules/axalloc/src/page.rs index a4b52315..50b90eec 100644 --- a/modules/axalloc/src/page.rs +++ b/modules/axalloc/src/page.rs @@ -1,7 +1,7 @@ -use axerrno::{AxError, AxResult}; +use axerrno::AxResult; use memory_addr::{PhysAddr, VirtAddr}; -use crate::{PAGE_SIZE, global_allocator}; +use crate::{PAGE_SIZE, UsageKind, global_allocator}; /// A RAII wrapper of contiguous 4K-sized pages. /// @@ -15,13 +15,13 @@ pub struct GlobalPage { impl GlobalPage { /// Allocate one 4K-sized page. pub fn alloc() -> AxResult { - global_allocator() - .alloc_pages(1, PAGE_SIZE) - .map(|vaddr| Self { - start_vaddr: vaddr.into(), - num_pages: 1, - }) - .map_err(alloc_err_to_ax_err) + let vaddr = global_allocator() + .alloc_pages(1, PAGE_SIZE, UsageKind::Global) + .map_err(|_| axerrno::AxError::from(axerrno::AxErrorKind::NoMemory))?; + Ok(Self { + start_vaddr: vaddr.into(), + num_pages: 1, + }) } /// Allocate one 4K-sized page and fill with zero. @@ -32,14 +32,14 @@ impl GlobalPage { } /// Allocate contiguous 4K-sized pages. - pub fn alloc_contiguous(num_pages: usize, align_pow2: usize) -> AxResult { - global_allocator() - .alloc_pages(num_pages, align_pow2) - .map(|vaddr| Self { - start_vaddr: vaddr.into(), - num_pages, - }) - .map_err(alloc_err_to_ax_err) + pub fn alloc_contiguous(num_pages: usize, alignment: usize) -> AxResult { + let vaddr = global_allocator() + .alloc_pages(num_pages, alignment, UsageKind::Global) + .map_err(|_| axerrno::AxError::from(axerrno::AxErrorKind::NoMemory))?; + Ok(Self { + start_vaddr: vaddr.into(), + num_pages, + }) } /// Get the start virtual address of this page. @@ -93,15 +93,10 @@ impl GlobalPage { impl Drop for GlobalPage { fn drop(&mut self) { - global_allocator().dealloc_pages(self.start_vaddr.into(), self.num_pages); - } -} - -const fn alloc_err_to_ax_err(e: AllocError) -> AxError { - match e { - AllocError::InvalidParam | AllocError::MemoryOverlap | AllocError::NotAllocated => { - AxError::InvalidInput - } - AllocError::NoMemory => AxError::NoMemory, + global_allocator().dealloc_pages( + self.start_vaddr.into(), + self.num_pages, + UsageKind::Global, + ); } } diff --git a/modules/axalloc/src/tracking.rs b/modules/axalloc/src/tracking.rs new file mode 100644 index 00000000..77ab7091 --- /dev/null +++ b/modules/axalloc/src/tracking.rs @@ -0,0 +1,88 @@ +use alloc::collections::btree_map::BTreeMap; +use core::{ + alloc::Layout, + ops::Range, + sync::atomic::{AtomicBool, Ordering}, +}; + +use axbacktrace::Backtrace; +use kspin::SpinNoIrq; + +pub(crate) static TRACKING_ENABLED: AtomicBool = AtomicBool::new(false); + +#[percpu::def_percpu] +pub(crate) static IN_GLOBAL_ALLOCATOR: bool = false; + +/// Metadata for each allocation made by the global allocator. +#[derive(Debug)] +pub struct AllocationInfo { + /// Layout of the allocation. + pub layout: Layout, + /// Backtrace at the time of allocation. + pub backtrace: Backtrace, + /// Generation at which the allocation was made. + pub generation: u64, +} + +pub(crate) struct GlobalState { + // FIXME: don't know why using HashMap causes crash + pub map: BTreeMap, + pub generation: u64, +} + +static STATE: SpinNoIrq = SpinNoIrq::new(GlobalState { + map: BTreeMap::new(), + generation: 0, +}); + +/// Enables allocation tracking. +pub fn enable_tracking() { + TRACKING_ENABLED.store(true, Ordering::SeqCst); +} + +/// Disables allocation tracking. +pub fn disable_tracking() { + TRACKING_ENABLED.store(false, Ordering::SeqCst); +} + +/// Returns whether allocation tracking is enabled. +pub fn tracking_enabled() -> bool { + TRACKING_ENABLED.load(Ordering::SeqCst) +} + +pub(crate) fn with_state(f: impl FnOnce(Option<&mut GlobalState>) -> R) -> R { + IN_GLOBAL_ALLOCATOR.with_current(|in_global| { + if *in_global || !tracking_enabled() { + f(None) + } else { + *in_global = true; + let mut state = STATE.lock(); + let result = f(Some(&mut state)); + *in_global = false; + result + } + }) +} + +/// Returns the current generation of the global allocator. +/// +/// The generation is incremented every time a new allocation is made. It +/// can be utilized to track the changes in the allocation state over time. +/// +/// See [`allocations_in`]. +pub fn current_generation() -> u64 { + STATE.lock().generation +} + +/// Visits all allocations made by the global allocator within the given +/// generation range. +pub fn allocations_in(range: Range, visitor: impl FnMut(&AllocationInfo)) { + with_state(|state| { + state + .unwrap() + .map + .values() + .filter(move |info| range.contains(&info.generation)) + .for_each(visitor) + }); +} diff --git a/modules/axruntime/src/lib.rs b/modules/axruntime/src/lib.rs index b7714daa..445a0950 100644 --- a/modules/axruntime/src/lib.rs +++ b/modules/axruntime/src/lib.rs @@ -226,12 +226,14 @@ pub fn rust_main(cpu_id: usize, arg: usize) -> ! { #[cfg(feature = "alloc")] fn init_allocator() { - use axhal::mem::{MemRegionFlags, memory_regions, phys_to_virt}; + use axhal::mem::{MemRegionFlags, VirtAddr, memory_regions, phys_to_virt, virt_to_phys}; use memory_addr::MemoryAddr; info!("Initialize global memory allocator..."); info!(" use {} allocator.", axalloc::global_allocator().name()); + axalloc::configure_addr_translator(virt_to_phys); + let mut max_region_size = 0; let mut max_region_paddr = 0.into(); let mut use_next_free = false; diff --git a/modules/axvm b/modules/axvm index afd3ff04..b4b88e06 160000 --- a/modules/axvm +++ b/modules/axvm @@ -1 +1 @@ -Subproject commit afd3ff04aff59dba6a58020c2baa69353c815457 +Subproject commit b4b88e06703b20b2f3b3eef409fafbf191cea679 From 519ce5692026b6a8d2db2311294f28e424289e5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 30 Jan 2026 13:44:18 +0800 Subject: [PATCH 16/19] =?UTF-8?q?fix:=20=E6=9B=B4=E6=96=B0=E5=AD=90?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=BC=95=E7=94=A8=E4=BB=A5=E5=8F=8D=E6=98=A0?= =?UTF-8?q?=E6=9C=80=E6=96=B0=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/arm_vcpu | 2 +- modules/axvm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/arm_vcpu b/modules/arm_vcpu index 84042ae5..5b7e40db 160000 --- a/modules/arm_vcpu +++ b/modules/arm_vcpu @@ -1 +1 @@ -Subproject commit 84042ae5fdbfe6d4c9ea4076d8caf5d48ca55d19 +Subproject commit 5b7e40dbf67d760e202114ba9431e81c2b45f9e7 diff --git a/modules/axvm b/modules/axvm index b4b88e06..afd3ff04 160000 --- a/modules/axvm +++ b/modules/axvm @@ -1 +1 @@ -Subproject commit b4b88e06703b20b2f3b3eef409fafbf191cea679 +Subproject commit afd3ff04aff59dba6a58020c2baa69353c815457 From 883e185db7bf053ff96aa45a19b27b5a5d8eb473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 30 Jan 2026 13:44:34 +0800 Subject: [PATCH 17/19] =?UTF-8?q?fix:=20=E6=9B=B4=E6=96=B0=20syn=20?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E7=89=88=E6=9C=AC=E8=87=B3=202.0.114?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 290c3163..e610cf79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2051,7 +2051,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.111", + "syn 2.0.114", "unicode-xid", ] From 9a84571773c2adfab2d10703aecd60c3bb17c3e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 30 Jan 2026 13:48:25 +0800 Subject: [PATCH 18/19] =?UTF-8?q?fix:=20=E6=9B=B4=E6=96=B0=20buddy-slab-al?= =?UTF-8?q?locator=20=E5=AD=90=E6=A8=A1=E5=9D=97=E5=BC=95=E7=94=A8?= =?UTF-8?q?=E8=87=B3=E6=9C=80=E6=96=B0=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index e610cf79..09caaf44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1331,7 +1331,7 @@ dependencies = [ [[package]] name = "buddy-slab-allocator" version = "0.1.0" -source = "git+https://github.com/arceos-hypervisor/buddy-slab-allocator.git?branch=main#60fc7e076b4d623828e8a9b83e1649ee4c379469" +source = "git+https://github.com/arceos-hypervisor/buddy-slab-allocator.git?branch=main#13167188073a43c58bff92b860c603ae10a03a0a" dependencies = [ "cfg-if", "log", From 0991526bce3b93b5677b6d4152286c88b0dee76c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:11:16 +0800 Subject: [PATCH 19/19] Next dev (#358) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 更新依赖项,添加 ringbuf 包并修正 VM 状态常量名称 * fix: 更新 axvm 子模块引用至最新提交 * fix: 修正启动页面设置中的入口指针类型转换 --- Cargo.lock | 10 ++++++++++ kernel/src/main.rs | 5 ++++- kernel/src/shell/commands/fs.rs | 1 - kernel/src/shell/commands/vm.rs | 8 ++++---- modules/axvm | 2 +- platform/x86-qemu-q35/src/mp.rs | 2 +- 6 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 09caaf44..0efa00d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1157,6 +1157,7 @@ dependencies = [ "page_table_multiarch", "ranges-ext", "raw-cpuid 11.6.0", + "ringbuf", "spin 0.10.0", "thiserror 2.0.18", "timer_list", @@ -4563,6 +4564,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ringbuf" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe47b720588c8702e34b5979cb3271a8b1842c7cb6f57408efa70c779363488c" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "riscv" version = "0.14.0" diff --git a/kernel/src/main.rs b/kernel/src/main.rs index e8b65bea..7763600f 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -4,17 +4,20 @@ #[macro_use] extern crate log; +#[allow(unused_imports)] #[macro_use] extern crate alloc; +#[allow(unused_imports)] +#[macro_use] extern crate axstd as std; extern crate driver; // extern crate axruntime; mod logo; -mod task; mod shell; +mod task; mod vmm; pub use shell::*; diff --git a/kernel/src/shell/commands/fs.rs b/kernel/src/shell/commands/fs.rs index fe2c03df..ac852084 100644 --- a/kernel/src/shell/commands/fs.rs +++ b/kernel/src/shell/commands/fs.rs @@ -7,7 +7,6 @@ use std::collections::BTreeMap; use std::fs::{self, File, FileType}; #[cfg(feature = "fs")] use std::io::{self, Read, Write}; -use std::println; use std::string::{String, ToString}; use super::super::parser::{CommandNode, FlagDef, ParsedCommand}; diff --git a/kernel/src/shell/commands/vm.rs b/kernel/src/shell/commands/vm.rs index 70487609..71e3e96f 100644 --- a/kernel/src/shell/commands/vm.rs +++ b/kernel/src/shell/commands/vm.rs @@ -143,7 +143,7 @@ fn vm_start(cmd: &ParsedCommand) { continue; } - if status != VMStatus::Inited && status != VMStatus::Stopped { + if status != VMStatus::Initialized && status != VMStatus::Stopped { println!("⚠ VM[{}] is in {:?} state, cannot start", vm.id(), status); continue; } @@ -289,7 +289,7 @@ fn stop_vm_by_id(vm_id: usize) { println!("⚠ VM[{}] is already stopped", vm_id); return; } - VMStatus::Inited => { + VMStatus::Initialized => { println!("⚠ VM[{}] is not running yet", vm_id); return; } @@ -364,7 +364,7 @@ fn delete_vm_by_id(vm_id: usize, force: bool) { } println!("⚠ Force deleting stopping VM[{}]...", vm_id); } - VMStatus::Stopped | VMStatus::Inited => { + VMStatus::Stopped | VMStatus::Initialized => { println!("Deleting VM[{}] (status: {:?})...", vm_id, status); // Resources will be automatically released when VM is dropped println!(" ✓ VM resources will be released on drop"); @@ -531,7 +531,7 @@ fn show_vm_details(vm_id: usize) { // Add state-specific information match status { - VMStatus::Inited => { + VMStatus::Initialized => { println!(); println!(" ℹ VM is ready. Use 'vm start {}' to boot.", vm_id); } diff --git a/modules/axvm b/modules/axvm index afd3ff04..87972cfb 160000 --- a/modules/axvm +++ b/modules/axvm @@ -1 +1 @@ -Subproject commit afd3ff04aff59dba6a58020c2baa69353c815457 +Subproject commit 87972cfbcd56ec9c9bac08a8f3b0f4cfb2d2e2af diff --git a/platform/x86-qemu-q35/src/mp.rs b/platform/x86-qemu-q35/src/mp.rs index da89b9a8..b5935310 100644 --- a/platform/x86-qemu-q35/src/mp.rs +++ b/platform/x86-qemu-q35/src/mp.rs @@ -29,7 +29,7 @@ unsafe fn setup_startup_page(stack_top: PhysAddr) { ); } start_page[U64_PER_PAGE - 2] = stack_top.as_usize() as u64; // stack_top - start_page[U64_PER_PAGE - 1] = ap_entry32 as usize as _; // entry + start_page[U64_PER_PAGE - 1] = ap_entry32 as *const () as usize as _; // entry } /// Starts the given secondary CPU with its boot stack.