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..3e346787 100644 --- a/.github/workflows/test-board.yml +++ b/.github/workflows/test-board.yml @@ -27,10 +27,14 @@ jobs: - ${{ matrix.board }} steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v6 + with: + submodules: "recursive" - - name: Install dependencies + - uses: cargo-bins/cargo-binstall@main + + - name: Install ostool + # run: cargo binstall ostool run: cargo +stable install ostool --version ^0.8 - name: Run tests diff --git a/.github/workflows/test-qemu.yml b/.github/workflows/test-qemu.yml index f4e510b3..b1c15412 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 @@ -31,10 +31,14 @@ jobs: - intel steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v6 + with: + submodules: "recursive" - - name: Install dependencies + - uses: cargo-bins/cargo-binstall@main + + - name: Install ostool + # run: cargo binstall ostool run: cargo +stable install ostool --version ^0.8 - name: Download images and patch vm configs 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..0efa00d9 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" @@ -59,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", ] @@ -74,7 +72,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "version_check", - "zerocopy 0.8.31", + "zerocopy 0.8.33", ] [[package]] @@ -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.3.0", "axconfig", "axdriver", "axerrno 0.1.2", @@ -214,30 +203,16 @@ dependencies = [ [[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" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5487b0a69ebddf2f8affd1e0d32a875fc6213b3a15e3315f9d7beb34b174d457" +checksum = "ad71090ed958939b87f6c99e0acd3c476b3a0dbd54f2e2d9aa1ca1f40dc4417e" 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,27 +237,20 @@ 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", "spin 0.10.0", ] [[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 +258,7 @@ dependencies = [ "axvisor_api", "bitmaps", "log", - "memory_addr 0.4.1", + "memory_addr", "spin 0.9.8", "tock-registers 0.10.1", ] @@ -307,6 +275,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" @@ -315,7 +289,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -332,21 +306,23 @@ 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", ] @@ -360,7 +336,22 @@ dependencies = [ "cfg-if", "kspin", "log", - "memory_addr 0.4.1", + "memory_addr", + "strum 0.27.2", +] + +[[package]] +name = "axalloc" +version = "0.3.0" +dependencies = [ + "axbacktrace", + "axerrno 0.2.2", + "buddy-slab-allocator", + "kernel_guard", + "kspin", + "log", + "memory_addr", + "percpu", "strum 0.27.2", ] @@ -384,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", @@ -394,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]] @@ -409,14 +400,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 +421,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 +429,7 @@ dependencies = [ "axvmconfig", "cfg-if", "log", - "memory_addr 0.4.1", + "memory_addr", "range-alloc", "spin 0.9.8", ] @@ -447,14 +437,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 +462,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.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)", @@ -491,10 +479,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 +577,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", "axbacktrace", "axdriver", "axfs", @@ -603,7 +591,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 +603,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 +634,7 @@ name = "axhal" version = "0.2.0" source = "git+https://github.com/arceos-org/arceos.git?tag=dev-251216#16096568f5ae6ad2d687eff2927a4cb69cef8133" dependencies = [ - "axalloc", + "axalloc 0.3.0", "axconfig", "axcpu", "axplat", @@ -661,7 +649,7 @@ dependencies = [ "lazyinit", "linkme", "log", - "memory_addr 0.4.1", + "memory_addr", "page_table_multiarch", "percpu", ] @@ -675,12 +663,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.114", +] + [[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 +704,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 +724,14 @@ name = "axmm" version = "0.2.0" source = "git+https://github.com/arceos-org/arceos.git?tag=dev-251216#16096568f5ae6ad2d687eff2927a4cb69cef8133" dependencies = [ - "axalloc", + "axalloc 0.3.0", "axconfig", "axerrno 0.1.2", "axhal", "kspin", "lazyinit", "log", - "memory_addr 0.4.1", + "memory_addr", "memory_set", ] @@ -763,36 +764,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.11+spec-1.1.0", ] [[package]] @@ -800,8 +800,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", @@ -850,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]] @@ -931,7 +931,7 @@ dependencies = [ name = "axruntime" version = "0.1.0" dependencies = [ - "axalloc", + "axalloc 0.3.0", "axconfig", "axdisplay", "axdriver", @@ -951,7 +951,9 @@ dependencies = [ "crate_interface", "ctor_bare", "log", + "memory_addr", "percpu", + "percpu_macros", "somehal", ] @@ -1008,15 +1010,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", @@ -1047,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", @@ -1067,114 +1069,113 @@ 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", + "syn 2.0.114", "timer_list", - "toml 0.9.10+spec-1.1.0", - "vm-fdt 0.3.0 (git+https://github.com/bullhh/vm-fdt.git)", + "toml 0.9.11+spec-1.1.0", ] [[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", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[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", + "derive_more", + "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", + "ringbuf", + "spin 0.10.0", + "thiserror 2.0.18", + "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", @@ -1184,7 +1185,7 @@ dependencies = [ "schemars", "serde", "serde_repr", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", ] [[package]] @@ -1202,25 +1203,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" @@ -1229,7 +1211,7 @@ checksum = "e585a01076fee271c5aabcf36212acb349fb3e638561d842fffa8ca013f4fdd8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1260,30 +1242,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" @@ -1364,16 +1326,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#13167188073a43c58bff92b860c603ae10a03a0a" dependencies = [ - "spin 0.9.8", + "cfg-if", + "log", ] [[package]] @@ -1499,7 +1461,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1513,7 +1475,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1533,23 +1495,14 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.49" +version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" 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" @@ -1564,9 +1517,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", @@ -1575,22 +1528,11 @@ 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" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -1598,9 +1540,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", @@ -1617,14 +1559,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" @@ -1634,11 +1576,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]] @@ -1765,7 +1707,7 @@ checksum = "70272a03a2cef15589bac05d3d15c023752f5f8f2da8be977d983a9d9e6250fb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1839,9 +1781,10 @@ dependencies = [ "crossterm_winapi", "derive_more", "document-features", + "futures-core", "mio", "parking_lot", - "rustix 1.1.2", + "rustix 1.1.3", "signal-hook", "signal-hook-mio", "winapi", @@ -1866,16 +1809,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" @@ -1893,15 +1826,9 @@ checksum = "9a49d5cd78b1c748184d41407b14a58af8403c13328ff2b9f49b0a418c24e3ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] -[[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" @@ -1973,6 +1900,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" @@ -1984,7 +1921,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1997,7 +1934,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]] @@ -2008,7 +1958,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2019,7 +1969,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]] @@ -2051,7 +2012,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2060,7 +2021,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]] @@ -2074,24 +2035,25 @@ 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", "quote", "rustc_version", - "syn 2.0.111", + "syn 2.0.114", + "unicode-xid", ] [[package]] @@ -2118,7 +2080,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2145,7 +2107,7 @@ dependencies = [ "aarch64-cpu-ext", "cfg-if", "spin 0.10.0", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2159,14 +2121,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", @@ -2234,7 +2196,7 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2246,7 +2208,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2267,7 +2229,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2278,7 +2240,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2299,7 +2261,7 @@ dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2370,7 +2332,7 @@ checksum = "ba8f5038f5845165d06fe1453fe4130ad546d3314818bbda57e208e7b0cffe08" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2388,29 +2350,50 @@ 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.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" @@ -2427,15 +2410,15 @@ dependencies = [ "md5", "serde", "sha1", - "thiserror 2.0.17", - "vm-fdt 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "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", @@ -2540,7 +2523,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2585,9 +2568,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", @@ -2616,12 +2599,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" @@ -2643,9 +2620,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", @@ -2684,16 +2661,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" @@ -2850,7 +2817,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", @@ -3069,9 +3036,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", @@ -3101,15 +3068,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]] @@ -3136,9 +3103,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", @@ -3150,15 +3117,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,15 +3128,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +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", @@ -3189,13 +3147,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]] @@ -3214,35 +3172,23 @@ 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", ] -[[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" @@ -3252,7 +3198,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3264,7 +3210,7 @@ dependencies = [ "bitflags 2.10.0", "prettyplease", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3333,29 +3279,19 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.178" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" - -[[package]] -name = "libloading" -version = "0.8.9" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if", - "windows-link", -] +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]] @@ -3412,7 +3348,7 @@ checksum = "e5cec0ec4228b4853bb129c84dbf093a27e6c7a20526da046defc334a1b017f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3479,15 +3415,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 +3464,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 +3477,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 +3496,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" @@ -3646,13 +3552,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", ] @@ -3667,19 +3573,9 @@ dependencies = [ "libc", ] -[[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" +version = "0.3.0" [[package]] name = "num" @@ -3815,7 +3711,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3847,9 +3743,9 @@ dependencies = [ [[package]] name = "ostool" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9faa3c583310e624402b2c8ee736594bc1fc99c225a06e40a6e7073fba98f71" +checksum = "b5243f6531ef2442252a2774f688f9f87f979bbc39db478cda4d27f8efaadb97" dependencies = [ "anyhow", "byte-unit", @@ -3860,6 +3756,7 @@ dependencies = [ "cursive", "env_logger", "fitimage", + "futures", "indicatif", "jkconfig", "log", @@ -3868,7 +3765,7 @@ dependencies = [ "object", "ratatui", "regex", - "reqwest 0.12.26", + "reqwest 0.12.28", "schemars", "serde", "serde_json", @@ -3877,33 +3774,11 @@ dependencies = [ "tar", "tftpd", "tokio", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", "uboot-shell", "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" @@ -3913,7 +3788,7 @@ dependencies = [ "bitflags 2.10.0", "log", "num-align", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -3922,9 +3797,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 +3810,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 +3845,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 +3855,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" @@ -4011,7 +3867,7 @@ dependencies = [ "log", "pci_types", "rdif-pcie", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -4040,27 +3896,7 @@ checksum = "8a9f4cc54a2e471ff72f1499461ba381ad4eae9cbd60d29c258545b995e406e0" dependencies = [ "proc-macro2", "quote", - "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", + "syn 2.0.114", ] [[package]] @@ -4080,33 +3916,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,52 +3925,28 @@ 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", - "syn 2.0.111", - "thiserror 2.0.17", + "syn 2.0.114", + "thiserror 2.0.18", ] [[package]] @@ -4173,7 +3958,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4196,9 +3981,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "portable-atomic-util" @@ -4230,7 +4015,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]] @@ -4240,19 +4025,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "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", + "syn 2.0.114", ] [[package]] @@ -4283,14 +4056,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", ] @@ -4329,7 +4102,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2 0.6.1", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -4350,7 +4123,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -4409,7 +4182,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]] @@ -4429,7 +4202,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]] @@ -4438,14 +4211,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", ] @@ -4455,6 +4228,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.18", +] + [[package]] name = "ratatui" version = "0.29.0" @@ -4467,7 +4250,7 @@ dependencies = [ "crossterm 0.28.1", "indoc", "instability", - "itertools 0.13.0", + "itertools", "lru", "paste", "strum 0.26.3", @@ -4494,18 +4277,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" @@ -4516,17 +4287,7 @@ dependencies = [ "async-trait", "paste", "rdif-def", - "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", + "thiserror 2.0.18", ] [[package]] @@ -4538,9 +4299,9 @@ dependencies = [ "cfg-if", "dma-api 0.5.2", "futures", - "rdif-base 0.7.0", + "rdif-base", "spin_on", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -4549,7 +4310,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]] @@ -4558,18 +4319,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c238eb44d86fabc99028adc973f896ce2202aeb6184cc8b89863f2d157d7ca06" 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", + "thiserror 2.0.18", ] [[package]] @@ -4579,8 +4329,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "170ec813e6cf4d1e5fa53fa8fed0fadc7aaab96683d4f1d44c602a6109931eb4" dependencies = [ "cfg-if", - "rdif-base 0.7.0", - "thiserror 2.0.17", + "rdif-base", + "thiserror 2.0.18", ] [[package]] @@ -4590,60 +4340,8 @@ 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", - "thiserror 2.0.17", + "rdif-base", + "thiserror 2.0.18", ] [[package]] @@ -4655,23 +4353,12 @@ 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", + "thiserror 2.0.18", ] [[package]] @@ -4682,7 +4369,7 @@ checksum = "eab3105c9af32e901a2adc7d920b39ff8b6ee0f6f0b7dfdeaf18f306ec12606f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4696,9 +4383,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", ] @@ -4720,7 +4407,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4819,9 +4506,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", @@ -4829,7 +4516,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", @@ -4863,15 +4550,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" @@ -4880,12 +4558,21 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "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" @@ -4899,25 +4586,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" @@ -4926,7 +4594,7 @@ checksum = "e8c4aa1ea1af6dcc83a61be12e8189f9b293c3ba5a487778a4cd89fb060fdbbc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4944,39 +4612,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", @@ -4995,9 +4636,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", @@ -5013,9 +4654,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", @@ -5043,19 +4684,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" @@ -5068,9 +4700,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", @@ -5084,9 +4716,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" @@ -5118,9 +4750,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", @@ -5131,9 +4763,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", @@ -5155,9 +4787,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", @@ -5165,37 +4797,15 @@ 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", "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 +4823,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "sbi-rt" @@ -5243,9 +4853,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", @@ -5256,14 +4866,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]] @@ -5277,7 +4887,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 +4897,7 @@ dependencies = [ "kspin", "log", "paste", - "smccc 0.2.2", + "smccc", "spin 0.10.0", ] @@ -5379,7 +4989,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5390,20 +5000,20 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -5425,7 +5035,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5529,10 +5139,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", ] @@ -5560,19 +5171,13 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c73e0ca8c566478040487791c9f488f86c5aec846ca1ab18484be8a1d8c55cd" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -5615,107 +5220,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", + "toml 0.9.11+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" @@ -5789,7 +5316,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5801,7 +5328,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5836,9 +5363,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", @@ -5868,7 +5395,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5932,14 +5459,14 @@ 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", "once_cell", - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.61.2", ] @@ -5960,11 +5487,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]] @@ -5975,25 +5502,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", @@ -6001,22 +5528,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", @@ -6073,9 +5600,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", @@ -6096,7 +5623,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -6121,9 +5648,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", @@ -6146,9 +5673,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", @@ -6226,9 +5753,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", @@ -6310,7 +5837,7 @@ dependencies = [ "lenient_semver", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -6364,14 +5891,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" @@ -6391,7 +5918,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", ] @@ -6457,9 +5984,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", @@ -6526,18 +6053,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#f89a04511f68ba81a48832edcc2ebf4b8fb793b0" +dependencies = [ + "libc", + "thiserror 2.0.18", +] [[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" @@ -6562,18 +6090,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", @@ -6584,11 +6112,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", @@ -6597,9 +6126,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", @@ -6607,31 +6136,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", @@ -6649,9 +6178,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", ] @@ -6699,7 +6228,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -6710,7 +6239,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -7000,9 +6529,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" @@ -7069,25 +6598,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.18", "x86", "x86_64", "x86_vlapic", @@ -7096,16 +6620,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 +6638,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,14 +6660,14 @@ dependencies = [ "flate2", "jkconfig", "ostool", - "reqwest 0.12.26", + "reqwest 0.12.28", "schemars", "serde", "serde_json", "sha2", "tar", "tokio", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", ] [[package]] @@ -7168,7 +6689,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure", ] @@ -7184,11 +6705,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]] @@ -7199,18 +6720,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]] @@ -7230,7 +6751,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure", ] @@ -7270,5 +6791,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 f06c0194..821efc0e 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,10 +45,12 @@ axstd = {git = "https://github.com/arceos-org/arceos.git", tag = "dev-251216", f "irq", "multitask", "task-ext", + "alloc", + "myplat", "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"} @@ -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,41 @@ 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" +axbacktrace = "0.1" -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"] axconfig = {path = "modules/axconfig"} axruntime = {path = "modules/axruntime"} axfs = {path = "modules/axfs"} +axalloc = {path = "modules/axalloc"} 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-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-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/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-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-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/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/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..3f6e33e3 --- /dev/null +++ b/doc/changelog/v0.3.0/AxVCpu.md @@ -0,0 +1,99 @@ +# 重构 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层抹除架构差异 + +### 单一配置源(Single Source of Truth) + +所有运行参数(arch、平台、设备编排、资源限制等)在 axvisor 中构建并冻结。 + +### Arch 隔离, 模块化设计 + +配置只“下行”到 axvm、axdevice;运行期状态可“上行”以供监控。 + +循环依赖(crate-interface)被限制在 `axplat <-> axplat-dyn` 最小范围内。 + +对 `arceos` 的依赖仅包括 `std` 兼容部分,以此使 `AxVisor` 本体和所有组件做到 OS 无关,后续可方便支持其他单内核甚至宏内核,微内核等不同宿主环境。 + +`Arch` 专属代码集中于 `axvm::arch::*`。对于 `Arch` 相关的功能,通过 `axplat-dyn` 透传 `somehal` 的 `Arch` 实现,使得 `Arch` 相关功能限制在 `AxVm` 内部,避免其他组件产生对 `Arch` 的依赖。 + +`common` 提供跨架构通用逻辑、模块与接口定义。 + +每个架构实现其特定的 VCPU、内存模型、中断注入等,组合`common`中的模块,如 `device`、`addrspace` 等。最终通过 `trait ArchVm` 适配器暴露统一接口供上层调用。从而实现增加或修改某个架构时不影响其他架构。 + +### 通过状态机管理虚拟机生命周期与资源分配 + +- `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 00000000..52425a8a Binary files /dev/null and b/doc/changelog/v0.3.0/architecture-old.png differ diff --git a/doc/changelog/v0.3.0/layout.dio b/doc/changelog/v0.3.0/layout.dio new file mode 100644 index 00000000..e841d6f9 --- /dev/null +++ b/doc/changelog/v0.3.0/layout.dio @@ -0,0 +1,307 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file 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 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..e17fb82d 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -3,69 +3,49 @@ 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 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} # 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 +spin = "0.9" +# axvm = { path = "../axvm" } +# axvmconfig = { version = "0.1", default-features = false } -[target.'cfg(target_arch = "aarch64")'.dependencies] -aarch64-cpu-ext = "0.1" -arm-gic-driver = {version = "0.15.5", features = ["rdif"]} +lazy_static = {version = "1.5", default-features = false, features = ["spin_no_std"]} [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..7763600f 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -4,32 +4,37 @@ #[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 axruntime; extern crate driver; -mod hal; +// extern crate axruntime; + mod logo; mod shell; mod task; mod vmm; +pub use shell::*; +pub use vmm::*; + #[unsafe(no_mangle)] 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"); - + vmm::wait_for_all_vms_exit(); + info!("All guest VMs exited."); 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..ac852084 100644 --- a/kernel/src/shell/command/base.rs +++ b/kernel/src/shell/commands/fs.rs @@ -1,12 +1,15 @@ +//! 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}; #[cfg(feature = "fs")] 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 +21,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 +281,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 +527,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 +647,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..75a7d05b --- /dev/null +++ b/kernel/src/shell/commands/mod.rs @@ -0,0 +1,240 @@ +//! 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..71e3e96f --- /dev/null +++ b/kernel/src/shell/commands/vm.rs @@ -0,0 +1,676 @@ +//! Virtual machine management commands +//! +//! Commands for managing virtual machines (create, start, stop, list, etc.). + +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 axvm::VMStatus; +use axvm::config::AxVMCrateConfig; + +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::Initialized && 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::Initialized => { + 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::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"); + } + _ => { + 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::Initialized => { + 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..00845b35 --- /dev/null +++ b/kernel/src/shell/completion.rs @@ -0,0 +1,292 @@ +//! 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, +} + +impl CompletionResult { + /// Create a new completion result + pub fn new(prefix: String, matches: Vec) -> Self { + Self { prefix, matches } + } + + /// Check if there's exactly one match + pub fn is_unique(&self) -> bool { + self.matches.len() == 1 + } + + /// Check if there are no matches + #[allow(dead_code)] + 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..48d5cc61 100644 --- a/kernel/src/shell/mod.rs +++ b/kernel/src/shell/mod.rs @@ -1,225 +1,32 @@ -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(); +//! ``` + +mod completion; +mod parser; +mod shell_impl; + +mod commands; + +// Re-export shell types and functions +pub use shell_impl::{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 78% rename from kernel/src/shell/command/history.rs rename to kernel/src/shell/parser/history.rs index 9c13fc83..63173ce1 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,8 +44,9 @@ 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() { @@ -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..ae809c38 --- /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_impl; + +pub use history::{CommandHistory, clear_line_and_redraw}; +pub use node::{CommandNode, FlagDef, OptionDef, ParseError, ParsedCommand}; +pub use parser_impl::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_impl.rs b/kernel/src/shell/parser/parser_impl.rs new file mode 100644 index 00000000..5526db08 --- /dev/null +++ b/kernel/src/shell/parser/parser_impl.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_impl.rs b/kernel/src/shell/shell_impl.rs new file mode 100644 index 00000000..443fca8d --- /dev/null +++ b/kernel/src/shell/shell_impl.rs @@ -0,0 +1,394 @@ +//! 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 = 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().is_multiple_of(3) { + 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 + .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); + + 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_command() { + 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/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..fb565f57 100644 --- a/kernel/src/vmm/config.rs +++ b/kernel/src/vmm/config.rs @@ -1,17 +1,75 @@ -use axaddrspace::GuestPhysAddr; -use axerrno::AxResult; +use alloc::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(); -#[cfg(target_arch = "aarch64")] -use crate::vmm::fdt::*; + // 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."); + } -use alloc::sync::Arc; + 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![]; + + 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, + }, + }; + + 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 +95,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 +108,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 +116,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 +142,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 +168,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/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 23addd73..62b5f494 100644 --- a/kernel/src/vmm/images/mod.rs +++ b/kernel/src/vmm/images/mod.rs @@ -1,309 +1,143 @@ -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::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 - ) - ) - })?; + use alloc::vec::Vec; + + 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, + }; - Ok(buffer) - } + // 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 + }; - /// 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 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 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 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 }; - // 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(()) + Ok(VMImagesConfig { + kernel, + bios, + dtb, + ramdisk, + }) } - 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)?; + fn load_image_file(path: &str) -> anyhow::Result> { + use axstd::io::Read; - let image_load_regions = vm.get_image_load_region(image_load_gpa, image_size)?; - let mut file = BufReader::new(image_file); + let mut file = axstd::fs::File::open(path) + .map_err(|e| anyhow::anyhow!("Failed to open image file '{}': {}", path, e))?; - 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) - ) - })?; + let metadata = file + .metadata() + .map_err(|e| anyhow::anyhow!("Failed to get metadata for '{}': {}", path, e))?; - crate::hal::arch::cache::dcache_range( - CacheOp::Clean, - (buffer.as_ptr() as usize).into(), - buffer.len(), - ); - } + let file_size = metadata.len() as usize; + let mut buffer = Vec::with_capacity(file_size); - Ok(()) - } + file.read_to_end(&mut buffer) + .map_err(|e| anyhow::anyhow!("Failed to read image file '{}': {}", path, e))?; - 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)) + Ok(buffer) } } diff --git a/kernel/src/vmm/mod.rs b/kernel/src/vmm/mod.rs index 5beecbce..1387ec36 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, VmId}; /// 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(vm.id()) } -/// 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/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 7c6dd419..33f39ff1 100644 --- a/kernel/src/vmm/vm_list.rs +++ b/kernel/src/vmm/vm_list.rs @@ -1,9 +1,7 @@ -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 +69,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..5b7e40db --- /dev/null +++ b/modules/arm_vcpu @@ -0,0 +1 @@ +Subproject commit 5b7e40dbf67d760e202114ba9431e81c2b45f9e7 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..28d5990a --- /dev/null +++ b/modules/axalloc/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "axalloc" +version.workspace = true +edition.workspace = true +authors = ["Yuekai Jia "] +description = "Axvisor global memory allocator" +license.workspace = true +repository = "https://github.com/arceos-org/arceos/tree/main/modules/axalloc" + +[features] +default = [] +tracking = ["dep:percpu", "dep:axbacktrace", "buddy-slab-allocator/tracking"] + +[dependencies] +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 new file mode 100644 index 00000000..98382196 --- /dev/null +++ b/modules/axalloc/src/lib.rs @@ -0,0 +1,380 @@ +//! The Axvisor memory allocator. + +#![no_std] + +#[macro_use] +extern crate log; +extern crate alloc; + +use core::{ + alloc::{GlobalAlloc, Layout}, + fmt, + 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; + +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. +/// +/// This is an adapter around the allocator::GlobalAllocator that provides +/// compatibility with the original axalloc API. +pub struct GlobalAllocator { + inner: SpinNoIrq>, + usages: SpinNoIrq, +} + +impl Default for GlobalAllocator { + fn default() -> Self { + Self::new() + } +} + +impl GlobalAllocator { + /// Creates an empty [`GlobalAllocator`]. + pub const fn new() -> Self { + Self { + 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-slab-allocator" + } + + /// Initializes the allocator with the given region. + pub fn init(&self, start_vaddr: usize, size: usize) { + info!( + "Initialize global memory allocator, start_vaddr: {}, size: {}", + 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. + 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. + 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. + pub fn dealloc(&self, pos: NonNull, layout: Layout) { + self.usages + .lock() + .dealloc(UsageKind::RustHeap, layout.size()); + self.inner.lock().dealloc(pos, layout); + } + + /// Allocates contiguous pages. + 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. + pub fn alloc_pages_at( + &self, + start: usize, + num_pages: usize, + 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. + 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 { + 0 + } + + /// Returns the number of available bytes in the byte allocator. + #[cfg(not(feature = "tracking"))] + pub fn available_bytes(&self) -> usize { + 0 + } + + /// Returns the number of allocated pages in the page allocator. + #[cfg(not(feature = "tracking"))] + pub fn used_pages(&self) -> usize { + 0 + } + + /// Returns the number of available pages in the page allocator. + #[cfg(not(feature = "tracking"))] + pub fn available_pages(&self) -> usize { + 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 { + 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) { + 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(); + } +} + +#[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) { + 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) -> AllocResult { + debug!( + "add a memory region to global allocator: [{:#x}, {:#x})", + start_vaddr, + start_vaddr + size + ); + GLOBAL_ALLOCATOR.add_memory(start_vaddr, size) +} diff --git a/modules/axalloc/src/page.rs b/modules/axalloc/src/page.rs new file mode 100644 index 00000000..50b90eec --- /dev/null +++ b/modules/axalloc/src/page.rs @@ -0,0 +1,102 @@ +use axerrno::AxResult; +use memory_addr::{PhysAddr, VirtAddr}; + +use crate::{PAGE_SIZE, UsageKind, 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 { + 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. + 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, 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. + 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, + 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/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/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/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..445a0950 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"))] { @@ -226,11 +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; @@ -250,7 +253,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..87972cfb --- /dev/null +++ b/modules/axvm @@ -0,0 +1 @@ +Subproject commit 87972cfbcd56ec9c9bac08a8f3b0f4cfb2d2e2af 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..b5935310 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,11 +25,11 @@ 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 - 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. 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"] 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"); }