diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index 94c20e3..0000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,4 +0,0 @@ - -[env] -AX_WORK_DIR = { value = ".", relative = true } -AX_LIBC_DIR = { value = "tools/axlibc", relative = true } \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7847211..bc7a673 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,48 +3,8 @@ name: Build CI on: push: pull_request: - workflow_call: - inputs: - TopTestDirectory: - description: 'The directory where the main repository will be placed' - required: true - type: string - CallerPackage: - description: 'The package to call the workflow' - required: true - type: string - CallerRepository: - description: 'The repository to call the workflow' - required: true - type: string - CallerCommit: - description: 'The commit of the repository to call the workflow' - required: true - type: string - TopBranch: - description: 'The branch of the main repository' - required: false - type: string - default: 'main' - jobs: - prepare_for_external_test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - id: step1 - if: github.repository != 'Starry-OS/Starry' - # 输出的值可以在后续的job中使用 - run: echo "TopTestDirectory=${{ inputs.TopTestDirectory }}" >> $GITHUB_OUTPUT - - id: step2 - if: github.repository == 'Starry-OS/Starry' - # 工作目录为当前目录 - run: echo "TopTestDirectory=." >> $GITHUB_OUTPUT - outputs: - TopTestDirectory: ${{ steps.step1.outputs.TopTestDirectory || steps.step2.outputs.TopTestDirectory }} - clippy: - needs: prepare_for_external_test runs-on: ubuntu-latest strategy: fail-fast: false @@ -52,7 +12,6 @@ jobs: rust-toolchain: [nightly, nightly-2024-05-02] env: RUSTUP_TOOLCHAIN: ${{ matrix.rust-toolchain }} - WORKING_DIRECTORY: ${{ needs.prepare_for_external_test.outputs.TopTestDirectory }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -60,38 +19,29 @@ jobs: toolchain: ${{ matrix.rust-toolchain }} components: rust-src, clippy, rustfmt targets: x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none, aarch64-unknown-none-softfloat - - name: Clone Top Repository - if: github.repository != 'Starry-OS/Starry' - run: git clone https://github.com/Starry-OS/Starry.git ${{ env.WORKING_DIRECTORY }} && cd ${{ env.WORKING_DIRECTORY }} && git checkout ${{ inputs.TopBranch }} - - name: Update Top Repository - working-directory: ${{ env.WORKING_DIRECTORY }} - run: make pre_update + - uses: ./.github/workflows/actions/setup-musl + with: + arch: x86_64 + - uses: ./.github/workflows/actions/setup-musl + with: + arch: riscv64 + - uses: ./.github/workflows/actions/setup-musl + with: + arch: aarch64 - name: Check rust version - working-directory: ${{ env.WORKING_DIRECTORY }} run: rustc --version --verbose - name: Clippy for the default target - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make clippy - name: Clippy for x86_64 - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make clippy ARCH=x86_64 - name: Clippy for riscv64 - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make clippy ARCH=riscv64 - name: Clippy for aarch64 - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make clippy ARCH=aarch64 - name: Check code format - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: cargo fmt --all -- --check build-apps-for-unikernel: - needs: prepare_for_external_test runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -101,7 +51,6 @@ jobs: rust-toolchain: [nightly, nightly-2024-05-02] env: RUSTUP_TOOLCHAIN: ${{ matrix.rust-toolchain }} - WORKING_DIRECTORY: ${{ needs.prepare_for_external_test.outputs.TopTestDirectory }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -111,109 +60,56 @@ jobs: targets: x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none, aarch64-unknown-none-softfloat - uses: Swatinem/rust-cache@v2 - run: cargo install cargo-binutils - - name: Clone Top Repository - if: github.repository != 'Starry-OS/Starry' - run: | - git clone https://github.com/Starry-OS/Starry.git ${{ env.WORKING_DIRECTORY }} && cd ${{ env.WORKING_DIRECTORY }} && git checkout ${{ inputs.TopBranch }} && cd .. - sh ${{ env.WORKING_DIRECTORY }}/scripts/test/external_test.sh ${{ env.WORKING_DIRECTORY }} ${{ inputs.CallerPackage }} ${{ inputs.CallerRepository }} ${{ inputs.CallerCommit }} - - name: Update Top Repository - working-directory: ${{ env.WORKING_DIRECTORY }} - run: make pre_update - name: Build helloworld - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/helloworld - name: Build memtest - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/memtest - name: Build exception - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/exception - name: Build display - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/display - name: Build task/yield - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/task/yield - name: Build task/parallel - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/task/parallel - name: Build task/sleep - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/task/sleep - name: Build task/priority - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/task/priority - name: Build task/tls - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/task/tls - name: Build fs/shell - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/fs/shell - name: Build net/echoserver - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/net/echoserver - name: Build net/httpclient - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/net/httpclient - name: Build net/httpserver - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/net/httpserver - name: Build net/udpserver - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/net/udpserver - uses: ./.github/workflows/actions/setup-musl with: arch: ${{ matrix.arch }} - name: Build c/helloworld - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/c/helloworld - name: Build c/memtest - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/c/memtest - name: Build c/sqlite3 - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/c/sqlite3 - name: Build c/httpclient - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/c/httpclient - name: Build c/httpserver - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/c/httpserver - name: Build c/udpserver - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/c/udpserver - name: Build c/iperf - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/c/iperf - name: Build c/redis - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make ARCH=${{ matrix.arch }} A=apps/c/redis SMP=4 build-apps-for-other-platforms: - needs: prepare_for_external_test runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -222,7 +118,6 @@ jobs: rust-toolchain: [nightly, nightly-2024-05-02] env: RUSTUP_TOOLCHAIN: ${{ matrix.rust-toolchain }} - WORKING_DIRECTORY: ${{ needs.prepare_for_external_test.outputs.TopTestDirectory }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -233,50 +128,27 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: cargo install cargo-binutils - - name: Clone Top Repository - if: github.repository != 'Starry-OS/Starry' - run: | - git clone https://github.com/Starry-OS/Starry.git ${{ env.WORKING_DIRECTORY }} && cd ${{ env.WORKING_DIRECTORY }} && git checkout ${{ inputs.TopBranch }} && cd .. - sh ${{ env.WORKING_DIRECTORY }}/scripts/test/external_test.sh ${{ env.WORKING_DIRECTORY }} ${{ inputs.CallerPackage }} ${{ inputs.CallerRepository }} ${{ inputs.CallerCommit }} - - name: Update Top Repository - working-directory: ${{ env.WORKING_DIRECTORY }} - run: make pre_update - uses: ./.github/workflows/actions/setup-musl with: arch: x86_64 - name: Build helloworld for x86_64-pc-oslab - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make PLATFORM=x86_64-pc-oslab A=apps/helloworld - name: Build net/httpserver for x86_64-pc-oslab - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make PLATFORM=x86_64-pc-oslab A=apps/net/httpserver FEATURES=driver-ixgbe - name: Build c/iperf for x86_64-pc-oslab - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make PLATFORM=x86_64-pc-oslab A=apps/c/iperf FEATURES=driver-ixgbe,driver-ramdisk - name: Build c/redis for x86_64-pc-oslab - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make PLATFORM=x86_64-pc-oslab A=apps/c/redis FEATURES=driver-ixgbe,driver-ramdisk SMP=4 - name: Build helloworld for aarch64-raspi4 - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make PLATFORM=aarch64-raspi4 A=apps/helloworld - name: Build fs/shell for aarch64-raspi4 - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make PLATFORM=aarch64-raspi4 A=apps/fs/shell FEATURES=driver-bcm2835-sdhci - name: Build helloworld for aarch64-bsta1000b - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: make PLATFORM=aarch64-bsta1000b A=apps/helloworld build-apps-for-std: - needs: prepare_for_external_test runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -286,75 +158,39 @@ jobs: rust-toolchain: [nightly-2024-05-02] env: RUSTUP_TOOLCHAIN: ${{ matrix.rust-toolchain }} - WORKING_DIRECTORY: ${{ needs.prepare_for_external_test.outputs.TopTestDirectory }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.rust-toolchain }} - - name: Clone Top Repository - if: github.repository != 'Starry-OS/Starry' - run: | - git clone https://github.com/Starry-OS/Starry.git ${{ env.WORKING_DIRECTORY }} && cd ${{ env.WORKING_DIRECTORY }} && git checkout ${{ inputs.TopBranch }} && cd .. - sh ${{ env.WORKING_DIRECTORY }}/scripts/test/external_test.sh ${{ env.WORKING_DIRECTORY }} ${{ inputs.CallerPackage }} ${{ inputs.CallerRepository }} ${{ inputs.CallerCommit }} - - name: Update Top Repository - working-directory: ${{ env.WORKING_DIRECTORY }} - run: make pre_update - name: Build helloworld - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: cargo build -p arceos-helloworld - name: Build memtest - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: cargo build -p arceos-memtest - name: Build exception - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: cargo build -p arceos-exception - name: Build task/yield - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: cargo build -p arceos-yield - name: Build task/parallel - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: cargo build -p arceos-parallel - name: Build task/sleep - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: cargo build -p arceos-sleep - name: Build task/priority - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: cargo build -p arceos-priority - name: Build task/tls - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: cargo build -p arceos-tls - name: Build fs/shell - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: cargo build -p arceos-shell - name: Build net/echoserver - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: cargo build -p arceos-echoserver - name: Build net/httpclient - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: cargo build -p arceos-httpclient - name: Build net/httpserver - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: cargo build -p arceos-httpserver - name: Build net/udpserver - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: cargo build -p arceos-udpserver build-apps-for-monolithic: - needs: prepare_for_external_test runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -364,7 +200,6 @@ jobs: rust-toolchain: [nightly, nightly-2024-05-02] env: RUSTUP_TOOLCHAIN: ${{ matrix.rust-toolchain }} - WORKING_DIRECTORY: ${{ needs.prepare_for_external_test.outputs.TopTestDirectory }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -374,17 +209,7 @@ jobs: targets: x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none, aarch64-unknown-none-softfloat - uses: Swatinem/rust-cache@v2 - run: cargo install cargo-binutils - - name: Clone Top Repository - if: github.repository != 'Starry-OS/Starry' - run: | - git clone https://github.com/Starry-OS/Starry.git ${{ env.WORKING_DIRECTORY }} && cd ${{ env.WORKING_DIRECTORY }} && git checkout ${{ inputs.TopBranch }} && cd .. - sh ${{ env.WORKING_DIRECTORY }}/scripts/test/external_test.sh ${{ env.WORKING_DIRECTORY }} ${{ inputs.CallerPackage }} ${{ inputs.CallerRepository }} ${{ inputs.CallerCommit }} - - name: Update Top Repository - working-directory: ${{ env.WORKING_DIRECTORY }} - run: make pre_update - name: Bulld monolithic-userboot - working-directory: ${{ env.WORKING_DIRECTORY }} - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run: | sh ./build_img.sh -a ${{ matrix.arch }} make ARCH=${{ matrix.arch }} A=apps/monolithic_userboot diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6fba4a3..92ee06b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -20,8 +20,9 @@ jobs: with: profile: minimal toolchain: ${{ env.rust-toolchain }} - - name: Update Top Repository - run: make pre_update + - uses: ./.github/workflows/actions/setup-musl + with: + arch: x86_64 - name: Build docs continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }} run: make doc_check_missing diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e4275d0..b1e6a7a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,59 +3,19 @@ name: Test CI on: push: pull_request: - workflow_call: - inputs: - TopTestDirectory: - description: 'The directory where the main repository will be placed' - required: true - type: string - CallerPackage: - description: 'The package to call the workflow' - required: true - type: string - CallerRepository: - description: 'The repository to call the workflow' - required: true - type: string - CallerCommit: - description: 'The commit of the repository to call the workflow' - required: true - type: string - TopBranch: - description: 'The branch of the main repository' - required: false - type: string - default: 'main' + env: qemu-version: 8.2.0 rust-toolchain: nightly-2024-05-02 jobs: - prepare_for_external_test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - id: step1 - if: github.repository != 'Starry-OS/Starry' - # 输出的值可以在后续的job中使用 - run: echo "TopTestDirectory=${{ inputs.TopTestDirectory }}" >> $GITHUB_OUTPUT - - id: step2 - if: github.repository == 'Starry-OS/Starry' - run: echo "TopTestDirectory=./" >> $GITHUB_OUTPUT - outputs: - TopTestDirectory: ${{ steps.step1.outputs.TopTestDirectory || steps.step2.outputs.TopTestDirectory }} - - app-test-for-unikernel: - needs: prepare_for_external_test runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest] arch: [x86_64, riscv64, aarch64] - env: - WORKING_DIRECTORY: ${{ needs.prepare_for_external_test.outputs.TopTestDirectory }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -64,14 +24,6 @@ jobs: components: rust-src - uses: Swatinem/rust-cache@v2 - run: cargo install cargo-binutils - - name: Clone Top Repository - if: github.repository != 'Starry-OS/Starry' - run: | - git clone https://github.com/Starry-OS/Starry.git ${{ env.WORKING_DIRECTORY }} && cd ${{ env.WORKING_DIRECTORY }} && git checkout ${{ inputs.TopBranch }} && cd .. - sh ${{ env.WORKING_DIRECTORY }}/scripts/test/external_test.sh ${{ env.WORKING_DIRECTORY }} ${{ inputs.CallerPackage }} ${{ inputs.CallerRepository }} ${{ inputs.CallerCommit }} - - name: Update Top Repository - working-directory: ${{ env.WORKING_DIRECTORY }} - run: make pre_update - uses: ./.github/workflows/actions/setup-qemu with: qemu-version: ${{ env.qemu-version }} @@ -79,21 +31,17 @@ jobs: with: arch: ${{ matrix.arch }} - name: Run app tests - working-directory: ${{ env.WORKING_DIRECTORY }} run: | make disk_img make test ARCH=${{ matrix.arch }} app-test-for-monolithic: - needs: prepare_for_external_test runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest] arch: [x86_64, riscv64, aarch64] - env: - WORKING_DIRECTORY: ${{ needs.prepare_for_external_test.outputs.TopTestDirectory }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -102,14 +50,6 @@ jobs: components: rust-src - uses: Swatinem/rust-cache@v2 - run: cargo install cargo-binutils - - name: Clone Top Repository - if: github.repository != 'Starry-OS/Starry' - run: | - git clone https://github.com/Starry-OS/Starry.git ${{ env.WORKING_DIRECTORY }} && cd ${{ env.WORKING_DIRECTORY }} && git checkout ${{ inputs.TopBranch }} && cd .. - sh ${{ env.WORKING_DIRECTORY }}/scripts/test/external_test.sh ${{ env.WORKING_DIRECTORY }} ${{ inputs.CallerPackage }} ${{ inputs.CallerRepository }} ${{ inputs.CallerCommit }} - - name: Update Top Repository - working-directory: ${{ env.WORKING_DIRECTORY }} - run: make pre_update - uses: ./.github/workflows/actions/setup-qemu with: qemu-version: ${{ env.qemu-version }} @@ -117,20 +57,16 @@ jobs: with: arch: ${{ matrix.arch }} - name: Run app tests - working-directory: ${{ env.WORKING_DIRECTORY }} run: | make disk_img make test_monolithic ARCH=${{ matrix.arch }} app-test-for-ext4fs: - needs: prepare_for_external_test runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest] - arch: [x86_64, riscv64, aarch64] - env: - WORKING_DIRECTORY: ${{ needs.prepare_for_external_test.outputs.TopTestDirectory }} + arch: [x86_64, riscv64, aarch64] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -139,14 +75,6 @@ jobs: components: rust-src - uses: Swatinem/rust-cache@v2 - run: cargo install cargo-binutils - - name: Clone Top Repository - if: github.repository != 'Starry-OS/Starry' - run: | - git clone https://github.com/Starry-OS/Starry.git ${{ env.WORKING_DIRECTORY }} && cd ${{ env.WORKING_DIRECTORY }} && git checkout ${{ inputs.TopBranch }} && cd .. - sh ${{ env.WORKING_DIRECTORY }}/scripts/test/external_test.sh ${{ env.WORKING_DIRECTORY }} ${{ inputs.CallerPackage }} ${{ inputs.CallerRepository }} ${{ inputs.CallerCommit }} - - name: Update Top Repository - working-directory: ${{ env.WORKING_DIRECTORY }} - run: make pre_update - uses: ./.github/workflows/actions/setup-qemu with: qemu-version: ${{ env.qemu-version }} @@ -154,7 +82,6 @@ jobs: with: arch: ${{ matrix.arch }} - name: Run app tests - working-directory: ${{ needs.prepare_for_external_test.outputs.TopTestDirectory }} run: | sh ./build_img.sh -a ${{ matrix.arch }} -fs ext4 export MONOLITHIC_TESTCASE=other diff --git a/Cargo.lock b/Cargo.lock index b41167c..d6628e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,7 +37,34 @@ version = "0.1.0" source = "git+https://github.com/Starry-OS/allocator.git#7c9f5b83faf77893a5fb4387cd94fa6a27b65779" dependencies = [ "bitmap-allocator", + "buddy_system_allocator 0.9.1", "rlsf", + "slab_allocator", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "another_ext4" +version = "0.1.0" +source = "git+https://github.com/Starry-OS/ljx_ext4.git?branch=main#ae00f9ed9ca9fe81e608a419f85eacb922bde816" +dependencies = [ + "bitflags 2.6.0", + "log", + "spin", ] [[package]] @@ -156,17 +183,16 @@ dependencies = [ [[package]] name = "arceos_api" version = "0.1.0" -source = "git+https://github.com/Starry-OS/arceos_api.git#fc77e8d99632a8d89fd10481a74186891f2fa1f2" dependencies = [ "axalloc", - "axconfig", + "axconfig 0.1.0", "axdisplay", "axerrno", "axfeat", "axfs", "axhal", "axio", - "axlog", + "axlog 0.1.0", "axnet", "axruntime", "axtask", @@ -175,16 +201,15 @@ dependencies = [ [[package]] name = "arceos_posix_api" version = "0.1.0" -source = "git+https://github.com/Starry-OS/arceos_posix_api.git#0ef12d062570badfad6b9832d2c886c2c24d6bcb" dependencies = [ "axalloc", - "axconfig", + "axconfig 0.1.0", "axerrno", "axfeat", "axfs", "axhal", "axio", - "axlog", + "axlog 0.1.0", "axnet", "axruntime", "axsync", @@ -199,30 +224,30 @@ dependencies = [ [[package]] name = "arch_boot" version = "0.1.0" -source = "git+https://github.com/Starry-OS/arch_boot.git#03cb86283f3bbc3d85c88e37dfc561fe80d24210" dependencies = [ "aarch64-cpu", - "axconfig", + "axalloc", + "axconfig 0.1.0", "axhal", - "axlog", + "axlog 0.1.0", "axruntime", "axtrap", "cfg-if", "linux_syscall_api", "log", "of", - "raw-cpuid 11.1.0", + "raw-cpuid 11.2.0", "riscv 0.10.1", "sbi-rt 0.0.2", "tock-registers", "x86", - "x86_64 0.14.12", + "x86_64 0.15.1", ] [[package]] name = "arm_gic" version = "0.1.0" -source = "git+https://github.com/Starry-OS/arm_gic.git#9aedb126ed0b059ee4e69e032c5e41b593ed10f0" +source = "git+https://github.com/Starry-OS/arm_gic.git#b6dfcc3221338749fb26d90271c8ed8f69dc114b" dependencies = [ "aarch64-cpu", "bitflags 2.6.0", @@ -238,16 +263,24 @@ dependencies = [ "tock-registers", ] +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axalloc" version = "0.1.0" -source = "git+https://github.com/Starry-OS/axalloc.git#da0de65d739b91fb422d33477d10cee473436c42" dependencies = [ "allocator", "axerrno", @@ -269,7 +302,15 @@ dependencies = [ [[package]] name = "axconfig" version = "0.1.0" -source = "git+https://github.com/Starry-OS/axconfig.git#ecfc417015a36450abb514092fb8c811512205ea" +dependencies = [ + "serde", + "toml_edit", +] + +[[package]] +name = "axconfig" +version = "0.1.0" +source = "git+https://github.com/Starry-OS/axconfig.git#2a8803384caa8ee69af3156c0df8dc2cd9722539" dependencies = [ "serde", "toml_edit", @@ -278,7 +319,6 @@ dependencies = [ [[package]] name = "axdisplay" version = "0.1.0" -source = "git+https://github.com/Starry-OS/axdisplay.git#ada3928ed2f9303cc0bbf75758c53716447ac730" dependencies = [ "axdriver", "axsync", @@ -290,10 +330,9 @@ dependencies = [ [[package]] name = "axdriver" version = "0.1.0" -source = "git+https://github.com/Starry-OS/axdriver.git#fdf9d5efb411cfecfe4cc06bc9517a1fc8f3f7d3" dependencies = [ "axalloc", - "axconfig", + "axconfig 0.1.0", "axhal", "cfg-if", "driver_block", @@ -316,14 +355,13 @@ dependencies = [ [[package]] name = "axfeat" version = "0.1.0" -source = "git+https://github.com/Starry-OS/axfeat.git#a0d5a4813397aae6ce84a749a1c16955f4e9a874" dependencies = [ "axalloc", "axdisplay", "axdriver", "axfs", "axhal", - "axlog", + "axlog 0.1.0", "axnet", "axprocess", "axruntime", @@ -336,9 +374,9 @@ dependencies = [ [[package]] name = "axfs" version = "0.1.0" -source = "git+https://github.com/Starry-OS/axfs.git#74ba6295c0113d2305d0037ffbae558fa237e846" dependencies = [ - "axconfig", + "another_ext4", + "axconfig 0.1.0", "axdriver", "axerrno", "axfs_devfs", @@ -346,14 +384,17 @@ dependencies = [ "axfs_vfs", "axio", "axsync", + "axtask", "bitflags 2.6.0", "capability", "cfg-if", "crate_interface", "driver_block", + "ext4_rs", "fatfs", "lazy_init", "log", + "lwext4_rust", ] [[package]] @@ -392,31 +433,31 @@ dependencies = [ [[package]] name = "axfutex" version = "0.1.0" -source = "git+https://github.com/Starry-OS/axfutex.git#5eee19d07e750720c1b540dc8990dbf96c565433" dependencies = [ "axerrno", "axfs", "axhal", - "axlog", + "axlog 0.1.0 (git+https://github.com/Starry-OS/axlog.git)", "axsync", "axtask", + "bitflags 2.6.0", "hashbrown 0.11.2", "lazy_static", "log", + "numeric-enum-macro", ] [[package]] name = "axhal" version = "0.1.0" -source = "git+https://github.com/Starry-OS/axhal.git#769caa8b8749b324a1370a8da11c0e6a7597eedf" dependencies = [ "aarch64-cpu", "arm_gic", "arm_pl011", "axalloc", - "axconfig", + "axconfig 0.1.0", "axfs_ramfs", - "axlog", + "axlog 0.1.0", "bitflags 2.6.0", "cfg-if", "crate_interface", @@ -432,7 +473,7 @@ dependencies = [ "page_table_entry", "percpu", "ratio", - "raw-cpuid 11.1.0", + "raw-cpuid 11.2.0", "riscv 0.11.1", "sbi-rt 0.0.3", "spinlock", @@ -464,6 +505,18 @@ dependencies = [ "bindgen", ] +[[package]] +name = "axlog" +version = "0.1.0" +dependencies = [ + "axlog 0.1.0", + "cfg-if", + "chrono", + "crate_interface", + "log", + "spinlock", +] + [[package]] name = "axlog" version = "0.1.0" @@ -478,10 +531,9 @@ dependencies = [ [[package]] name = "axmem" version = "0.1.0" -source = "git+https://github.com/Starry-OS/axmem.git#2e66a8ad1a4f14f629d2a07e67abcf21f5216012" dependencies = [ "axalloc", - "axconfig", + "axconfig 0.1.0", "axerrno", "axfs", "axhal", @@ -498,7 +550,6 @@ dependencies = [ [[package]] name = "axnet" version = "0.1.0" -source = "git+https://github.com/Starry-OS/axnet.git#48d68eb8c32bfb1bb89977fbe19ca42a0d1fb7a9" dependencies = [ "axdriver", "axerrno", @@ -511,23 +562,22 @@ dependencies = [ "driver_net", "lazy_init", "log", - "smoltcp", + "smoltcp 0.11.0", "spin", ] [[package]] name = "axprocess" version = "0.1.0" -source = "git+https://github.com/Starry-OS/axprocess.git#6ab9c7a7d41f43ecebcd6ee329bd9fa7edc763b8" dependencies = [ "axalloc", - "axconfig", + "axconfig 0.1.0", "axerrno", "axfs", "axfutex", "axhal", "axio", - "axlog", + "axlog 0.1.0", "axmem", "axsignal", "axsync", @@ -544,15 +594,14 @@ dependencies = [ [[package]] name = "axruntime" version = "0.1.0" -source = "git+https://github.com/Starry-OS/axruntime.git#0a8b576c156767eeff3ee43a3dab3a1548ffe3ee" dependencies = [ "axalloc", - "axconfig", + "axconfig 0.1.0", "axdisplay", "axdriver", "axfs", "axhal", - "axlog", + "axlog 0.1.0", "axmem", "axnet", "axprocess", @@ -567,7 +616,6 @@ dependencies = [ [[package]] name = "axsignal" version = "0.1.0" -source = "git+https://github.com/Starry-OS/axsignal.git#2bd62ee3a0a5e7135803315db87df1edc51004ce" dependencies = [ "axhal", "bitflags 2.6.0", @@ -578,19 +626,18 @@ dependencies = [ [[package]] name = "axstarry" version = "0.1.0" -source = "git+https://github.com/Starry-OS/axstarry.git#a60752d10eb805a820436b86c429d621a95384ce" dependencies = [ "arch_boot", "axfeat", "axfs", - "axlog", + "axlog 0.1.0", + "axtask", "linux_syscall_api", ] [[package]] name = "axstd" version = "0.1.0" -source = "git+https://github.com/Starry-OS/axstd.git#0b0595b3f24a68009a47d120e5991b89a6c9f2d3" dependencies = [ "arceos_api", "arch_boot", @@ -603,24 +650,25 @@ dependencies = [ [[package]] name = "axsync" version = "0.1.0" -source = "git+https://github.com/Starry-OS/axsync.git#70651a7c8e918e6f6aced3014acfd16f209aa4c0" dependencies = [ "axhal", + "axsync", "axtask", "cfg-if", + "rand", "spinlock", ] [[package]] name = "axtask" version = "0.1.0" -source = "git+https://github.com/Starry-OS/axtask.git#5c7f4e485a4ab08d51b3118828e3ee1552d54dc6" dependencies = [ "axbacktrace", - "axconfig", + "axconfig 0.1.0", "axhal", - "axlog", + "axlog 0.1.0", "axsignal", + "axtask", "cfg-if", "kernel_guard", "lazy_init", @@ -629,6 +677,7 @@ dependencies = [ "memory_addr", "numeric-enum-macro", "percpu", + "rand", "scheduler", "spinlock", "taskctx", @@ -638,11 +687,10 @@ dependencies = [ [[package]] name = "axtrap" version = "0.1.0" -source = "git+https://github.com/Starry-OS/axtrap.git#f2529e64355db2c4df9561fd6603bf9eb9325cc4" dependencies = [ "aarch64-cpu", "axhal", - "axlog", + "axlog 0.1.0", "axtask", "cfg-if", "handler_table", @@ -669,16 +717,27 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "bcm2835-sdhci" +version = "0.1.0" +source = "git+https://github.com/lhw2002426/bcm2835-sdhci.git?rev=e974f16#e974f168efa72b470a01f61bdef32240c66f54fc" +dependencies = [ + "aarch64-cpu", + "log", + "tock-registers", + "volatile 0.2.7", +] + [[package]] name = "bindgen" -version = "0.69.4" +version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools", + "itertools 0.12.1", "lazy_static", "lazycell", "log", @@ -688,7 +747,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.72", + "syn 2.0.90", "which", ] @@ -730,6 +789,24 @@ version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6" +[[package]] +name = "buddy_system_allocator" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d44d578cadd17312c75e7d0ef489361f160ace58f7139aa32001fee1a51b89b5" + +[[package]] +name = "buddy_system_allocator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7913f22349ffcfc6ca0ca9a656ec26cfbba538ed49c31a273dff2c5d1ea83d9" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "byteorder" version = "1.5.0" @@ -745,6 +822,15 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "cc" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +dependencies = [ + "shlex", +] + [[package]] name = "cexpr" version = "0.6.0" @@ -760,6 +846,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -777,6 +877,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + [[package]] name = "crate_interface" version = "0.1.1" @@ -784,20 +896,36 @@ source = "git+https://github.com/Starry-OS/crate_interface.git#d27dd9608dbf04b31 dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] name = "critical-section" -version = "1.1.2" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "cstr_core" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd98742e4fdca832d40cab219dc2e3048de17d873248f83f17df47c1bea70956" +dependencies = [ + "cty", + "memchr", +] + +[[package]] +name = "cty" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" [[package]] name = "defmt" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99dd22262668b887121d4672af5a64b238f026099f1a2a1b322066c9ecfe9e0" +checksum = "86f6162c53f659f65d00619fe31f14556a6e9f8752ccc4a41bd177ffcf3d6130" dependencies = [ "bitflags 1.3.2", "defmt-macros", @@ -805,22 +933,22 @@ dependencies = [ [[package]] name = "defmt-macros" -version = "0.3.9" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9f309eff1f79b3ebdf252954d90ae440599c26c2c553fe87a2d17195f2dcb" +checksum = "9d135dd939bad62d7490b0002602d35b358dce5fd9233a709d3c1ef467d4bde6" dependencies = [ "defmt-parser", - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] name = "defmt-parser" -version = "0.3.4" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4a5fefe330e8d7f31b16a318f9ce81000d8e35e69b93eae154d16d2278f70f" +checksum = "3983b127f13995e68c1e29071e5d115cd96f215ccb5e6812e3728cd6f92653b3" dependencies = [ "thiserror", ] @@ -828,8 +956,9 @@ dependencies = [ [[package]] name = "driver_block" version = "0.1.0" -source = "git+https://github.com/Starry-OS/driver_block.git#47e771d7d312ad7090d452a0ac95e1d617f13aca" +source = "git+https://github.com/Starry-OS/driver_block.git#077ab12e19b3bdea99b0c8bcb484ceef076965ce" dependencies = [ + "bcm2835-sdhci", "driver_common", "log", ] @@ -853,6 +982,8 @@ version = "0.1.0" source = "git+https://github.com/Starry-OS/driver_net.git#5d16bf2fe6eae2aa3b7d4f929162c07d949a6955" dependencies = [ "driver_common", + "e1000-driver", + "ixgbe-driver", "log", "spin", ] @@ -885,6 +1016,15 @@ dependencies = [ "tock-registers", ] +[[package]] +name = "e1000-driver" +version = "0.2.0" +source = "git+https://github.com/elliott10/e1000-driver.git?rev=7b2458e5#7b2458e5606909fd7116072cf07844db006559a0" +dependencies = [ + "log", + "volatile 0.3.0", +] + [[package]] name = "either" version = "1.13.0" @@ -943,17 +1083,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" -[[package]] -name = "enumn" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.72", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -962,12 +1091,21 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "ext4_rs" +version = "1.0.0" +source = "git+https://github.com/yuoo655/ext4_rs.git?rev=6bcc7f5#6bcc7f5d6382ba1940f29f9a15869f35fe77b5c0" +dependencies = [ + "bitflags 2.6.0", + "log", ] [[package]] @@ -1024,6 +1162,15 @@ name = "handler_table" version = "0.1.0" source = "git+https://github.com/Starry-OS/handler_table.git#d6495e7d835cbc999874f0ecf466ee1211447a79" +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hash32" version = "0.3.1" @@ -1044,9 +1191,22 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "heapless" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32 0.2.1", + "rustc_version", + "spin", + "stable_deref_trait", +] [[package]] name = "heapless" @@ -1054,7 +1214,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ - "hash32", + "hash32 0.3.1", "stable_deref_trait", ] @@ -1064,17 +1224,49 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", ] [[package]] name = "indexmap" -version = "2.2.6" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.2", +] + +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", ] [[package]] @@ -1086,6 +1278,28 @@ dependencies = [ "either", ] +[[package]] +name = "ixgbe-driver" +version = "0.1.0" +source = "git+https://github.com/KuangjuX/ixgbe-driver.git?rev=8e5eb74#8e5eb741299d7d95c373ec745e39a1473fe84563" +dependencies = [ + "bit_field", + "core_detect", + "log", + "smoltcp 0.10.0", + "volatile 0.3.0", +] + +[[package]] +name = "js-sys" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "kernel_guard" version = "0.1.0" @@ -1117,15 +1331,15 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets", @@ -1134,7 +1348,7 @@ dependencies = [ [[package]] name = "linked_list" version = "0.1.0" -source = "git+https://github.com/Starry-OS/linked_list.git#01ec54f275fda4ece9741f10d281550c077c0312" +source = "git+https://github.com/Starry-OS/linked_list.git#283dddc46fac48d007eb3c67ea52f1b136d15c13" [[package]] name = "linux-raw-sys" @@ -1145,15 +1359,14 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "linux_syscall_api" version = "0.1.0" -source = "git+https://github.com/Starry-OS/linux_syscall_api.git#bc828f3316e6b4560f5e79602b679c7d9d31d5ff" dependencies = [ - "axconfig", + "axconfig 0.1.0", "axerrno", "axfeat", "axfs", "axfutex", "axhal", - "axlog", + "axlog 0.1.0", "axmem", "axnet", "axprocess", @@ -1182,9 +1395,18 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "lwext4_rust" +version = "0.2.0" +source = "git+https://github.com/elliott10/lwext4_rust.git#83489293ef4535672caee0c5755729f3e0315a96" +dependencies = [ + "log", + "printf-compat", +] [[package]] name = "managed" @@ -1284,7 +1506,7 @@ source = "git+https://github.com/mexus/numeric-enum-macro#20aef288b2ecd2381ab662 [[package]] name = "of" version = "0.1.0" -source = "git+https://github.com/Starry-OS/of.git#ae6dc82903a23f2ad7d959beccb7651efe21e935" +source = "git+https://github.com/Starry-OS/of.git#45a6550889880fb4f35da7e2c3e00bf4dbf89295" dependencies = [ "fdt", "lazy_static", @@ -1292,9 +1514,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "page_table" @@ -1342,57 +1564,76 @@ source = "git+https://github.com/Starry-OS/percpu_macros.git#b785b814c5489ac7043 dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] -name = "prettyplease" +name = "ppv-lite86" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.72", + "syn 2.0.90", +] + +[[package]] +name = "printf-compat" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b002af28ffe3d3d67202ae717810a28125a494d5396debc43de01ee136ac404" +dependencies = [ + "bitflags 1.3.2", + "cstr_core", + "cty", + "itertools 0.9.0", ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "proc-macro-error-attr2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.109", - "version_check", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "proc-macro-error2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ + "proc-macro-error-attr2", "proc-macro2", "quote", - "version_check", + "syn 2.0.90", ] [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1403,6 +1644,18 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", "rand_core", ] @@ -1411,6 +1664,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] [[package]] name = "ratio" @@ -1428,18 +1684,18 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "11.1.0" +version = "11.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb9ee317cfe3fbd54b36a511efc1edd42e216903c9cd575e686dd68a2ba90d8d" +checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.5" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -1449,9 +1705,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1460,9 +1716,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "riscv" @@ -1503,24 +1759,33 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "sbi-rt" @@ -1569,24 +1834,30 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "serde" -version = "1.0.204" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -1595,16 +1866,38 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "slab_allocator" +version = "0.3.1" +source = "git+https://github.com/arceos-org/slab_allocator.git#3c13499d664ccd36f66786985b753340aea57f5a" +dependencies = [ + "buddy_system_allocator 0.10.0", +] + +[[package]] +name = "smoltcp" +version = "0.10.0" +source = "git+https://github.com/rcore-os/smoltcp.git?rev=2ade274#2ade2747abc4d779d0836154b0413d13ce16cd5b" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "cfg-if", + "defmt", + "heapless 0.7.17", + "log", + "managed", +] + [[package]] name = "smoltcp" version = "0.11.0" -source = "git+https://github.com/rcore-os/smoltcp.git?rev=b7134a3#b7134a31442db149c2398b27f95a5d2b54414a4d" +source = "git+https://github.com/rcore-os/smoltcp.git?rev=8bf9a9a#8bf9a9a61ce9d50e72b71d541ecb24e29cc5450e" dependencies = [ "bitflags 1.3.2", "byteorder", "cfg-if", "defmt", - "heapless", + "heapless 0.8.0", "log", "managed", ] @@ -1665,9 +1958,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -1677,9 +1970,10 @@ dependencies = [ [[package]] name = "taskctx" version = "0.1.0" -source = "git+https://github.com/Starry-OS/taskctx.git#d7d646ca1f4cc2bd0466563b0563b2c321fb0929" +source = "git+https://github.com/Starry-OS/taskctx.git#3c3ad2fb4defce02bc6186b836471ad28ab9c349" dependencies = [ "aarch64-cpu", + "axconfig 0.1.0 (git+https://github.com/Starry-OS/axconfig.git)", "cfg-if", "log", "memory_addr", @@ -1691,22 +1985,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -1728,9 +2022,9 @@ checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.22.18" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1490595c74d930da779e944f5ba2ecdf538af67df1a9848cbd156af43c1b7cf0" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "toml_datetime", @@ -1739,15 +2033,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "version_check" @@ -1757,12 +2051,11 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "virtio-drivers" -version = "0.7.5" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6a39747311dabb3d37807037ed1c3c38d39f99198d091b5b79ecd5c8d82f799" +checksum = "aa40e09453618c7a927c08c5a990497a2954da7c2aaa6c65e0d4f0fc975f6114" dependencies = [ "bitflags 2.6.0", - "enumn", "log", "zerocopy", ] @@ -1773,6 +2066,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "volatile" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b06ad3ed06fef1713569d547cdbdb439eafed76341820fb0e0344f29a41945" + +[[package]] +name = "volatile" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8e76fae08f03f96e166d2dfda232190638c10e0383841252416f9cfe2ae60e6" + [[package]] name = "volatile" version = "0.4.6" @@ -1785,6 +2090,61 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.90", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" + [[package]] name = "which" version = "4.4.2" @@ -1797,6 +2157,15 @@ dependencies = [ "rustix", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -1806,6 +2175,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -1872,9 +2250,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.16" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b480ae9340fc261e6be3e95a1ba86d54ae3f9171132a73ce8d4bbaf68339507c" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -1912,7 +2290,7 @@ dependencies = [ "bit_field", "bitflags 2.6.0", "rustversion", - "volatile", + "volatile 0.4.6", ] [[package]] @@ -1924,7 +2302,7 @@ dependencies = [ "bit_field", "bitflags 2.6.0", "rustversion", - "volatile", + "volatile 0.4.6", ] [[package]] @@ -1960,5 +2338,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] diff --git a/Cargo.toml b/Cargo.toml index fa2521f..23a379f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,89 @@ -[patch] +[workspace] +resolver = "2" -[profile.dev] -lto = true +members = [ + "modules/arch_boot", + "modules/axalloc", + "modules/axconfig", + "modules/axdisplay", + "modules/axdriver", + "modules/axfs", + "modules/axfutex", + "modules/axhal", + "modules/axlog", + "modules/axmem", + "modules/axnet", + "modules/axprocess", + "modules/axruntime", + "modules/axsignal", + "modules/axsync", + "modules/axtask", + "modules/axtrap", + + "api/axfeat", + "api/arceos_api", + "api/arceos_posix_api", + "api/linux_syscall_api", + + "ulib/axstd", + "ulib/axlibc", + "ulib/axstarry", + + "apps/display", + "apps/exception", + "apps/helloworld", + "apps/memtest", + "apps/fs/shell", + "apps/net/echoserver", + "apps/net/httpclient", + "apps/net/httpserver", + "apps/net/udpserver", + "apps/net/bwbench", + "apps/task/parallel", + "apps/task/sleep", + "apps/task/yield", + "apps/task/priority", + "apps/task/tls", + "apps/monolithic_userboot", +] + +[workspace.package] +version = "0.1.0" +authors = ["Yuekai Jia "] +license = "GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0" +homepage = "https://github.com/arceos-org/arceos" +documentation = "https://arceos-org.github.io/arceos" +repository = "https://github.com/arceos-org/arceos" +keywords = ["arceos", "kernel"] +categories = ["os", "no-std"] + +[workspace.dependencies] +axstd = { path = "ulib/axstd" } +axlibc = { path = "ulib/axlibc" } +axstarry = { path = "ulib/axstarry" } + +arceos_api = { path = "api/arceos_api" } +arceos_posix_api = { path = "api/arceos_posix_api" } +axfeat = { path = "api/axfeat" } +linux_syscall_api = { path = "api/linux_syscall_api" } + +arch_boot = { path = "modules/arch_boot" } +axalloc = { path = "modules/axalloc" } +axconfig = { path = "modules/axconfig" } +axdisplay = { path = "modules/axdisplay" } +axdriver = { path = "modules/axdriver" } +axfs = { path = "modules/axfs" } +axfutex = { path = "modules/axfutex" } +axhal = { path = "modules/axhal" } +axlog = { path = "modules/axlog" } +axmem = { path = "modules/axmem" } +axnet = { path = "modules/axnet" } +axprocess = { path = "modules/axprocess" } +axruntime = { path = "modules/axruntime" } +axsignal = { path = "modules/axsignal" } +axsync = { path = "modules/axsync" } +axtask = { path = "modules/axtask" } +axtrap = { path = "modules/axtrap" } [profile.release] lto = true - -[workspace] -members = ["apps/display", "apps/exception", "apps/helloworld", "apps/memtest", "apps/fs/shell", "apps/net/echoserver", "apps/net/httpclient", "apps/net/httpserver", "apps/net/udpserver", "apps/net/bwbench", "apps/task/parallel", "apps/task/sleep", "apps/task/yield", "apps/task/priority", "apps/task/tls", "apps/monolithic_userboot", "tools/axlibc"] -resolver = "2" diff --git a/Makefile b/Makefile index 812774c..45bec39 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ SMP ?= 1 MODE ?= release LOG ?= off V ?= -LIBC_DIR = tools/axlibc +LIBC_DIR = ulib/axlibc # App options A ?= apps/monolithic_userboot APP ?= $(A) @@ -133,6 +133,7 @@ export AX_LOG=$(LOG) export AX_TARGET=$(TARGET) export AX_IP=$(IP) export AX_GW=$(GW) +export AX_WORK_DIR=$(CURDIR) # Binutils CROSS_COMPILE ?= $(ARCH)-linux-musl- @@ -210,7 +211,7 @@ fmt: cargo fmt --all fmt_c: - @clang-format --style=file -i $(shell find tools/axlibc -iname '*.c' -o -iname '*.h') + @clang-format --style=file -i $(shell find ulib/axlibc -iname '*.c' -o -iname '*.h') test: $(call app_test) @@ -230,7 +231,7 @@ clean: clean_c cargo clean clean_c:: - rm -rf tools/axlibc/build_* + rm -rf ulib/axlibc/build_* rm -rf $(app-objs) .PHONY: all build disasm run justrun debug clippy fmt fmt_c test test_no_fail_fast clean clean_c doc disk_image make_bin diff --git a/api/arceos_api/.gitignore b/api/arceos_api/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/api/arceos_api/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/api/arceos_api/Cargo.toml b/api/arceos_api/Cargo.toml new file mode 100644 index 0000000..672efc4 --- /dev/null +++ b/api/arceos_api/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "arceos_api" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Public APIs and types for ArceOS modules" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/api/arceos_api" +documentation = "https://rcore-os.github.io/arceos/arceos_api/index.html" +keywords = ["Starry"] + +[features] +default = [] + +irq = ["axfeat/irq"] +alloc = ["dep:axalloc", "axfeat/alloc"] +multitask = ["axtask/multitask", "axfeat/multitask"] +fs = ["dep:axfs", "axfeat/fs"] +net = ["dep:axnet", "axfeat/net"] +display = ["dep:axdisplay", "axfeat/display"] + +myfs = ["axfeat/myfs"] + +# Use dummy functions if the feature is not enabled +dummy-if-not-enabled = [] + +[dependencies] +axfeat = { workspace = true } +axruntime = { workspace = true } +axconfig = { workspace = true } +axlog = { workspace = true } +axio = { git = "https://github.com/Starry-OS/axio.git" } +axerrno = { git = "https://github.com/Starry-OS/axerrno.git" } +axhal = { workspace = true } +axalloc = { workspace = true, optional = true } +axtask = { workspace = true, optional = true } +axfs = { workspace = true, optional = true } +axnet = { workspace = true, optional = true } +axdisplay = { workspace = true, optional = true } diff --git a/api/arceos_api/src/imp/display.rs b/api/arceos_api/src/imp/display.rs new file mode 100644 index 0000000..1abe25e --- /dev/null +++ b/api/arceos_api/src/imp/display.rs @@ -0,0 +1,11 @@ +pub use axdisplay::DisplayInfo as AxDisplayInfo; + +/// Gets the framebuffer information. +pub fn ax_framebuffer_info() -> AxDisplayInfo { + axdisplay::framebuffer_info() +} + +/// Flushes the framebuffer, i.e. show on the screen. +pub fn ax_framebuffer_flush() { + axdisplay::framebuffer_flush() +} diff --git a/api/arceos_api/src/imp/fs.rs b/api/arceos_api/src/imp/fs.rs new file mode 100644 index 0000000..fd7dac3 --- /dev/null +++ b/api/arceos_api/src/imp/fs.rs @@ -0,0 +1,87 @@ +use alloc::string::String; +use axerrno::AxResult; +use axfs::fops::{Directory, File}; + +pub use axfs::fops::DirEntry as AxDirEntry; +pub use axfs::fops::FileAttr as AxFileAttr; +pub use axfs::fops::FilePerm as AxFilePerm; +pub use axfs::fops::FileType as AxFileType; +pub use axfs::fops::OpenOptions as AxOpenOptions; +pub use axio::SeekFrom as AxSeekFrom; + +#[cfg(feature = "myfs")] +pub use axfs::fops::{Disk as AxDisk, MyFileSystemIf}; + +/// A handle to an opened file. +pub struct AxFileHandle(File); + +/// A handle to an opened directory. +pub struct AxDirHandle(Directory); + +pub fn ax_open_file(path: &str, opts: &AxOpenOptions) -> AxResult { + Ok(AxFileHandle(File::open(path, opts)?)) +} + +pub fn ax_open_dir(path: &str, opts: &AxOpenOptions) -> AxResult { + Ok(AxDirHandle(Directory::open_dir(path, opts)?)) +} + +pub fn ax_read_file(file: &mut AxFileHandle, buf: &mut [u8]) -> AxResult { + file.0.read(buf) +} + +pub fn ax_read_file_at(file: &AxFileHandle, offset: u64, buf: &mut [u8]) -> AxResult { + file.0.read_at(offset, buf) +} + +pub fn ax_write_file(file: &mut AxFileHandle, buf: &[u8]) -> AxResult { + file.0.write(buf) +} + +pub fn ax_write_file_at(file: &AxFileHandle, offset: u64, buf: &[u8]) -> AxResult { + file.0.write_at(offset, buf) +} + +pub fn ax_truncate_file(file: &AxFileHandle, size: u64) -> AxResult { + file.0.truncate(size) +} + +pub fn ax_flush_file(file: &AxFileHandle) -> AxResult { + file.0.flush() +} + +pub fn ax_seek_file(file: &mut AxFileHandle, pos: AxSeekFrom) -> AxResult { + file.0.seek(pos) +} + +pub fn ax_file_attr(file: &AxFileHandle) -> AxResult { + file.0.get_attr() +} + +pub fn ax_read_dir(dir: &mut AxDirHandle, dirents: &mut [AxDirEntry]) -> AxResult { + dir.0.read_dir(dirents) +} + +pub fn ax_create_dir(path: &str) -> AxResult { + axfs::api::create_dir(path) +} + +pub fn ax_remove_dir(path: &str) -> AxResult { + axfs::api::remove_dir(path) +} + +pub fn ax_remove_file(path: &str) -> AxResult { + axfs::api::remove_file(path) +} + +pub fn ax_rename(old: &str, new: &str) -> AxResult { + axfs::api::rename(old, new) +} + +pub fn ax_current_dir() -> AxResult { + axfs::api::current_dir() +} + +pub fn ax_set_current_dir(path: &str) -> AxResult { + axfs::api::set_current_dir(path) +} diff --git a/api/arceos_api/src/imp/mem.rs b/api/arceos_api/src/imp/mem.rs new file mode 100644 index 0000000..3dd1ded --- /dev/null +++ b/api/arceos_api/src/imp/mem.rs @@ -0,0 +1,12 @@ +cfg_alloc! { + use core::alloc::Layout; + use core::ptr::NonNull; + + pub fn ax_alloc(layout: Layout) -> Option> { + axalloc::global_allocator().alloc(layout).ok() + } + + pub fn ax_dealloc(ptr: NonNull, layout: Layout) { + axalloc::global_allocator().dealloc(ptr, layout) + } +} diff --git a/api/arceos_api/src/imp/mod.rs b/api/arceos_api/src/imp/mod.rs new file mode 100644 index 0000000..865fdc4 --- /dev/null +++ b/api/arceos_api/src/imp/mod.rs @@ -0,0 +1,42 @@ +mod mem; +mod task; + +cfg_fs! { + mod fs; + pub use fs::*; +} + +cfg_net! { + mod net; + pub use net::*; +} + +cfg_display! { + mod display; + pub use display::*; +} + +mod stdio { + use core::fmt; + + pub fn ax_console_read_byte() -> Option { + axhal::console::getchar().map(|c| if c == b'\r' { b'\n' } else { c }) + } + + pub fn ax_console_write_bytes(buf: &[u8]) -> crate::AxResult { + axhal::console::write_bytes(buf); + Ok(buf.len()) + } + + pub fn ax_console_write_fmt(args: fmt::Arguments) -> fmt::Result { + axlog::print_fmt(args) + } +} + +pub use self::mem::*; +pub use self::stdio::*; +pub use self::task::*; + +pub use axhal::misc::terminate as ax_terminate; +pub use axhal::time::{current_time as ax_current_time, TimeValue as AxTimeValue}; +pub use axio::PollState as AxPollState; diff --git a/api/arceos_api/src/imp/net.rs b/api/arceos_api/src/imp/net.rs new file mode 100644 index 0000000..88d4f77 --- /dev/null +++ b/api/arceos_api/src/imp/net.rs @@ -0,0 +1,131 @@ +use crate::io::AxPollState; +use axerrno::AxResult; +use axnet::{UdpSocket, TcpSocket}; +use core::net::{IpAddr, SocketAddr}; + +/// A handle to a TCP socket. +pub struct AxTcpSocketHandle(TcpSocket); + +/// A handle to a UDP socket. +pub struct AxUdpSocketHandle(UdpSocket); + +//////////////////////////////////////////////////////////////////////////////// +// TCP socket +//////////////////////////////////////////////////////////////////////////////// + +pub fn ax_tcp_socket() -> AxTcpSocketHandle { + AxTcpSocketHandle(TcpSocket::new()) +} + +pub fn ax_tcp_socket_addr(socket: &AxTcpSocketHandle) -> AxResult { + socket.0.local_addr() +} + +pub fn ax_tcp_peer_addr(socket: &AxTcpSocketHandle) -> AxResult { + socket.0.peer_addr() +} + +pub fn ax_tcp_set_nonblocking(socket: &AxTcpSocketHandle, nonblocking: bool) -> AxResult { + socket.0.set_nonblocking(nonblocking); + Ok(()) +} + +pub fn ax_tcp_connect(socket: &AxTcpSocketHandle, addr: SocketAddr) -> AxResult { + socket.0.connect(addr) +} + +pub fn ax_tcp_bind(socket: &AxTcpSocketHandle, addr: SocketAddr) -> AxResult { + socket.0.bind(addr) +} + +pub fn ax_tcp_listen(socket: &AxTcpSocketHandle, _backlog: usize) -> AxResult { + socket.0.listen() +} + +pub fn ax_tcp_accept(socket: &AxTcpSocketHandle) -> AxResult<(AxTcpSocketHandle, SocketAddr)> { + let new_sock = socket.0.accept()?; + let addr = new_sock.peer_addr()?; + Ok((AxTcpSocketHandle(new_sock), addr)) +} + +pub fn ax_tcp_send(socket: &AxTcpSocketHandle, buf: &[u8]) -> AxResult { + socket.0.send(buf) +} + +pub fn ax_tcp_recv(socket: &AxTcpSocketHandle, buf: &mut [u8]) -> AxResult { + socket.0.recv(buf) +} + +pub fn ax_tcp_poll(socket: &AxTcpSocketHandle) -> AxResult { + socket.0.poll() +} + +pub fn ax_tcp_shutdown(socket: &AxTcpSocketHandle) -> AxResult { + socket.0.shutdown() +} + +//////////////////////////////////////////////////////////////////////////////// +// UDP socket +//////////////////////////////////////////////////////////////////////////////// + +pub fn ax_udp_socket() -> AxUdpSocketHandle { + AxUdpSocketHandle(UdpSocket::new()) +} + +pub fn ax_udp_socket_addr(socket: &AxUdpSocketHandle) -> AxResult { + socket.0.local_addr() +} + +pub fn ax_udp_peer_addr(socket: &AxUdpSocketHandle) -> AxResult { + socket.0.peer_addr() +} + +pub fn ax_udp_set_nonblocking(socket: &AxUdpSocketHandle, nonblocking: bool) -> AxResult { + socket.0.set_nonblocking(nonblocking); + Ok(()) +} + +pub fn ax_udp_bind(socket: &AxUdpSocketHandle, addr: SocketAddr) -> AxResult { + socket.0.bind(addr) +} + +pub fn ax_udp_recv_from(socket: &AxUdpSocketHandle, buf: &mut [u8]) -> AxResult<(usize, SocketAddr)> { + socket.0.recv_from(buf) +} + +pub fn ax_udp_peek_from(socket: &AxUdpSocketHandle, buf: &mut [u8]) -> AxResult<(usize, SocketAddr)> { + socket.0.peek_from(buf) +} + +pub fn ax_udp_send_to(socket: &AxUdpSocketHandle, buf: &[u8], addr: SocketAddr) -> AxResult { + socket.0.send_to(buf, addr) +} + +pub fn ax_udp_connect(socket: &AxUdpSocketHandle, addr: SocketAddr) -> AxResult { + socket.0.connect(addr) +} + +pub fn ax_udp_send(socket: &AxUdpSocketHandle, buf: &[u8]) -> AxResult { + socket.0.send(buf) +} + +pub fn ax_udp_recv(socket: &AxUdpSocketHandle, buf: &mut [u8]) -> AxResult { + socket.0.recv(buf) +} + +pub fn ax_udp_poll(socket: &AxUdpSocketHandle) -> AxResult { + socket.0.poll() +} + +//////////////////////////////////////////////////////////////////////////////// +// Miscellaneous +//////////////////////////////////////////////////////////////////////////////// + +pub fn ax_dns_query(domain_name: &str) -> AxResult> { + axnet::dns_query(domain_name) +} + +pub fn ax_poll_interfaces() -> AxResult { + axnet::poll_interfaces(); + Ok(()) +} diff --git a/api/arceos_api/src/imp/task.rs b/api/arceos_api/src/imp/task.rs new file mode 100644 index 0000000..63516aa --- /dev/null +++ b/api/arceos_api/src/imp/task.rs @@ -0,0 +1,112 @@ +pub fn ax_sleep_until(deadline: crate::time::AxTimeValue) { + #[cfg(feature = "multitask")] + axtask::sleep_until(deadline); + #[cfg(not(feature = "multitask"))] + axhal::time::busy_wait_until(deadline); +} + +pub fn ax_yield_now() { + #[cfg(feature = "multitask")] + axtask::yield_now(); + #[cfg(not(feature = "multitask"))] + if cfg!(feature = "irq") { + axhal::arch::wait_for_irqs(); + } else { + core::hint::spin_loop(); + } +} + +pub fn ax_exit(_exit_code: i32) -> ! { + #[cfg(feature = "multitask")] + axtask::exit(_exit_code); + #[cfg(not(feature = "multitask"))] + axhal::misc::terminate(); +} + +cfg_task! { + use core::time::Duration; + + /// A handle to a task. + pub struct AxTaskHandle { + inner: axtask::AxTaskRef, + id: u64, + } + + impl AxTaskHandle { + /// Returns the task ID. + pub fn id(&self) -> u64 { + self.id + } + } + + /// A handle to a wait queue. + /// + /// A wait queue is used to store sleeping tasks waiting for a certain event + /// to happen. + pub struct AxWaitQueueHandle(axtask::WaitQueue); + + impl AxWaitQueueHandle { + /// Creates a new empty wait queue. + pub const fn new() -> Self { + Self(axtask::WaitQueue::new()) + } + } + + pub fn ax_current_task_id() -> u64 { + axtask::current().id().as_u64() + } + + pub fn ax_spawn(f: F, name: alloc::string::String, stack_size: usize) -> AxTaskHandle + where + F: FnOnce() + Send + 'static, + { + let inner = axtask::spawn_raw(f, name, stack_size); + AxTaskHandle { + id: inner.id().as_u64(), + inner, + } + } + + pub fn ax_wait_for_exit(task: AxTaskHandle) -> Option { + // task.inner.join() + axtask::join(&task.inner) + } + + pub fn ax_set_current_priority(prio: isize) -> crate::AxResult { + if axtask::set_priority(prio) { + Ok(()) + } else { + axerrno::ax_err!( + BadState, + "ax_set_current_priority: failed to set task priority" + ) + } + } + + pub fn ax_wait_queue_wait( + wq: &AxWaitQueueHandle, + until_condition: impl Fn() -> bool, + timeout: Option, + ) -> bool { + #[cfg(feature = "irq")] + if let Some(dur) = timeout { + return wq.0.wait_timeout_until(dur, until_condition); + } + + if timeout.is_some() { + axlog::warn!("ax_wait_queue_wait: the `timeout` argument is ignored without the `irq` feature"); + } + wq.0.wait_until(until_condition); + false + } + + pub fn ax_wait_queue_wake(wq: &AxWaitQueueHandle, count: u32) { + if count == u32::MAX { + wq.0.notify_all(); + } else { + for _ in 0..count { + wq.0.notify_one(); + } + } + } +} diff --git a/api/arceos_api/src/lib.rs b/api/arceos_api/src/lib.rs new file mode 100644 index 0000000..eacf0f5 --- /dev/null +++ b/api/arceos_api/src/lib.rs @@ -0,0 +1,339 @@ +//! Public APIs and types for [ArceOS] modules +//! +//! [ArceOS]: https://github.com/rcore-os/arceos + +#![no_std] +#![feature(doc_auto_cfg)] +#![feature(doc_cfg)] +#![allow(unused_imports)] + +#[cfg(any( + feature = "alloc", + feature = "fs", + feature = "net", + feature = "multitask", + feature = "dummy-if-not-enabled" +))] +extern crate alloc; +extern crate axruntime; + +#[macro_use] +mod macros; +mod imp; + +pub use axerrno::{AxError, AxResult}; + +/// Platform-specific constants and parameters. +pub mod config { + pub use axconfig::*; +} + +/// System operations. +pub mod sys { + define_api! { + /// Shutdown the whole system and all CPUs. + pub fn ax_terminate() -> !; + } +} + +/// Time-related operations. +pub mod time { + define_api_type! { + pub type AxTimeValue; + } + + define_api! { + /// Returns the current clock time. + pub fn ax_current_time() -> AxTimeValue; + } +} + +/// Memory management. +pub mod mem { + use core::{alloc::Layout, ptr::NonNull}; + + define_api! { + @cfg "alloc"; + /// Allocate a continuous memory blocks with the given `layout` in + /// the global allocator. + /// + /// Returns [`None`] if the allocation fails. + pub fn ax_alloc(layout: Layout) -> Option>; + /// Deallocate the memory block at the given `ptr` pointer with the given + /// `layout`, which should be allocated by [`ax_alloc`]. + pub fn ax_dealloc(ptr: NonNull, layout: Layout); + } +} + +/// Standard input and output. +pub mod stdio { + use core::fmt; + define_api! { + /// Reads a byte from the console, or returns [`None`] if no input is available. + pub fn ax_console_read_byte() -> Option; + /// Writes a slice of bytes to the console, returns the number of bytes written. + pub fn ax_console_write_bytes(buf: &[u8]) -> crate::AxResult; + /// Writes a formatted string to the console. + pub fn ax_console_write_fmt(args: fmt::Arguments) -> fmt::Result; + } +} + +/// Multi-threading management. +pub mod task { + define_api_type! { + @cfg "multitask"; + pub type AxTaskHandle; + pub type AxWaitQueueHandle; + } + + define_api! { + /// Current task is going to sleep, it will be woken up at the given deadline. + /// + /// If the feature `multitask` is not enabled, it uses busy-wait instead + pub fn ax_sleep_until(deadline: crate::time::AxTimeValue); + + /// Current task gives up the CPU time voluntarily, and switches to another + /// ready task. + /// + /// If the feature `multitask` is not enabled, it does nothing. + pub fn ax_yield_now(); + + /// Exits the current task with the given exit code. + pub fn ax_exit(exit_code: i32) -> !; + } + + define_api! { + @cfg "multitask"; + + /// Returns the current task's ID. + pub fn ax_current_task_id() -> u64; + /// Spawns a new task with the given entry point and other arguments. + pub fn ax_spawn( + f: impl FnOnce() + Send + 'static, + name: alloc::string::String, + stack_size: usize + ) -> AxTaskHandle; + /// Waits for the given task to exit, and returns its exit code (the + /// argument of [`ax_exit`]). + pub fn ax_wait_for_exit(task: AxTaskHandle) -> Option; + /// Sets the priority of the current task. + pub fn ax_set_current_priority(prio: isize) -> crate::AxResult; + + /// Blocks the current task and put it into the wait queue, until the + /// given condition becomes true, or the the given duration has elapsed + /// (if specified). + pub fn ax_wait_queue_wait( + wq: &AxWaitQueueHandle, + until_condition: impl Fn() -> bool, + timeout: Option, + ) -> bool; + /// Wakes up one or more tasks in the wait queue. + /// + /// The maximum number of tasks to wake up is specified by `count`. If + /// `count` is `u32::MAX`, it will wake up all tasks in the wait queue. + pub fn ax_wait_queue_wake(wq: &AxWaitQueueHandle, count: u32); + } +} + +/// Filesystem manipulation operations. +pub mod fs { + use crate::AxResult; + + define_api_type! { + @cfg "fs"; + pub type AxFileHandle; + pub type AxDirHandle; + pub type AxOpenOptions; + pub type AxFileAttr; + pub type AxFileType; + pub type AxFilePerm; + pub type AxDirEntry; + pub type AxSeekFrom; + #[cfg(feature = "myfs")] + pub type AxDisk; + #[cfg(feature = "myfs")] + pub type MyFileSystemIf; + } + + define_api! { + @cfg "fs"; + + /// Opens a file at the path relative to the current directory with the + /// options specified by `opts`. + pub fn ax_open_file(path: &str, opts: &AxOpenOptions) -> AxResult; + /// Opens a directory at the path relative to the current directory with + /// the options specified by `opts`. + pub fn ax_open_dir(path: &str, opts: &AxOpenOptions) -> AxResult; + + /// Reads the file at the current position, returns the number of bytes read. + /// + /// After the read, the cursor will be advanced by the number of bytes read. + pub fn ax_read_file(file: &mut AxFileHandle, buf: &mut [u8]) -> AxResult; + /// Reads the file at the given position, returns the number of bytes read. + /// + /// It does not update the file cursor. + pub fn ax_read_file_at(file: &AxFileHandle, offset: u64, buf: &mut [u8]) -> AxResult; + /// Writes the file at the current position, returns the number of bytes + /// written. + /// + /// After the write, the cursor will be advanced by the number of bytes + /// written. + pub fn ax_write_file(file: &mut AxFileHandle, buf: &[u8]) -> AxResult; + /// Writes the file at the given position, returns the number of bytes + /// written. + /// + /// It does not update the file cursor. + pub fn ax_write_file_at(file: &AxFileHandle, offset: u64, buf: &[u8]) -> AxResult; + /// Truncates the file to the specified size. + pub fn ax_truncate_file(file: &AxFileHandle, size: u64) -> AxResult; + /// Flushes the file, writes all buffered data to the underlying device. + pub fn ax_flush_file(file: &AxFileHandle) -> AxResult; + /// Sets the cursor of the file to the specified offset. Returns the new + /// position after the seek. + pub fn ax_seek_file(file: &mut AxFileHandle, pos: AxSeekFrom) -> AxResult; + /// Returns attributes of the file. + pub fn ax_file_attr(file: &AxFileHandle) -> AxResult; + + /// Reads directory entries starts from the current position into the + /// given buffer, returns the number of entries read. + /// + /// After the read, the cursor of the directory will be advanced by the + /// number of entries read. + pub fn ax_read_dir(dir: &mut AxDirHandle, dirents: &mut [AxDirEntry]) -> AxResult; + /// Creates a new, empty directory at the provided path. + pub fn ax_create_dir(path: &str) -> AxResult; + /// Removes an empty directory. + /// + /// If the directory is not empty, it will return an error. + pub fn ax_remove_dir(path: &str) -> AxResult; + /// Removes a file from the filesystem. + pub fn ax_remove_file(path: &str) -> AxResult; + /// Rename a file or directory to a new name. + /// + /// It will delete the original file if `old` already exists. + pub fn ax_rename(old: &str, new: &str) -> AxResult; + + /// Returns the current working directory. + pub fn ax_current_dir() -> AxResult; + /// Changes the current working directory to the specified path. + pub fn ax_set_current_dir(path: &str) -> AxResult; + } +} + +/// Networking primitives for TCP/UDP communication. +pub mod net { + use crate::{io::AxPollState, AxResult}; + use core::net::{IpAddr, SocketAddr}; + + define_api_type! { + @cfg "net"; + pub type AxTcpSocketHandle; + pub type AxUdpSocketHandle; + } + + define_api! { + @cfg "net"; + + // TCP socket + + /// Creates a new TCP socket. + pub fn ax_tcp_socket() -> AxTcpSocketHandle; + /// Returns the local address and port of the TCP socket. + pub fn ax_tcp_socket_addr(socket: &AxTcpSocketHandle) -> AxResult; + /// Returns the remote address and port of the TCP socket. + pub fn ax_tcp_peer_addr(socket: &AxTcpSocketHandle) -> AxResult; + /// Moves this TCP socket into or out of nonblocking mode. + pub fn ax_tcp_set_nonblocking(socket: &AxTcpSocketHandle, nonblocking: bool) -> AxResult; + + /// Connects the TCP socket to the given address and port. + pub fn ax_tcp_connect(handle: &AxTcpSocketHandle, addr: SocketAddr) -> AxResult; + /// Binds the TCP socket to the given address and port. + pub fn ax_tcp_bind(socket: &AxTcpSocketHandle, addr: SocketAddr) -> AxResult; + /// Starts listening on the bound address and port. + pub fn ax_tcp_listen(socket: &AxTcpSocketHandle, _backlog: usize) -> AxResult; + /// Accepts a new connection on the TCP socket. + /// + /// This function will block the calling thread until a new TCP connection + /// is established. When established, a new TCP socket is returned. + pub fn ax_tcp_accept(socket: &AxTcpSocketHandle) -> AxResult<(AxTcpSocketHandle, SocketAddr)>; + + /// Transmits data in the given buffer on the TCP socket. + pub fn ax_tcp_send(socket: &AxTcpSocketHandle, buf: &[u8]) -> AxResult; + /// Receives data on the TCP socket, and stores it in the given buffer. + /// On success, returns the number of bytes read. + pub fn ax_tcp_recv(socket: &AxTcpSocketHandle, buf: &mut [u8]) -> AxResult; + /// Returns whether the TCP socket is readable or writable. + pub fn ax_tcp_poll(socket: &AxTcpSocketHandle) -> AxResult; + /// Closes the connection on the TCP socket. + pub fn ax_tcp_shutdown(socket: &AxTcpSocketHandle) -> AxResult; + + // UDP socket + + /// Creates a new UDP socket. + pub fn ax_udp_socket() -> AxUdpSocketHandle; + /// Returns the local address and port of the UDP socket. + pub fn ax_udp_socket_addr(socket: &AxUdpSocketHandle) -> AxResult; + /// Returns the remote address and port of the UDP socket. + pub fn ax_udp_peer_addr(socket: &AxUdpSocketHandle) -> AxResult; + /// Moves this UDP socket into or out of nonblocking mode. + pub fn ax_udp_set_nonblocking(socket: &AxUdpSocketHandle, nonblocking: bool) -> AxResult; + + /// Binds the UDP socket to the given address and port. + pub fn ax_udp_bind(socket: &AxUdpSocketHandle, addr: SocketAddr) -> AxResult; + /// Receives a single datagram message on the UDP socket. + pub fn ax_udp_recv_from(socket: &AxUdpSocketHandle, buf: &mut [u8]) -> AxResult<(usize, SocketAddr)>; + /// Receives a single datagram message on the UDP socket, without + /// removing it from the queue. + pub fn ax_udp_peek_from(socket: &AxUdpSocketHandle, buf: &mut [u8]) -> AxResult<(usize, SocketAddr)>; + /// Sends data on the UDP socket to the given address. On success, + /// returns the number of bytes written. + pub fn ax_udp_send_to(socket: &AxUdpSocketHandle, buf: &[u8], addr: SocketAddr) -> AxResult; + + /// Connects this UDP socket to a remote address, allowing the `send` and + /// `recv` to be used to send data and also applies filters to only receive + /// data from the specified address. + pub fn ax_udp_connect(socket: &AxUdpSocketHandle, addr: SocketAddr) -> AxResult; + /// Sends data on the UDP socket to the remote address to which it is + /// connected. + pub fn ax_udp_send(socket: &AxUdpSocketHandle, buf: &[u8]) -> AxResult; + /// Receives a single datagram message on the UDP socket from the remote + /// address to which it is connected. On success, returns the number of + /// bytes read. + pub fn ax_udp_recv(socket: &AxUdpSocketHandle, buf: &mut [u8]) -> AxResult; + /// Returns whether the UDP socket is readable or writable. + pub fn ax_udp_poll(socket: &AxUdpSocketHandle) -> AxResult; + + // Miscellaneous + + /// Resolves the host name to a list of IP addresses. + pub fn ax_dns_query(domain_name: &str) -> AxResult>; + /// Poll the network stack. + /// + /// It may receive packets from the NIC and process them, and transmit queued + /// packets to the NIC. + pub fn ax_poll_interfaces() -> AxResult; + } +} + +/// Graphics manipulation operations. +pub mod display { + define_api_type! { + @cfg "display"; + pub type AxDisplayInfo; + } + + define_api! { + @cfg "display"; + /// Gets the framebuffer information. + pub fn ax_framebuffer_info() -> AxDisplayInfo; + /// Flushes the framebuffer, i.e. show on the screen. + pub fn ax_framebuffer_flush(); + } +} + +/// Input/output operations. +pub mod io { + define_api_type! { + pub type AxPollState; + } +} diff --git a/api/arceos_api/src/macros.rs b/api/arceos_api/src/macros.rs new file mode 100644 index 0000000..09ab1e2 --- /dev/null +++ b/api/arceos_api/src/macros.rs @@ -0,0 +1,79 @@ +#![allow(unused_macros)] + +macro_rules! define_api_type { + ($( $(#[$attr:meta])* $vis:vis type $name:ident; )+) => { + $( + $vis use $crate::imp::$name; + )+ + }; + ( @cfg $feature:literal; $( $(#[$attr:meta])* $vis:vis type $name:ident; )+ ) => { + $( + #[cfg(feature = $feature)] + $(#[$attr])* + $vis use $crate::imp::$name; + + #[cfg(all(feature = "dummy-if-not-enabled", not(feature = $feature)))] + $(#[$attr])* + $vis struct $name; + )+ + }; +} + +macro_rules! define_api { + ($( $(#[$attr:meta])* $vis:vis fn $name:ident( $($arg:ident : $type:ty),* $(,)? ) $( -> $ret:ty )? ; )+) => { + $( + $(#[$attr])* + $vis fn $name( $($arg : $type),* ) $( -> $ret )? { + $crate::imp::$name( $($arg),* ) + } + )+ + }; + ( + @cfg $feature:literal; + $( $(#[$attr:meta])* $vis:vis fn $name:ident( $($arg:ident : $type:ty),* $(,)? ) $( -> $ret:ty )? ; )+ + ) => { + $( + #[cfg(feature = $feature)] + $(#[$attr])* + $vis fn $name( $($arg : $type),* ) $( -> $ret )? { + $crate::imp::$name( $($arg),* ) + } + + #[allow(unused_variables)] + #[cfg(all(feature = "dummy-if-not-enabled", not(feature = $feature)))] + $(#[$attr])* + $vis fn $name( $($arg : $type),* ) $( -> $ret )? { + unimplemented!(stringify!($name)) + } + )+ + }; +} + +macro_rules! _cfg_common { + ( $feature:literal $($item:item)* ) => { + $( + #[cfg(feature = $feature)] + $item + )* + } +} + +macro_rules! cfg_alloc { + ($($item:item)*) => { _cfg_common!{ "alloc" $($item)* } } +} + +macro_rules! cfg_fs { + ($($item:item)*) => { _cfg_common!{ "fs" $($item)* } } +} + +macro_rules! cfg_net { + ($($item:item)*) => { _cfg_common!{ "net" $($item)* } } +} + +macro_rules! cfg_display { + ($($item:item)*) => { _cfg_common!{ "display" $($item)* } } +} + +macro_rules! cfg_task { + ($($item:item)*) => { _cfg_common!{ "multitask" $($item)* } } +} diff --git a/api/arceos_posix_api/.gitignore b/api/arceos_posix_api/.gitignore new file mode 100644 index 0000000..e362811 --- /dev/null +++ b/api/arceos_posix_api/.gitignore @@ -0,0 +1,16 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +ctypes_gen.rs diff --git a/api/arceos_posix_api/Cargo.toml b/api/arceos_posix_api/Cargo.toml new file mode 100644 index 0000000..6e6d99f --- /dev/null +++ b/api/arceos_posix_api/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "arceos_posix_api" +version = "0.1.0" +edition = "2021" +authors = [ + "Yuekai Jia ", + "yanjuguang ", + "wudashuai ", + "yfblock <321353225@qq.com>", + "scPointer ", + "Shiping Yuan ", +] +description = "POSIX-compatible APIs for ArceOS modules" +license = "GPL-3.0-or-later OR Apache-2.0" +repository = "https://github.com/rcore-os/arceos/tree/main/api/arceos_posix_api" +keywords = ["Starry"] + +[features] +default = [] + +smp = ["axfeat/smp"] +irq = ["axfeat/irq"] +alloc = ["dep:axalloc", "axfeat/alloc"] +multitask = ["axtask/multitask", "axfeat/multitask", "axsync/multitask"] +fd = ["alloc"] +fs = ["dep:axfs", "axfeat/fs", "fd"] +net = ["dep:axnet", "axfeat/net", "fd"] +pipe = ["fd"] +select = ["fd"] +epoll = ["fd"] + +[dependencies] +# ArceOS modules +axfeat = { workspace = true } +axruntime = { workspace = true } +axconfig = { workspace = true } +axlog = { workspace = true } +axhal = { workspace = true } +axsync = { workspace = true } +axalloc = { workspace = true, optional = true } +axtask = { workspace = true, optional = true } +axfs = { workspace = true, optional = true } +axnet = { workspace = true, optional = true } + +# Other crates +axio = { git = "https://github.com/Starry-OS/axio.git" } +axerrno = { git = "https://github.com/Starry-OS/axerrno.git" } +static_assertions = "1.1.0" +spin = { version = "0.9" } +lazy_static = { version = "1.5", features = ["spin_no_std"] } +flatten_objects = { git = "https://github.com/Starry-OS/flatten_objects.git" } + +[build-dependencies] +bindgen ={ version = "0.69" } diff --git a/api/arceos_posix_api/build.rs b/api/arceos_posix_api/build.rs new file mode 100644 index 0000000..3cf0e8c --- /dev/null +++ b/api/arceos_posix_api/build.rs @@ -0,0 +1,106 @@ +fn main() { + use std::io::Write; + + fn gen_pthread_mutex(out_file: &str) -> std::io::Result<()> { + // TODO: generate size and initial content automatically. + let (mutex_size, mutex_init) = if cfg!(feature = "multitask") { + if cfg!(feature = "smp") { + (3, "{0, 0, 0}") // core::mem::transmute::<_, [usize; 2]>(axsync::Mutex::new(())) + } else { + (2, "{0, 0}") // core::mem::transmute::<_, [usize; 3]>(axsync::Mutex::new(())) + } + } else { + (1, "{0}") + }; + + let mut output = Vec::new(); + writeln!( + output, + "// Generated by arceos_posix_api/build.rs, DO NOT edit!" + )?; + writeln!( + output, + r#" +typedef struct {{ + long __l[{mutex_size}]; +}} pthread_mutex_t; + +#define PTHREAD_MUTEX_INITIALIZER {{ .__l = {mutex_init}}} +"# + )?; + std::fs::write(out_file, output)?; + Ok(()) + } + + fn gen_c_to_rust_bindings(in_file: &str, out_file: &str) { + println!("cargo:rerun-if-changed={in_file}"); + + let allow_types = [ + "stat", + "size_t", + "ssize_t", + "off_t", + "mode_t", + "sock.*", + "fd_set", + "timeval", + "pthread_t", + "pthread_attr_t", + "pthread_mutex_t", + "pthread_mutexattr_t", + "epoll_event", + "iovec", + "clockid_t", + "rlimit", + "aibuf", + ]; + let allow_vars = [ + "O_.*", + "AF_.*", + "SOCK_.*", + "IPPROTO_.*", + "FD_.*", + "F_.*", + "_SC_.*", + "EPOLL_CTL_.*", + "EPOLL.*", + "RLIMIT_.*", + "EAI_.*", + "MAXADDRS", + ]; + + #[derive(Debug)] + struct MyCallbacks; + + impl bindgen::callbacks::ParseCallbacks for MyCallbacks { + fn include_file(&self, fname: &str) { + if !fname.contains("ax_pthread_mutex.h") { + println!("cargo:rerun-if-changed={}", fname); + } + } + } + + let mut builder = bindgen::Builder::default() + .header(in_file) + .clang_arg("-I./../../ulib/axlibc/include") + .parse_callbacks(Box::new(MyCallbacks)) + .derive_default(true) + .size_t_is_usize(false) + .use_core(); + for ty in allow_types { + builder = builder.allowlist_type(ty); + } + for var in allow_vars { + builder = builder.allowlist_var(var); + } + + builder + .generate() + .expect("Unable to generate c->rust bindings") + .write_to_file(out_file) + .expect("Couldn't write bindings!"); + } + + gen_pthread_mutex("../../ulib/axlibc/include/ax_pthread_mutex.h").unwrap(); + gen_c_to_rust_bindings("ctypes.h", "src/ctypes_gen.rs"); +} diff --git a/api/arceos_posix_api/ctypes.h b/api/arceos_posix_api/ctypes.h new file mode 100644 index 0000000..7d745f8 --- /dev/null +++ b/api/arceos_posix_api/ctypes.h @@ -0,0 +1,14 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/api/arceos_posix_api/src/imp/fd_ops.rs b/api/arceos_posix_api/src/imp/fd_ops.rs new file mode 100644 index 0000000..69ee4b4 --- /dev/null +++ b/api/arceos_posix_api/src/imp/fd_ops.rs @@ -0,0 +1,161 @@ +use alloc::sync::Arc; +use core::ffi::c_int; + +use axerrno::{LinuxError, LinuxResult}; +use axio::PollState; +use flatten_objects::FlattenObjects; +use spin::RwLock; + +use super::stdio::{stdin, stdout}; +use crate::ctypes; + +pub const AX_FILE_LIMIT: usize = 1024; + +pub trait FileLike: Send + Sync { + fn read(&self, buf: &mut [u8]) -> LinuxResult; + fn write(&self, buf: &[u8]) -> LinuxResult; + fn stat(&self) -> LinuxResult; + fn into_any(self: Arc) -> Arc; + fn poll(&self) -> LinuxResult; + fn set_nonblocking(&self, nonblocking: bool) -> LinuxResult; +} + +lazy_static::lazy_static! { + static ref FD_TABLE: RwLock, AX_FILE_LIMIT>> = { + let mut fd_table = FlattenObjects::new(); + fd_table.add_at(0, Arc::new(stdin()) as _).unwrap(); // stdin + fd_table.add_at(1, Arc::new(stdout()) as _).unwrap(); // stdout + fd_table.add_at(2, Arc::new(stdout()) as _).unwrap(); // stderr + RwLock::new(fd_table) + }; +} + +pub fn get_file_like(fd: c_int) -> LinuxResult> { + FD_TABLE + .read() + .get(fd as usize) + .cloned() + .ok_or(LinuxError::EBADF) +} + +pub fn add_file_like(f: Arc) -> LinuxResult { + Ok(FD_TABLE.write().add(f).ok_or(LinuxError::EMFILE)? as c_int) +} + +pub fn close_file_like(fd: c_int) -> LinuxResult { + let f = FD_TABLE + .write() + .remove(fd as usize) + .ok_or(LinuxError::EBADF)?; + drop(f); + Ok(()) +} + +/// Close a file by `fd`. +pub fn sys_close(fd: c_int) -> c_int { + debug!("sys_close <= {}", fd); + if (0..=2).contains(&fd) { + return 0; // stdin, stdout, stderr + } + syscall_body!(sys_close, close_file_like(fd).map(|_| 0)) +} + +fn dup_fd(old_fd: c_int) -> LinuxResult { + let f = get_file_like(old_fd)?; + let new_fd = add_file_like(f)?; + Ok(new_fd) +} + +/// Duplicate a file descriptor. +pub fn sys_dup(old_fd: c_int) -> c_int { + debug!("sys_dup <= {}", old_fd); + syscall_body!(sys_dup, dup_fd(old_fd)) +} + +/// Duplicate a file descriptor, but it uses the file descriptor number specified in `new_fd`. +/// +/// TODO: `dup2` should forcibly close new_fd if it is already opened. +pub fn sys_dup2(old_fd: c_int, new_fd: c_int) -> c_int { + debug!("sys_dup2 <= old_fd: {}, new_fd: {}", old_fd, new_fd); + syscall_body!(sys_dup2, { + if old_fd == new_fd { + let r = sys_fcntl(old_fd, ctypes::F_GETFD as _, 0); + if r >= 0 { + return Ok(old_fd); + } else { + return Ok(r); + } + } + if new_fd as usize >= AX_FILE_LIMIT { + return Err(LinuxError::EBADF); + } + + let f = get_file_like(old_fd)?; + FD_TABLE + .write() + .add_at(new_fd as usize, f) + .ok_or(LinuxError::EMFILE)?; + + Ok(new_fd) + }) +} + +/// Duplicate a file descriptor. +pub fn sys_dup3(old_fd: c_int, new_fd: c_int, flags: c_int) -> c_int { + debug!( + "sys_dup3 <= old_fd: {}, new_fd: {}, flags: {}", + old_fd, new_fd, flags + ); + syscall_body!(sys_dup3, { + if old_fd == new_fd { + let r = sys_fcntl(old_fd, ctypes::F_GETFD as _, 0); + if r >= 0 { + return Ok(old_fd); + } else { + return Ok(r); + } + } + if new_fd as usize >= AX_FILE_LIMIT { + return Err(LinuxError::EBADF); + } + + let f = get_file_like(old_fd)?; + FD_TABLE + .write() + .add_at(new_fd as usize, f) + .ok_or(LinuxError::EMFILE)?; + + if (flags & (ctypes::O_CLOEXEC as c_int)) != 0 { + let _ = sys_fcntl(new_fd, ctypes::F_SETFD as _, ctypes::FD_CLOEXEC as _); + } + + Ok(new_fd) + }) +} + +/// Manipulate file descriptor. +/// +/// TODO: `SET/GET` command is ignored, hard-code stdin/stdout +pub fn sys_fcntl(fd: c_int, cmd: c_int, arg: usize) -> c_int { + debug!("sys_fcntl <= fd: {} cmd: {} arg: {}", fd, cmd, arg); + syscall_body!(sys_fcntl, { + match cmd as u32 { + ctypes::F_DUPFD => dup_fd(fd), + ctypes::F_DUPFD_CLOEXEC => { + // TODO: Change fd flags + dup_fd(fd) + } + ctypes::F_SETFL => { + if fd == 0 || fd == 1 || fd == 2 { + return Ok(0); + } + get_file_like(fd)?.set_nonblocking(arg & (ctypes::O_NONBLOCK as usize) > 0)?; + Ok(0) + } + _ => { + warn!("unsupported fcntl parameters: cmd {}", cmd); + Ok(0) + } + } + }) +} diff --git a/api/arceos_posix_api/src/imp/fs.rs b/api/arceos_posix_api/src/imp/fs.rs new file mode 100644 index 0000000..6c92f2d --- /dev/null +++ b/api/arceos_posix_api/src/imp/fs.rs @@ -0,0 +1,217 @@ +use alloc::sync::Arc; +use core::ffi::{c_char, c_int}; + +use axerrno::{LinuxError, LinuxResult}; +use axfs::fops::OpenOptions; +use axio::{PollState, SeekFrom}; +use axsync::Mutex; + +use super::fd_ops::{get_file_like, FileLike}; +use crate::{ctypes, utils::char_ptr_to_str}; + +pub struct File { + inner: Mutex, +} + +impl File { + fn new(inner: axfs::fops::File) -> Self { + Self { + inner: Mutex::new(inner), + } + } + + fn add_to_fd_table(self) -> LinuxResult { + super::fd_ops::add_file_like(Arc::new(self)) + } + + fn from_fd(fd: c_int) -> LinuxResult> { + let f = super::fd_ops::get_file_like(fd)?; + f.into_any() + .downcast::() + .map_err(|_| LinuxError::EINVAL) + } +} + +impl FileLike for File { + fn read(&self, buf: &mut [u8]) -> LinuxResult { + Ok(self.inner.lock().read(buf)?) + } + + fn write(&self, buf: &[u8]) -> LinuxResult { + Ok(self.inner.lock().write(buf)?) + } + + fn stat(&self) -> LinuxResult { + let metadata = self.inner.lock().get_attr()?; + let ty = metadata.file_type() as u8; + let perm = metadata.perm().bits() as u32; + let st_mode = ((ty as u32) << 12) | perm; + Ok(ctypes::stat { + st_ino: 1, + st_nlink: 1, + st_mode, + st_uid: 1000, + st_gid: 1000, + st_size: metadata.size() as _, + st_blocks: metadata.blocks() as _, + st_blksize: 512, + ..Default::default() + }) + } + + fn into_any(self: Arc) -> Arc { + self + } + + fn poll(&self) -> LinuxResult { + Ok(PollState { + readable: true, + writable: true, + }) + } + + fn set_nonblocking(&self, _nonblocking: bool) -> LinuxResult { + Ok(()) + } +} + +/// Convert open flags to [`OpenOptions`]. +fn flags_to_options(flags: c_int, _mode: ctypes::mode_t) -> OpenOptions { + let flags = flags as u32; + let mut options = OpenOptions::new(); + match flags & 0b11 { + ctypes::O_RDONLY => options.read(true), + ctypes::O_WRONLY => options.write(true), + _ => { + options.read(true); + options.write(true); + } + }; + if flags & ctypes::O_APPEND != 0 { + options.append(true); + } + if flags & ctypes::O_TRUNC != 0 { + options.truncate(true); + } + if flags & ctypes::O_CREAT != 0 { + options.create(true); + } + if flags & ctypes::O_EXEC != 0 { + options.create_new(true); + } + options +} + +/// Open a file by `filename` and insert it into the file descriptor table. +/// +/// Return its index in the file table (`fd`). Return `EMFILE` if it already +/// has the maximum number of files open. +pub fn sys_open(filename: *const c_char, flags: c_int, mode: ctypes::mode_t) -> c_int { + let filename = char_ptr_to_str(filename); + debug!("sys_open <= {:?} {:#o} {:#o}", filename, flags, mode); + syscall_body!(sys_open, { + let options = flags_to_options(flags, mode); + let file = axfs::fops::File::open(filename?, &options)?; + File::new(file).add_to_fd_table() + }) +} + +/// Set the position of the file indicated by `fd`. +/// +/// Return its position after seek. +pub fn sys_lseek(fd: c_int, offset: ctypes::off_t, whence: c_int) -> ctypes::off_t { + debug!("sys_lseek <= {} {} {}", fd, offset, whence); + syscall_body!(sys_lseek, { + let pos = match whence { + 0 => SeekFrom::Start(offset as _), + 1 => SeekFrom::Current(offset as _), + 2 => SeekFrom::End(offset as _), + _ => return Err(LinuxError::EINVAL), + }; + let off = File::from_fd(fd)?.inner.lock().seek(pos)?; + Ok(off) + }) +} + +/// Get the file metadata by `path` and write into `buf`. +/// +/// Return 0 if success. +pub unsafe fn sys_stat(path: *const c_char, buf: *mut ctypes::stat) -> c_int { + let path = char_ptr_to_str(path); + debug!("sys_stat <= {:?} {:#x}", path, buf as usize); + syscall_body!(sys_stat, { + if buf.is_null() { + return Err(LinuxError::EFAULT); + } + let mut options = OpenOptions::new(); + options.read(true); + let file = axfs::fops::File::open(path?, &options)?; + let st = File::new(file).stat()?; + unsafe { *buf = st }; + Ok(0) + }) +} + +/// Get file metadata by `fd` and write into `buf`. +/// +/// Return 0 if success. +pub unsafe fn sys_fstat(fd: c_int, buf: *mut ctypes::stat) -> c_int { + debug!("sys_fstat <= {} {:#x}", fd, buf as usize); + syscall_body!(sys_fstat, { + if buf.is_null() { + return Err(LinuxError::EFAULT); + } + + unsafe { *buf = get_file_like(fd)?.stat()? }; + Ok(0) + }) +} + +/// Get the metadata of the symbolic link and write into `buf`. +/// +/// Return 0 if success. +pub unsafe fn sys_lstat(path: *const c_char, buf: *mut ctypes::stat) -> ctypes::ssize_t { + let path = char_ptr_to_str(path); + debug!("sys_lstat <= {:?} {:#x}", path, buf as usize); + syscall_body!(sys_lstat, { + if buf.is_null() { + return Err(LinuxError::EFAULT); + } + unsafe { *buf = Default::default() }; // TODO + Ok(0) + }) +} + +/// Get the path of the current directory. +pub fn sys_getcwd(buf: *mut c_char, size: usize) -> *mut c_char { + debug!("sys_getcwd <= {:#x} {}", buf as usize, size); + syscall_body!(sys_getcwd, { + if buf.is_null() { + return Ok(core::ptr::null::() as _); + } + let dst = unsafe { core::slice::from_raw_parts_mut(buf as *mut u8, size as _) }; + let cwd = axfs::api::current_dir()?; + let cwd = cwd.as_bytes(); + if cwd.len() < size { + dst[..cwd.len()].copy_from_slice(cwd); + dst[cwd.len()] = 0; + Ok(buf) + } else { + Err(LinuxError::ERANGE) + } + }) +} + +/// Rename `old` to `new` +/// If new exists, it is first removed. +/// +/// Return 0 if the operation succeeds, otherwise return -1. +pub fn sys_rename(old: *const c_char, new: *const c_char) -> c_int { + syscall_body!(sys_rename, { + let old_path = char_ptr_to_str(old)?; + let new_path = char_ptr_to_str(new)?; + debug!("sys_rename <= old: {:?}, new: {:?}", old_path, new_path); + axfs::api::rename(old_path, new_path)?; + Ok(0) + }) +} diff --git a/api/arceos_posix_api/src/imp/io.rs b/api/arceos_posix_api/src/imp/io.rs new file mode 100644 index 0000000..269ce20 --- /dev/null +++ b/api/arceos_posix_api/src/imp/io.rs @@ -0,0 +1,72 @@ +use crate::ctypes; +use axerrno::LinuxError; +use core::ffi::{c_int, c_void}; + +#[cfg(feature = "fd")] +use crate::imp::fd_ops::get_file_like; +#[cfg(not(feature = "fd"))] +use axio::prelude::*; + +/// Read data from the file indicated by `fd`. +/// +/// Return the read size if success. +pub fn sys_read(fd: c_int, buf: *mut c_void, count: usize) -> ctypes::ssize_t { + debug!("sys_read <= {} {:#x} {}", fd, buf as usize, count); + syscall_body!(sys_read, { + if buf.is_null() { + return Err(LinuxError::EFAULT); + } + let dst = unsafe { core::slice::from_raw_parts_mut(buf as *mut u8, count) }; + #[cfg(feature = "fd")] + { + Ok(get_file_like(fd)?.read(dst)? as ctypes::ssize_t) + } + #[cfg(not(feature = "fd"))] + match fd { + 0 => Ok(super::stdio::stdin().read(dst)? as ctypes::ssize_t), + 1 | 2 => Err(LinuxError::EPERM), + _ => Err(LinuxError::EBADF), + } + }) +} + +/// Write data to the file indicated by `fd`. +/// +/// Return the written size if success. +pub fn sys_write(fd: c_int, buf: *const c_void, count: usize) -> ctypes::ssize_t { + debug!("sys_write <= {} {:#x} {}", fd, buf as usize, count); + syscall_body!(sys_write, { + if buf.is_null() { + return Err(LinuxError::EFAULT); + } + let src = unsafe { core::slice::from_raw_parts(buf as *const u8, count) }; + #[cfg(feature = "fd")] + { + Ok(get_file_like(fd)?.write(src)? as ctypes::ssize_t) + } + #[cfg(not(feature = "fd"))] + match fd { + 0 => Err(LinuxError::EPERM), + 1 | 2 => Ok(super::stdio::stdout().write(src)? as ctypes::ssize_t), + _ => Err(LinuxError::EBADF), + } + }) +} + +/// Write a vector. +pub unsafe fn sys_writev(fd: c_int, iov: *const ctypes::iovec, iocnt: c_int) -> ctypes::ssize_t { + debug!("sys_writev <= fd: {}", fd); + syscall_body!(sys_writev, { + if !(0..=1024).contains(&iocnt) { + return Err(LinuxError::EINVAL); + } + + let iovs = unsafe { core::slice::from_raw_parts(iov, iocnt as usize) }; + let mut ret = 0; + for iov in iovs.iter() { + ret += sys_write(fd, iov.iov_base, iov.iov_len); + } + + Ok(ret) + }) +} diff --git a/api/arceos_posix_api/src/imp/io_mpx/epoll.rs b/api/arceos_posix_api/src/imp/io_mpx/epoll.rs new file mode 100644 index 0000000..5404b2f --- /dev/null +++ b/api/arceos_posix_api/src/imp/io_mpx/epoll.rs @@ -0,0 +1,205 @@ +//! `epoll` implementation. +//! +//! TODO: do not support `EPOLLET` flag + +use alloc::collections::btree_map::Entry; +use alloc::collections::BTreeMap; +use alloc::sync::Arc; +use core::{ffi::c_int, time::Duration}; + +use axerrno::{LinuxError, LinuxResult}; +use axhal::time::current_time; +use axsync::Mutex; + +use crate::ctypes; +use crate::imp::fd_ops::{add_file_like, get_file_like, FileLike}; + +pub struct EpollInstance { + events: Mutex>, +} + +unsafe impl Send for ctypes::epoll_event {} +unsafe impl Sync for ctypes::epoll_event {} + +impl EpollInstance { + // TODO: parse flags + pub fn new(_flags: usize) -> Self { + Self { + events: Mutex::new(BTreeMap::new()), + } + } + + fn from_fd(fd: c_int) -> LinuxResult> { + get_file_like(fd)? + .into_any() + .downcast::() + .map_err(|_| LinuxError::EINVAL) + } + + fn control(&self, op: usize, fd: usize, event: &ctypes::epoll_event) -> LinuxResult { + match get_file_like(fd as c_int) { + Ok(_) => {} + Err(e) => return Err(e), + } + + match op as u32 { + ctypes::EPOLL_CTL_ADD => { + if let Entry::Vacant(e) = self.events.lock().entry(fd) { + e.insert(*event); + } else { + return Err(LinuxError::EEXIST); + } + } + ctypes::EPOLL_CTL_MOD => { + let mut events = self.events.lock(); + if let Entry::Occupied(mut ocp) = events.entry(fd) { + ocp.insert(*event); + } else { + return Err(LinuxError::ENOENT); + } + } + ctypes::EPOLL_CTL_DEL => { + let mut events = self.events.lock(); + if let Entry::Occupied(ocp) = events.entry(fd) { + ocp.remove_entry(); + } else { + return Err(LinuxError::ENOENT); + } + } + _ => { + return Err(LinuxError::EINVAL); + } + } + Ok(0) + } + + fn poll_all(&self, events: &mut [ctypes::epoll_event]) -> LinuxResult { + let ready_list = self.events.lock(); + let mut events_num = 0; + + for (infd, ev) in ready_list.iter() { + match get_file_like(*infd as c_int)?.poll() { + Err(_) => { + if (ev.events & ctypes::EPOLLERR) != 0 { + events[events_num].events = ctypes::EPOLLERR; + events[events_num].data = ev.data; + events_num += 1; + } + } + Ok(state) => { + if state.readable && (ev.events & ctypes::EPOLLIN != 0) { + events[events_num].events = ctypes::EPOLLIN; + events[events_num].data = ev.data; + events_num += 1; + } + + if state.writable && (ev.events & ctypes::EPOLLOUT != 0) { + events[events_num].events = ctypes::EPOLLOUT; + events[events_num].data = ev.data; + events_num += 1; + } + } + } + } + Ok(events_num) + } +} + +impl FileLike for EpollInstance { + fn read(&self, _buf: &mut [u8]) -> LinuxResult { + Err(LinuxError::ENOSYS) + } + + fn write(&self, _buf: &[u8]) -> LinuxResult { + Err(LinuxError::ENOSYS) + } + + fn stat(&self) -> LinuxResult { + let st_mode = 0o600u32; // rw------- + Ok(ctypes::stat { + st_ino: 1, + st_nlink: 1, + st_mode, + ..Default::default() + }) + } + + fn into_any(self: Arc) -> alloc::sync::Arc { + self + } + + fn poll(&self) -> LinuxResult { + Err(LinuxError::ENOSYS) + } + + fn set_nonblocking(&self, _nonblocking: bool) -> LinuxResult { + Ok(()) + } +} + +/// Creates a new epoll instance. +/// +/// It returns a file descriptor referring to the new epoll instance. +pub fn sys_epoll_create(size: c_int) -> c_int { + debug!("sys_epoll_create <= {}", size); + syscall_body!(sys_epoll_create, { + if size < 0 { + return Err(LinuxError::EINVAL); + } + let epoll_instance = EpollInstance::new(0); + add_file_like(Arc::new(epoll_instance)) + }) +} + +/// Control interface for an epoll file descriptor +pub unsafe fn sys_epoll_ctl( + epfd: c_int, + op: c_int, + fd: c_int, + event: *mut ctypes::epoll_event, +) -> c_int { + debug!("sys_epoll_ctl <= epfd: {} op: {} fd: {}", epfd, op, fd); + syscall_body!(sys_epoll_ctl, { + let ret = unsafe { + EpollInstance::from_fd(epfd)?.control(op as usize, fd as usize, &(*event))? as c_int + }; + Ok(ret) + }) +} + +/// Waits for events on the epoll instance referred to by the file descriptor epfd. +pub unsafe fn sys_epoll_wait( + epfd: c_int, + events: *mut ctypes::epoll_event, + maxevents: c_int, + timeout: c_int, +) -> c_int { + debug!( + "sys_epoll_wait <= epfd: {}, maxevents: {}, timeout: {}", + epfd, maxevents, timeout + ); + + syscall_body!(sys_epoll_wait, { + if maxevents <= 0 { + return Err(LinuxError::EINVAL); + } + let events = unsafe { core::slice::from_raw_parts_mut(events, maxevents as usize) }; + let deadline = (!timeout.is_negative()) + .then(|| current_time() + Duration::from_millis(timeout as u64)); + let epoll_instance = EpollInstance::from_fd(epfd)?; + loop { + #[cfg(feature = "net")] + axnet::poll_interfaces(); + let events_num = epoll_instance.poll_all(events)?; + if events_num > 0 { + return Ok(events_num as c_int); + } + + if deadline.map_or(false, |ddl| current_time() >= ddl) { + debug!(" timeout!"); + return Ok(0); + } + crate::sys_sched_yield(); + } + }) +} diff --git a/api/arceos_posix_api/src/imp/io_mpx/mod.rs b/api/arceos_posix_api/src/imp/io_mpx/mod.rs new file mode 100644 index 0000000..397ed44 --- /dev/null +++ b/api/arceos_posix_api/src/imp/io_mpx/mod.rs @@ -0,0 +1,16 @@ +//! I/O multiplexing: +//! +//! * [`select`](select::sys_select) +//! * [`epoll_create`](epoll::sys_epoll_create) +//! * [`epoll_ctl`](epoll::sys_epoll_ctl) +//! * [`epoll_wait`](epoll::sys_epoll_wait) + +#[cfg(feature = "epoll")] +mod epoll; +#[cfg(feature = "select")] +mod select; + +#[cfg(feature = "epoll")] +pub use self::epoll::{sys_epoll_create, sys_epoll_ctl, sys_epoll_wait}; +#[cfg(feature = "select")] +pub use self::select::sys_select; diff --git a/api/arceos_posix_api/src/imp/io_mpx/select.rs b/api/arceos_posix_api/src/imp/io_mpx/select.rs new file mode 100644 index 0000000..6986598 --- /dev/null +++ b/api/arceos_posix_api/src/imp/io_mpx/select.rs @@ -0,0 +1,165 @@ +use core::ffi::c_int; + +use axerrno::{LinuxError, LinuxResult}; +use axhal::time::current_time; + +use crate::{ctypes, imp::fd_ops::get_file_like}; + +const FD_SETSIZE: usize = 1024; +const BITS_PER_USIZE: usize = usize::BITS as usize; +const FD_SETSIZE_USIZES: usize = FD_SETSIZE.div_ceil(BITS_PER_USIZE); + +struct FdSets { + nfds: usize, + bits: [usize; FD_SETSIZE_USIZES * 3], +} + +impl FdSets { + fn from( + nfds: usize, + read_fds: *const ctypes::fd_set, + write_fds: *const ctypes::fd_set, + except_fds: *const ctypes::fd_set, + ) -> Self { + let nfds = nfds.min(FD_SETSIZE); + let nfds_usizes = nfds.div_ceil(BITS_PER_USIZE); + let mut bits = core::mem::MaybeUninit::<[usize; FD_SETSIZE_USIZES * 3]>::uninit(); + let bits_ptr = unsafe { core::mem::transmute(bits.as_mut_ptr()) }; + + let copy_from_fd_set = |bits_ptr: *mut usize, fds: *const ctypes::fd_set| unsafe { + let dst = core::slice::from_raw_parts_mut(bits_ptr, nfds_usizes); + if fds.is_null() { + dst.fill(0); + } else { + let fds_ptr = (*fds).fds_bits.as_ptr() as *const usize; + let src = core::slice::from_raw_parts(fds_ptr, nfds_usizes); + dst.copy_from_slice(src); + } + }; + + let bits = unsafe { + copy_from_fd_set(bits_ptr, read_fds); + copy_from_fd_set(bits_ptr.add(FD_SETSIZE_USIZES), write_fds); + copy_from_fd_set(bits_ptr.add(FD_SETSIZE_USIZES * 2), except_fds); + bits.assume_init() + }; + Self { nfds, bits } + } + + fn poll_all( + &self, + res_read_fds: *mut ctypes::fd_set, + res_write_fds: *mut ctypes::fd_set, + res_except_fds: *mut ctypes::fd_set, + ) -> LinuxResult { + let mut read_bits_ptr = self.bits.as_ptr(); + let mut write_bits_ptr = unsafe { read_bits_ptr.add(FD_SETSIZE_USIZES) }; + let mut execpt_bits_ptr = unsafe { read_bits_ptr.add(FD_SETSIZE_USIZES * 2) }; + let mut i = 0; + let mut res_num = 0; + while i < self.nfds { + let read_bits = unsafe { *read_bits_ptr }; + let write_bits = unsafe { *write_bits_ptr }; + let except_bits = unsafe { *execpt_bits_ptr }; + unsafe { + read_bits_ptr = read_bits_ptr.add(1); + write_bits_ptr = write_bits_ptr.add(1); + execpt_bits_ptr = execpt_bits_ptr.add(1); + } + + let all_bits = read_bits | write_bits | except_bits; + if all_bits == 0 { + i += BITS_PER_USIZE; + continue; + } + let mut j = 0; + while j < BITS_PER_USIZE && i + j < self.nfds { + let bit = 1 << j; + if all_bits & bit == 0 { + j += 1; + continue; + } + let fd = i + j; + match get_file_like(fd as _)?.poll() { + Ok(state) => { + if state.readable && read_bits & bit != 0 { + unsafe { set_fd_set(res_read_fds, fd) }; + res_num += 1; + } + if state.writable && write_bits & bit != 0 { + unsafe { set_fd_set(res_write_fds, fd) }; + res_num += 1; + } + } + Err(e) => { + debug!(" except: {} {:?}", fd, e); + if except_bits & bit != 0 { + unsafe { set_fd_set(res_except_fds, fd) }; + res_num += 1; + } + } + } + j += 1; + } + i += BITS_PER_USIZE; + } + Ok(res_num) + } +} + +/// Monitor multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation +pub unsafe fn sys_select( + nfds: c_int, + readfds: *mut ctypes::fd_set, + writefds: *mut ctypes::fd_set, + exceptfds: *mut ctypes::fd_set, + timeout: *mut ctypes::timeval, +) -> c_int { + debug!( + "sys_select <= {} {:#x} {:#x} {:#x}", + nfds, readfds as usize, writefds as usize, exceptfds as usize + ); + syscall_body!(sys_select, { + if nfds < 0 { + return Err(LinuxError::EINVAL); + } + let nfds = (nfds as usize).min(FD_SETSIZE); + let deadline = unsafe { timeout.as_ref().map(|t| current_time() + (*t).into()) }; + let fd_sets = FdSets::from(nfds, readfds, writefds, exceptfds); + + unsafe { + zero_fd_set(readfds, nfds); + zero_fd_set(writefds, nfds); + zero_fd_set(exceptfds, nfds); + } + + loop { + #[cfg(feature = "net")] + axnet::poll_interfaces(); + let res = fd_sets.poll_all(readfds, writefds, exceptfds)?; + if res > 0 { + return Ok(res); + } + + if deadline.map_or(false, |ddl| current_time() >= ddl) { + debug!(" timeout!"); + return Ok(0); + } + crate::sys_sched_yield(); + } + }) +} + +unsafe fn zero_fd_set(fds: *mut ctypes::fd_set, nfds: usize) { + if !fds.is_null() { + let nfds_usizes = nfds.div_ceil(BITS_PER_USIZE); + let dst = &mut (*fds).fds_bits[..nfds_usizes]; + dst.fill(0); + } +} + +unsafe fn set_fd_set(fds: *mut ctypes::fd_set, fd: usize) { + if !fds.is_null() { + (*fds).fds_bits[fd / BITS_PER_USIZE] |= 1 << (fd % BITS_PER_USIZE); + } +} diff --git a/api/arceos_posix_api/src/imp/mod.rs b/api/arceos_posix_api/src/imp/mod.rs new file mode 100644 index 0000000..603f934 --- /dev/null +++ b/api/arceos_posix_api/src/imp/mod.rs @@ -0,0 +1,20 @@ +mod stdio; + +pub mod io; +pub mod resources; +pub mod sys; +pub mod task; +pub mod time; + +#[cfg(feature = "fd")] +pub mod fd_ops; +#[cfg(feature = "fs")] +pub mod fs; +#[cfg(any(feature = "select", feature = "epoll"))] +pub mod io_mpx; +#[cfg(feature = "net")] +pub mod net; +#[cfg(feature = "pipe")] +pub mod pipe; +#[cfg(feature = "multitask")] +pub mod pthread; diff --git a/api/arceos_posix_api/src/imp/net.rs b/api/arceos_posix_api/src/imp/net.rs new file mode 100644 index 0000000..861e473 --- /dev/null +++ b/api/arceos_posix_api/src/imp/net.rs @@ -0,0 +1,581 @@ +use alloc::{sync::Arc, vec, vec::Vec}; +use core::ffi::{c_char, c_int, c_void}; +use core::mem::size_of; +use core::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; + +use axerrno::{LinuxError, LinuxResult}; +use axio::PollState; +use axnet::{TcpSocket, UdpSocket}; +use axsync::Mutex; + +use super::fd_ops::FileLike; +use crate::ctypes; +use crate::utils::char_ptr_to_str; + +pub enum Socket { + Udp(Mutex), + Tcp(Mutex), +} + +impl Socket { + fn add_to_fd_table(self) -> LinuxResult { + super::fd_ops::add_file_like(Arc::new(self)) + } + + fn from_fd(fd: c_int) -> LinuxResult> { + let f = super::fd_ops::get_file_like(fd)?; + f.into_any() + .downcast::() + .map_err(|_| LinuxError::EINVAL) + } + + fn send(&self, buf: &[u8]) -> LinuxResult { + match self { + Socket::Udp(udpsocket) => Ok(udpsocket.lock().send(buf)?), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().send(buf)?), + } + } + + fn recv(&self, buf: &mut [u8]) -> LinuxResult { + match self { + Socket::Udp(udpsocket) => Ok(udpsocket.lock().recv_from(buf).map(|e| e.0)?), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().recv(buf)?), + } + } + + pub fn poll(&self) -> LinuxResult { + match self { + Socket::Udp(udpsocket) => Ok(udpsocket.lock().poll()?), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().poll()?), + } + } + + fn local_addr(&self) -> LinuxResult { + match self { + Socket::Udp(udpsocket) => Ok(udpsocket.lock().local_addr()?), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().local_addr()?), + } + } + + fn peer_addr(&self) -> LinuxResult { + match self { + Socket::Udp(udpsocket) => Ok(udpsocket.lock().peer_addr()?), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().peer_addr()?), + } + } + + fn bind(&self, addr: SocketAddr) -> LinuxResult { + match self { + Socket::Udp(udpsocket) => Ok(udpsocket.lock().bind(addr)?), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().bind(addr)?), + } + } + + fn connect(&self, addr: SocketAddr) -> LinuxResult { + match self { + Socket::Udp(udpsocket) => Ok(udpsocket.lock().connect(addr)?), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().connect(addr)?), + } + } + + fn sendto(&self, buf: &[u8], addr: SocketAddr) -> LinuxResult { + match self { + // diff: must bind before sendto + Socket::Udp(udpsocket) => Ok(udpsocket.lock().send_to(buf, addr)?), + Socket::Tcp(_) => Err(LinuxError::EISCONN), + } + } + + fn recvfrom(&self, buf: &mut [u8]) -> LinuxResult<(usize, Option)> { + match self { + // diff: must bind before recvfrom + Socket::Udp(udpsocket) => Ok(udpsocket + .lock() + .recv_from(buf) + .map(|res| (res.0, Some(res.1)))?), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().recv(buf).map(|res| (res, None))?), + } + } + + fn listen(&self) -> LinuxResult { + match self { + Socket::Udp(_) => Err(LinuxError::EOPNOTSUPP), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().listen()?), + } + } + + fn accept(&self) -> LinuxResult { + match self { + Socket::Udp(_) => Err(LinuxError::EOPNOTSUPP), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().accept()?), + } + } + + fn shutdown(&self) -> LinuxResult { + match self { + Socket::Udp(udpsocket) => { + let udpsocket = udpsocket.lock(); + udpsocket.peer_addr()?; + udpsocket.shutdown()?; + Ok(()) + } + + Socket::Tcp(tcpsocket) => { + let tcpsocket = tcpsocket.lock(); + tcpsocket.peer_addr()?; + tcpsocket.shutdown()?; + Ok(()) + } + } + } +} + +impl FileLike for Socket { + fn read(&self, buf: &mut [u8]) -> LinuxResult { + self.recv(buf) + } + + fn write(&self, buf: &[u8]) -> LinuxResult { + self.send(buf) + } + + fn stat(&self) -> LinuxResult { + // not really implemented + let st_mode = 0o140000 | 0o777u32; // S_IFSOCK | rwxrwxrwx + Ok(ctypes::stat { + st_ino: 1, + st_nlink: 1, + st_mode, + st_uid: 1000, + st_gid: 1000, + st_blksize: 4096, + ..Default::default() + }) + } + + fn into_any(self: Arc) -> Arc { + self + } + + fn poll(&self) -> LinuxResult { + self.poll() + } + + fn set_nonblocking(&self, nonblock: bool) -> LinuxResult { + match self { + Socket::Udp(udpsocket) => udpsocket.lock().set_nonblocking(nonblock), + Socket::Tcp(tcpsocket) => tcpsocket.lock().set_nonblocking(nonblock), + } + Ok(()) + } +} + +impl From for ctypes::sockaddr_in { + fn from(addr: SocketAddrV4) -> ctypes::sockaddr_in { + ctypes::sockaddr_in { + sin_family: ctypes::AF_INET as u16, + sin_port: addr.port().to_be(), + sin_addr: ctypes::in_addr { + // `s_addr` is stored as BE on all machines and the array is in BE order. + // So the native endian conversion method is used so that it's never swapped. + s_addr: u32::from_ne_bytes(addr.ip().octets()), + }, + sin_zero: [0; 8], + } + } +} + +impl From for SocketAddrV4 { + fn from(addr: ctypes::sockaddr_in) -> SocketAddrV4 { + SocketAddrV4::new( + Ipv4Addr::from(addr.sin_addr.s_addr.to_ne_bytes()), + u16::from_be(addr.sin_port), + ) + } +} + +fn into_sockaddr(addr: SocketAddr) -> (ctypes::sockaddr, ctypes::socklen_t) { + debug!(" Sockaddr: {}", addr); + match addr { + SocketAddr::V4(addr) => ( + unsafe { *(&ctypes::sockaddr_in::from(addr) as *const _ as *const ctypes::sockaddr) }, + size_of::() as _, + ), + SocketAddr::V6(_) => panic!("IPv6 is not supported"), + } +} + +fn from_sockaddr( + addr: *const ctypes::sockaddr, + addrlen: ctypes::socklen_t, +) -> LinuxResult { + if addr.is_null() { + return Err(LinuxError::EFAULT); + } + if addrlen != size_of::() as _ { + return Err(LinuxError::EINVAL); + } + + let mid = unsafe { *(addr as *const ctypes::sockaddr_in) }; + if mid.sin_family != ctypes::AF_INET as u16 { + return Err(LinuxError::EINVAL); + } + + let res = SocketAddr::V4(mid.into()); + debug!(" load sockaddr:{:#x} => {:?}", addr as usize, res); + Ok(res) +} + +/// Create an socket for communication. +/// +/// Return the socket file descriptor. +pub fn sys_socket(domain: c_int, socktype: c_int, protocol: c_int) -> c_int { + debug!("sys_socket <= {} {} {}", domain, socktype, protocol); + let (domain, socktype, protocol) = (domain as u32, socktype as u32, protocol as u32); + syscall_body!(sys_socket, { + match (domain, socktype, protocol) { + (ctypes::AF_INET, ctypes::SOCK_STREAM, ctypes::IPPROTO_TCP) + | (ctypes::AF_INET, ctypes::SOCK_STREAM, 0) => { + Socket::Tcp(Mutex::new(TcpSocket::new())).add_to_fd_table() + } + (ctypes::AF_INET, ctypes::SOCK_DGRAM, ctypes::IPPROTO_UDP) + | (ctypes::AF_INET, ctypes::SOCK_DGRAM, 0) => { + Socket::Udp(Mutex::new(UdpSocket::new())).add_to_fd_table() + } + _ => Err(LinuxError::EINVAL), + } + }) +} + +/// Bind a address to a socket. +/// +/// Return 0 if success. +pub fn sys_bind( + socket_fd: c_int, + socket_addr: *const ctypes::sockaddr, + addrlen: ctypes::socklen_t, +) -> c_int { + debug!( + "sys_bind <= {} {:#x} {}", + socket_fd, socket_addr as usize, addrlen + ); + syscall_body!(sys_bind, { + let addr = from_sockaddr(socket_addr, addrlen)?; + Socket::from_fd(socket_fd)?.bind(addr)?; + Ok(0) + }) +} + +/// Connects the socket to the address specified. +/// +/// Return 0 if success. +pub fn sys_connect( + socket_fd: c_int, + socket_addr: *const ctypes::sockaddr, + addrlen: ctypes::socklen_t, +) -> c_int { + debug!( + "sys_connect <= {} {:#x} {}", + socket_fd, socket_addr as usize, addrlen + ); + syscall_body!(sys_connect, { + let addr = from_sockaddr(socket_addr, addrlen)?; + Socket::from_fd(socket_fd)?.connect(addr)?; + Ok(0) + }) +} + +/// Send a message on a socket to the address specified. +/// +/// Return the number of bytes sent if success. +pub fn sys_sendto( + socket_fd: c_int, + buf_ptr: *const c_void, + len: ctypes::size_t, + flag: c_int, // currently not used + socket_addr: *const ctypes::sockaddr, + addrlen: ctypes::socklen_t, +) -> ctypes::ssize_t { + debug!( + "sys_sendto <= {} {:#x} {} {} {:#x} {}", + socket_fd, buf_ptr as usize, len, flag, socket_addr as usize, addrlen + ); + syscall_body!(sys_sendto, { + if buf_ptr.is_null() { + return Err(LinuxError::EFAULT); + } + let addr = from_sockaddr(socket_addr, addrlen)?; + let buf = unsafe { core::slice::from_raw_parts(buf_ptr as *const u8, len) }; + Socket::from_fd(socket_fd)?.sendto(buf, addr) + }) +} + +/// Send a message on a socket to the address connected. +/// +/// Return the number of bytes sent if success. +pub fn sys_send( + socket_fd: c_int, + buf_ptr: *const c_void, + len: ctypes::size_t, + flag: c_int, // currently not used +) -> ctypes::ssize_t { + debug!( + "sys_sendto <= {} {:#x} {} {}", + socket_fd, buf_ptr as usize, len, flag + ); + syscall_body!(sys_send, { + if buf_ptr.is_null() { + return Err(LinuxError::EFAULT); + } + let buf = unsafe { core::slice::from_raw_parts(buf_ptr as *const u8, len) }; + Socket::from_fd(socket_fd)?.send(buf) + }) +} + +/// Receive a message on a socket and get its source address. +/// +/// Return the number of bytes received if success. +pub unsafe fn sys_recvfrom( + socket_fd: c_int, + buf_ptr: *mut c_void, + len: ctypes::size_t, + flag: c_int, // currently not used + socket_addr: *mut ctypes::sockaddr, + addrlen: *mut ctypes::socklen_t, +) -> ctypes::ssize_t { + debug!( + "sys_recvfrom <= {} {:#x} {} {} {:#x} {:#x}", + socket_fd, buf_ptr as usize, len, flag, socket_addr as usize, addrlen as usize + ); + syscall_body!(sys_recvfrom, { + if buf_ptr.is_null() || socket_addr.is_null() || addrlen.is_null() { + return Err(LinuxError::EFAULT); + } + let socket = Socket::from_fd(socket_fd)?; + let buf = unsafe { core::slice::from_raw_parts_mut(buf_ptr as *mut u8, len) }; + + let res = socket.recvfrom(buf)?; + if let Some(addr) = res.1 { + unsafe { + (*socket_addr, *addrlen) = into_sockaddr(addr); + } + } + Ok(res.0) + }) +} + +/// Receive a message on a socket. +/// +/// Return the number of bytes received if success. +pub fn sys_recv( + socket_fd: c_int, + buf_ptr: *mut c_void, + len: ctypes::size_t, + flag: c_int, // currently not used +) -> ctypes::ssize_t { + debug!( + "sys_recv <= {} {:#x} {} {}", + socket_fd, buf_ptr as usize, len, flag + ); + syscall_body!(sys_recv, { + if buf_ptr.is_null() { + return Err(LinuxError::EFAULT); + } + let buf = unsafe { core::slice::from_raw_parts_mut(buf_ptr as *mut u8, len) }; + Socket::from_fd(socket_fd)?.recv(buf) + }) +} + +/// Listen for connections on a socket +/// +/// Return 0 if success. +pub fn sys_listen( + socket_fd: c_int, + backlog: c_int, // currently not used +) -> c_int { + debug!("sys_listen <= {} {}", socket_fd, backlog); + syscall_body!(sys_listen, { + Socket::from_fd(socket_fd)?.listen()?; + Ok(0) + }) +} + +/// Accept for connections on a socket +/// +/// Return file descriptor for the accepted socket if success. +pub unsafe fn sys_accept( + socket_fd: c_int, + socket_addr: *mut ctypes::sockaddr, + socket_len: *mut ctypes::socklen_t, +) -> c_int { + debug!( + "sys_accept <= {} {:#x} {:#x}", + socket_fd, socket_addr as usize, socket_len as usize + ); + syscall_body!(sys_accept, { + if socket_addr.is_null() || socket_len.is_null() { + return Err(LinuxError::EFAULT); + } + let socket = Socket::from_fd(socket_fd)?; + let new_socket = socket.accept()?; + let addr = new_socket.peer_addr()?; + let new_fd = Socket::add_to_fd_table(Socket::Tcp(Mutex::new(new_socket)))?; + unsafe { + (*socket_addr, *socket_len) = into_sockaddr(addr); + } + Ok(new_fd) + }) +} + +/// Shut down a full-duplex connection. +/// +/// Return 0 if success. +pub fn sys_shutdown( + socket_fd: c_int, + flag: c_int, // currently not used +) -> c_int { + debug!("sys_shutdown <= {} {}", socket_fd, flag); + syscall_body!(sys_shutdown, { + Socket::from_fd(socket_fd)?.shutdown()?; + Ok(0) + }) +} + +/// Query addresses for a domain name. +/// +/// Only IPv4. Ports are always 0. Ignore servname and hint. +/// Results' ai_flags and ai_canonname are 0 or NULL. +/// +/// Return address number if success. +pub unsafe fn sys_getaddrinfo( + nodename: *const c_char, + servname: *const c_char, + _hints: *const ctypes::addrinfo, + res: *mut *mut ctypes::addrinfo, +) -> c_int { + let name = char_ptr_to_str(nodename); + let port = char_ptr_to_str(servname); + debug!("sys_getaddrinfo <= {:?} {:?}", name, port); + syscall_body!(sys_getaddrinfo, { + if nodename.is_null() && servname.is_null() { + return Ok(0); + } + if res.is_null() { + return Err(LinuxError::EFAULT); + } + + let port = port.map_or(0, |p| p.parse::().unwrap_or(0)); + let ip_addrs = if let Ok(domain) = name { + if let Ok(a) = domain.parse::() { + vec![a] + } else { + axnet::dns_query(domain)? + } + } else { + vec![Ipv4Addr::LOCALHOST.into()] + }; + + let len = ip_addrs.len().min(ctypes::MAXADDRS as usize); + if len == 0 { + return Ok(0); + } + + let mut out: Vec = Vec::with_capacity(len); + for (i, &ip) in ip_addrs.iter().enumerate().take(len) { + let buf = match ip { + IpAddr::V4(ip) => ctypes::aibuf { + ai: ctypes::addrinfo { + ai_family: ctypes::AF_INET as _, + // TODO: This is a hard-code part, only return TCP parameters + ai_socktype: ctypes::SOCK_STREAM as _, + ai_protocol: ctypes::IPPROTO_TCP as _, + ai_addrlen: size_of::() as _, + ai_addr: core::ptr::null_mut(), + ai_canonname: core::ptr::null_mut(), + ai_next: core::ptr::null_mut(), + ai_flags: 0, + }, + sa: ctypes::aibuf_sa { + sin: SocketAddrV4::new(ip, port).into(), + }, + slot: i as i16, + lock: [0], + ref_: 0, + }, + _ => panic!("IPv6 is not supported"), + }; + out.push(buf); + out[i].ai.ai_addr = + unsafe { core::ptr::addr_of_mut!(out[i].sa.sin) as *mut ctypes::sockaddr }; + if i > 0 { + out[i - 1].ai.ai_next = core::ptr::addr_of_mut!(out[i].ai); + } + } + + out[0].ref_ = len as i16; + unsafe { *res = core::ptr::addr_of_mut!(out[0].ai) }; + core::mem::forget(out); // drop in `sys_freeaddrinfo` + Ok(len) + }) +} + +/// Free queried `addrinfo` struct +pub unsafe fn sys_freeaddrinfo(res: *mut ctypes::addrinfo) { + if res.is_null() { + return; + } + let aibuf_ptr = res as *mut ctypes::aibuf; + let len = (*aibuf_ptr).ref_ as usize; + assert!((*aibuf_ptr).slot == 0); + assert!(len > 0); + let vec = Vec::from_raw_parts(aibuf_ptr, len, len); // TODO: lock + drop(vec); +} + +/// Get current address to which the socket sockfd is bound. +pub unsafe fn sys_getsockname( + sock_fd: c_int, + addr: *mut ctypes::sockaddr, + addrlen: *mut ctypes::socklen_t, +) -> c_int { + debug!( + "sys_getsockname <= {} {:#x} {:#x}", + sock_fd, addr as usize, addrlen as usize + ); + syscall_body!(sys_getsockname, { + if addr.is_null() || addrlen.is_null() { + return Err(LinuxError::EFAULT); + } + if unsafe { *addrlen } < size_of::() as u32 { + return Err(LinuxError::EINVAL); + } + unsafe { + (*addr, *addrlen) = into_sockaddr(Socket::from_fd(sock_fd)?.local_addr()?); + } + Ok(0) + }) +} + +/// Get peer address to which the socket sockfd is connected. +pub unsafe fn sys_getpeername( + sock_fd: c_int, + addr: *mut ctypes::sockaddr, + addrlen: *mut ctypes::socklen_t, +) -> c_int { + debug!( + "sys_getpeername <= {} {:#x} {:#x}", + sock_fd, addr as usize, addrlen as usize + ); + syscall_body!(sys_getpeername, { + if addr.is_null() || addrlen.is_null() { + return Err(LinuxError::EFAULT); + } + if unsafe { *addrlen } < size_of::() as u32 { + return Err(LinuxError::EINVAL); + } + unsafe { + (*addr, *addrlen) = into_sockaddr(Socket::from_fd(sock_fd)?.peer_addr()?); + } + Ok(0) + }) +} diff --git a/api/arceos_posix_api/src/imp/pipe.rs b/api/arceos_posix_api/src/imp/pipe.rs new file mode 100644 index 0000000..17a9b24 --- /dev/null +++ b/api/arceos_posix_api/src/imp/pipe.rs @@ -0,0 +1,214 @@ +use alloc::sync::Arc; +use core::ffi::c_int; + +use axerrno::{LinuxError, LinuxResult}; +use axio::PollState; +use axsync::Mutex; + +use super::fd_ops::{add_file_like, close_file_like, FileLike}; +use crate::ctypes; + +#[derive(Copy, Clone, PartialEq)] +enum RingBufferStatus { + Full, + Empty, + Normal, +} + +const RING_BUFFER_SIZE: usize = 256; + +pub struct PipeRingBuffer { + arr: [u8; RING_BUFFER_SIZE], + head: usize, + tail: usize, + status: RingBufferStatus, +} + +impl PipeRingBuffer { + pub const fn new() -> Self { + Self { + arr: [0; RING_BUFFER_SIZE], + head: 0, + tail: 0, + status: RingBufferStatus::Empty, + } + } + + pub fn write_byte(&mut self, byte: u8) { + self.status = RingBufferStatus::Normal; + self.arr[self.tail] = byte; + self.tail = (self.tail + 1) % RING_BUFFER_SIZE; + if self.tail == self.head { + self.status = RingBufferStatus::Full; + } + } + + pub fn read_byte(&mut self) -> u8 { + self.status = RingBufferStatus::Normal; + let c = self.arr[self.head]; + self.head = (self.head + 1) % RING_BUFFER_SIZE; + if self.head == self.tail { + self.status = RingBufferStatus::Empty; + } + c + } + + /// Get the length of remaining data in the buffer + pub const fn available_read(&self) -> usize { + if matches!(self.status, RingBufferStatus::Empty) { + 0 + } else if self.tail > self.head { + self.tail - self.head + } else { + self.tail + RING_BUFFER_SIZE - self.head + } + } + + /// Get the length of remaining space in the buffer + pub const fn available_write(&self) -> usize { + if matches!(self.status, RingBufferStatus::Full) { + 0 + } else { + RING_BUFFER_SIZE - self.available_read() + } + } +} + +pub struct Pipe { + readable: bool, + buffer: Arc>, +} + +impl Pipe { + pub fn new() -> (Pipe, Pipe) { + let buffer = Arc::new(Mutex::new(PipeRingBuffer::new())); + let read_end = Pipe { + readable: true, + buffer: buffer.clone(), + }; + let write_end = Pipe { + readable: false, + buffer, + }; + (read_end, write_end) + } + + pub const fn readable(&self) -> bool { + self.readable + } + + pub const fn writable(&self) -> bool { + !self.readable + } + + pub fn write_end_close(&self) -> bool { + Arc::strong_count(&self.buffer) == 1 + } +} + +impl FileLike for Pipe { + fn read(&self, buf: &mut [u8]) -> LinuxResult { + if !self.readable() { + return Err(LinuxError::EPERM); + } + let mut read_size = 0usize; + let max_len = buf.len(); + loop { + let mut ring_buffer = self.buffer.lock(); + let loop_read = ring_buffer.available_read(); + if loop_read == 0 { + if self.write_end_close() { + return Ok(read_size); + } + drop(ring_buffer); + // Data not ready, wait for write end + crate::sys_sched_yield(); // TODO: use synconize primitive + continue; + } + for _ in 0..loop_read { + if read_size == max_len { + return Ok(read_size); + } + buf[read_size] = ring_buffer.read_byte(); + read_size += 1; + } + } + } + + fn write(&self, buf: &[u8]) -> LinuxResult { + if !self.writable() { + return Err(LinuxError::EPERM); + } + let mut write_size = 0usize; + let max_len = buf.len(); + loop { + let mut ring_buffer = self.buffer.lock(); + let loop_write = ring_buffer.available_write(); + if loop_write == 0 { + drop(ring_buffer); + // Buffer is full, wait for read end to consume + crate::sys_sched_yield(); // TODO: use synconize primitive + continue; + } + for _ in 0..loop_write { + if write_size == max_len { + return Ok(write_size); + } + ring_buffer.write_byte(buf[write_size]); + write_size += 1; + } + } + } + + fn stat(&self) -> LinuxResult { + let st_mode = 0o10000 | 0o600u32; // S_IFIFO | rw------- + Ok(ctypes::stat { + st_ino: 1, + st_nlink: 1, + st_mode, + st_uid: 1000, + st_gid: 1000, + st_blksize: 4096, + ..Default::default() + }) + } + + fn into_any(self: Arc) -> Arc { + self + } + + fn poll(&self) -> LinuxResult { + let buf = self.buffer.lock(); + Ok(PollState { + readable: self.readable() && buf.available_read() > 0, + writable: self.writable() && buf.available_write() > 0, + }) + } + + fn set_nonblocking(&self, _nonblocking: bool) -> LinuxResult { + Ok(()) + } +} + +/// Create a pipe +/// +/// Return 0 if succeed +pub fn sys_pipe(fds: &mut [c_int]) -> c_int { + debug!("sys_pipe <= {:#x}", fds.as_ptr() as usize); + syscall_body!(sys_pipe, { + if fds.len() != 2 { + return Err(LinuxError::EFAULT); + } + + let (read_end, write_end) = Pipe::new(); + let read_fd = add_file_like(Arc::new(read_end))?; + let write_fd = add_file_like(Arc::new(write_end)).inspect_err(|_| { + close_file_like(read_fd).ok(); + })?; + + fds[0] = read_fd as c_int; + fds[1] = write_fd as c_int; + + Ok(0) + }) +} diff --git a/api/arceos_posix_api/src/imp/pthread/mod.rs b/api/arceos_posix_api/src/imp/pthread/mod.rs new file mode 100644 index 0000000..18a518c --- /dev/null +++ b/api/arceos_posix_api/src/imp/pthread/mod.rs @@ -0,0 +1,154 @@ +use alloc::{boxed::Box, collections::BTreeMap, sync::Arc}; +use core::cell::UnsafeCell; +use core::ffi::{c_int, c_void}; + +use axerrno::{LinuxError, LinuxResult}; +use axtask::AxTaskRef; +use spin::RwLock; + +use crate::ctypes; + +pub mod mutex; + +lazy_static::lazy_static! { + static ref TID_TO_PTHREAD: RwLock>> = { + let mut map = BTreeMap::new(); + let main_task = axtask::current(); + let main_tid = main_task.id().as_u64(); + let main_thread = Pthread { + inner: main_task.as_task_ref().clone(), + retval: Arc::new(Packet { + result: UnsafeCell::new(core::ptr::null_mut()), + }), + }; + let ptr = Box::into_raw(Box::new(main_thread)) as *mut c_void; + map.insert(main_tid, ForceSendSync(ptr)); + RwLock::new(map) + }; +} + +struct Packet { + result: UnsafeCell, +} + +unsafe impl Send for Packet {} +unsafe impl Sync for Packet {} + +pub struct Pthread { + inner: AxTaskRef, + retval: Arc>, +} + +impl Pthread { + fn create( + _attr: *const ctypes::pthread_attr_t, + start_routine: extern "C" fn(arg: *mut c_void) -> *mut c_void, + arg: *mut c_void, + ) -> LinuxResult { + let arg_wrapper = ForceSendSync(arg); + + let my_packet: Arc> = Arc::new(Packet { + result: UnsafeCell::new(core::ptr::null_mut()), + }); + let their_packet = my_packet.clone(); + + let main = move || { + let arg = arg_wrapper; + let ret = start_routine(arg.0); + unsafe { *their_packet.result.get() = ret }; + drop(their_packet); + }; + + let task_inner = axtask::spawn(main); + let tid = task_inner.id().as_u64(); + let thread = Pthread { + inner: task_inner, + retval: my_packet, + }; + let ptr = Box::into_raw(Box::new(thread)) as *mut c_void; + TID_TO_PTHREAD.write().insert(tid, ForceSendSync(ptr)); + Ok(ptr) + } + + fn current_ptr() -> *mut Pthread { + let tid = axtask::current().id().as_u64(); + match TID_TO_PTHREAD.read().get(&tid) { + None => core::ptr::null_mut(), + Some(ptr) => ptr.0 as *mut Pthread, + } + } + + fn current() -> Option<&'static Pthread> { + unsafe { core::ptr::NonNull::new(Self::current_ptr()).map(|ptr| ptr.as_ref()) } + } + + fn exit_current(retval: *mut c_void) -> ! { + let thread = Self::current().expect("fail to get current thread"); + unsafe { *thread.retval.result.get() = retval }; + axtask::exit(0); + } + + fn join(ptr: ctypes::pthread_t) -> LinuxResult<*mut c_void> { + if core::ptr::eq(ptr, Self::current_ptr() as _) { + return Err(LinuxError::EDEADLK); + } + + let thread = unsafe { Box::from_raw(ptr as *mut Pthread) }; + axtask::join(&thread.inner); + let tid = thread.inner.id().as_u64(); + let retval = unsafe { *thread.retval.result.get() }; + TID_TO_PTHREAD.write().remove(&tid); + drop(thread); + Ok(retval) + } +} + +/// Returns the `pthread` struct of current thread. +pub fn sys_pthread_self() -> ctypes::pthread_t { + Pthread::current().expect("fail to get current thread") as *const Pthread as _ +} + +/// Create a new thread with the given entry point and argument. +/// +/// If successful, it stores the pointer to the newly created `struct __pthread` +/// in `res` and returns 0. +pub unsafe fn sys_pthread_create( + res: *mut ctypes::pthread_t, + attr: *const ctypes::pthread_attr_t, + start_routine: extern "C" fn(arg: *mut c_void) -> *mut c_void, + arg: *mut c_void, +) -> c_int { + debug!( + "sys_pthread_create <= {:#x}, {:#x}", + start_routine as usize, arg as usize + ); + syscall_body!(sys_pthread_create, { + let ptr = Pthread::create(attr, start_routine, arg)?; + unsafe { core::ptr::write(res, ptr) }; + Ok(0) + }) +} + +/// Exits the current thread. The value `retval` will be returned to the joiner. +pub fn sys_pthread_exit(retval: *mut c_void) -> ! { + debug!("sys_pthread_exit <= {:#x}", retval as usize); + Pthread::exit_current(retval); +} + +/// Waits for the given thread to exit, and stores the return value in `retval`. +pub unsafe fn sys_pthread_join(thread: ctypes::pthread_t, retval: *mut *mut c_void) -> c_int { + debug!("sys_pthread_join <= {:#x}", retval as usize); + syscall_body!(sys_pthread_join, { + let ret = Pthread::join(thread)?; + if !retval.is_null() { + unsafe { core::ptr::write(retval, ret) }; + } + Ok(0) + }) +} + +#[derive(Clone, Copy)] +struct ForceSendSync(T); + +unsafe impl Send for ForceSendSync {} +unsafe impl Sync for ForceSendSync {} diff --git a/api/arceos_posix_api/src/imp/pthread/mutex.rs b/api/arceos_posix_api/src/imp/pthread/mutex.rs new file mode 100644 index 0000000..528779a --- /dev/null +++ b/api/arceos_posix_api/src/imp/pthread/mutex.rs @@ -0,0 +1,70 @@ +use crate::{ctypes, utils::check_null_mut_ptr}; + +use axerrno::LinuxResult; +use axsync::Mutex; + +use core::ffi::c_int; +use core::mem::{size_of, ManuallyDrop}; + +static_assertions::const_assert_eq!( + size_of::(), + size_of::() +); + +#[repr(C)] +pub struct PthreadMutex(Mutex<()>); + +impl PthreadMutex { + const fn new() -> Self { + Self(Mutex::new(())) + } + + fn lock(&self) -> LinuxResult { + let _guard = ManuallyDrop::new(self.0.lock()); + Ok(()) + } + + fn unlock(&self) -> LinuxResult { + unsafe { self.0.force_unlock() }; + Ok(()) + } +} + +/// Initialize a mutex. +pub fn sys_pthread_mutex_init( + mutex: *mut ctypes::pthread_mutex_t, + _attr: *const ctypes::pthread_mutexattr_t, +) -> c_int { + debug!("sys_pthread_mutex_init <= {:#x}", mutex as usize); + syscall_body!(sys_pthread_mutex_init, { + check_null_mut_ptr(mutex)?; + unsafe { + mutex.cast::().write(PthreadMutex::new()); + } + Ok(0) + }) +} + +/// Lock the given mutex. +pub fn sys_pthread_mutex_lock(mutex: *mut ctypes::pthread_mutex_t) -> c_int { + debug!("sys_pthread_mutex_lock <= {:#x}", mutex as usize); + syscall_body!(sys_pthread_mutex_lock, { + check_null_mut_ptr(mutex)?; + unsafe { + (*mutex.cast::()).lock()?; + } + Ok(0) + }) +} + +/// Unlock the given mutex. +pub fn sys_pthread_mutex_unlock(mutex: *mut ctypes::pthread_mutex_t) -> c_int { + debug!("sys_pthread_mutex_unlock <= {:#x}", mutex as usize); + syscall_body!(sys_pthread_mutex_unlock, { + check_null_mut_ptr(mutex)?; + unsafe { + (*mutex.cast::()).unlock()?; + } + Ok(0) + }) +} diff --git a/api/arceos_posix_api/src/imp/resources.rs b/api/arceos_posix_api/src/imp/resources.rs new file mode 100644 index 0000000..16956ae --- /dev/null +++ b/api/arceos_posix_api/src/imp/resources.rs @@ -0,0 +1,51 @@ +use crate::ctypes; +use axerrno::LinuxError; +use core::ffi::c_int; + +/// Get resource limitations +/// +/// TODO: support more resource types +pub unsafe fn sys_getrlimit(resource: c_int, rlimits: *mut ctypes::rlimit) -> c_int { + debug!("sys_getrlimit <= {} {:#x}", resource, rlimits as usize); + syscall_body!(sys_getrlimit, { + match resource as u32 { + ctypes::RLIMIT_DATA => {} + ctypes::RLIMIT_STACK => {} + ctypes::RLIMIT_NOFILE => {} + _ => return Err(LinuxError::EINVAL), + } + if rlimits.is_null() { + return Ok(0); + } + match resource as u32 { + ctypes::RLIMIT_STACK => unsafe { + (*rlimits).rlim_cur = axconfig::TASK_STACK_SIZE as _; + (*rlimits).rlim_max = axconfig::TASK_STACK_SIZE as _; + }, + #[cfg(feature = "fd")] + ctypes::RLIMIT_NOFILE => unsafe { + (*rlimits).rlim_cur = super::fd_ops::AX_FILE_LIMIT as _; + (*rlimits).rlim_max = super::fd_ops::AX_FILE_LIMIT as _; + }, + _ => {} + } + Ok(0) + }) +} + +/// Set resource limitations +/// +/// TODO: support more resource types +pub unsafe fn sys_setrlimit(resource: c_int, rlimits: *mut crate::ctypes::rlimit) -> c_int { + debug!("sys_setrlimit <= {} {:#x}", resource, rlimits as usize); + syscall_body!(sys_setrlimit, { + match resource as u32 { + crate::ctypes::RLIMIT_DATA => {} + crate::ctypes::RLIMIT_STACK => {} + crate::ctypes::RLIMIT_NOFILE => {} + _ => return Err(LinuxError::EINVAL), + } + // Currently do not support set resources + Ok(0) + }) +} diff --git a/api/arceos_posix_api/src/imp/stdio.rs b/api/arceos_posix_api/src/imp/stdio.rs new file mode 100644 index 0000000..ded9c33 --- /dev/null +++ b/api/arceos_posix_api/src/imp/stdio.rs @@ -0,0 +1,170 @@ +use axerrno::AxResult; +use axio::{prelude::*, BufReader}; +use axsync::Mutex; + +#[cfg(feature = "fd")] +use {alloc::sync::Arc, axerrno::LinuxError, axerrno::LinuxResult, axio::PollState}; + +fn console_read_bytes() -> Option { + axhal::console::getchar().map(|c| if c == b'\r' { b'\n' } else { c }) +} + +fn console_write_bytes(buf: &[u8]) -> AxResult { + axhal::console::write_bytes(buf); + Ok(buf.len()) +} + +struct StdinRaw; +struct StdoutRaw; + +impl Read for StdinRaw { + // Non-blocking read, returns number of bytes read. + fn read(&mut self, buf: &mut [u8]) -> AxResult { + let mut read_len = 0; + while read_len < buf.len() { + if let Some(c) = console_read_bytes() { + buf[read_len] = c; + read_len += 1; + } else { + break; + } + } + Ok(read_len) + } +} + +impl Write for StdoutRaw { + fn write(&mut self, buf: &[u8]) -> AxResult { + console_write_bytes(buf) + } + + fn flush(&mut self) -> AxResult { + Ok(()) + } +} + +pub struct Stdin { + inner: &'static Mutex>, +} + +impl Stdin { + // Block until at least one byte is read. + fn read_blocked(&self, buf: &mut [u8]) -> AxResult { + let read_len = self.inner.lock().read(buf)?; + if buf.is_empty() || read_len > 0 { + return Ok(read_len); + } + // try again until we get something + loop { + let read_len = self.inner.lock().read(buf)?; + if read_len > 0 { + return Ok(read_len); + } + crate::sys_sched_yield(); + } + } +} + +impl Read for Stdin { + fn read(&mut self, buf: &mut [u8]) -> AxResult { + self.read_blocked(buf) + } +} + +pub struct Stdout { + inner: &'static Mutex, +} + +impl Write for Stdout { + fn write(&mut self, buf: &[u8]) -> AxResult { + self.inner.lock().write(buf) + } + + fn flush(&mut self) -> AxResult { + self.inner.lock().flush() + } +} + +/// Constructs a new handle to the standard input of the current process. +pub fn stdin() -> Stdin { + static INSTANCE: Mutex> = Mutex::new(BufReader::new(StdinRaw)); + Stdin { inner: &INSTANCE } +} + +/// Constructs a new handle to the standard output of the current process. +pub fn stdout() -> Stdout { + static INSTANCE: Mutex = Mutex::new(StdoutRaw); + Stdout { inner: &INSTANCE } +} + +#[cfg(feature = "fd")] +impl super::fd_ops::FileLike for Stdin { + fn read(&self, buf: &mut [u8]) -> LinuxResult { + Ok(self.read_blocked(buf)?) + } + + fn write(&self, _buf: &[u8]) -> LinuxResult { + Err(LinuxError::EPERM) + } + + fn stat(&self) -> LinuxResult { + let st_mode = 0o20000 | 0o440u32; // S_IFCHR | r--r----- + Ok(crate::ctypes::stat { + st_ino: 1, + st_nlink: 1, + st_mode, + ..Default::default() + }) + } + + fn into_any(self: Arc) -> Arc { + self + } + + fn poll(&self) -> LinuxResult { + Ok(PollState { + readable: true, + writable: true, + }) + } + + fn set_nonblocking(&self, _nonblocking: bool) -> LinuxResult { + Ok(()) + } +} + +#[cfg(feature = "fd")] +impl super::fd_ops::FileLike for Stdout { + fn read(&self, _buf: &mut [u8]) -> LinuxResult { + Err(LinuxError::EPERM) + } + + fn write(&self, buf: &[u8]) -> LinuxResult { + Ok(self.inner.lock().write(buf)?) + } + + fn stat(&self) -> LinuxResult { + let st_mode = 0o20000 | 0o220u32; // S_IFCHR | -w--w---- + Ok(crate::ctypes::stat { + st_ino: 1, + st_nlink: 1, + st_mode, + ..Default::default() + }) + } + + fn into_any(self: Arc) -> Arc { + self + } + + fn poll(&self) -> LinuxResult { + Ok(PollState { + readable: true, + writable: true, + }) + } + + fn set_nonblocking(&self, _nonblocking: bool) -> LinuxResult { + Ok(()) + } +} diff --git a/api/arceos_posix_api/src/imp/sys.rs b/api/arceos_posix_api/src/imp/sys.rs new file mode 100644 index 0000000..0e7a432 --- /dev/null +++ b/api/arceos_posix_api/src/imp/sys.rs @@ -0,0 +1,29 @@ +use core::ffi::{c_int, c_long}; + +use crate::ctypes; + +const PAGE_SIZE_4K: usize = 4096; + +/// Return system configuration infomation +/// +/// Notice: currently only support what unikraft covers +pub fn sys_sysconf(name: c_int) -> c_long { + debug!("sys_sysconf <= {}", name); + syscall_body!(sys_sysconf, { + match name as u32 { + // Page size + ctypes::_SC_PAGE_SIZE => Ok(PAGE_SIZE_4K), + // Total physical pages + ctypes::_SC_PHYS_PAGES => Ok(axconfig::PHYS_MEMORY_SIZE / PAGE_SIZE_4K), + // Number of processors in use + ctypes::_SC_NPROCESSORS_ONLN => Ok(axconfig::SMP), + // Avaliable physical pages + #[cfg(feature = "alloc")] + ctypes::_SC_AVPHYS_PAGES => Ok(axalloc::global_allocator().available_pages()), + // Maximum number of files per process + #[cfg(feature = "fd")] + ctypes::_SC_OPEN_MAX => Ok(super::fd_ops::AX_FILE_LIMIT), + _ => Ok(0), + } + }) +} diff --git a/api/arceos_posix_api/src/imp/task.rs b/api/arceos_posix_api/src/imp/task.rs new file mode 100644 index 0000000..5ba05fe --- /dev/null +++ b/api/arceos_posix_api/src/imp/task.rs @@ -0,0 +1,40 @@ +use core::ffi::c_int; + +/// Relinquish the CPU, and switches to another task. +/// +/// For single-threaded configuration (`multitask` feature is disabled), we just +/// relax the CPU and wait for incoming interrupts. +pub fn sys_sched_yield() -> c_int { + #[cfg(feature = "multitask")] + axtask::yield_now(); + #[cfg(not(feature = "multitask"))] + if cfg!(feature = "irq") { + axhal::arch::wait_for_irqs(); + } else { + core::hint::spin_loop(); + } + 0 +} + +/// Get current thread ID. +pub fn sys_getpid() -> c_int { + syscall_body!(sys_getpid, + #[cfg(feature = "multitask")] + { + Ok(axtask::current().id().as_u64() as c_int) + } + #[cfg(not(feature = "multitask"))] + { + Ok(2) // `main` task ID + } + ) +} + +/// Exit current task +pub fn sys_exit(exit_code: c_int) -> ! { + debug!("sys_exit <= {}", exit_code); + #[cfg(feature = "multitask")] + axtask::exit(exit_code); + #[cfg(not(feature = "multitask"))] + axhal::misc::terminate(); +} diff --git a/api/arceos_posix_api/src/imp/time.rs b/api/arceos_posix_api/src/imp/time.rs new file mode 100644 index 0000000..56fa3a1 --- /dev/null +++ b/api/arceos_posix_api/src/imp/time.rs @@ -0,0 +1,84 @@ +use axerrno::LinuxError; +use core::ffi::{c_int, c_long}; +use core::time::Duration; + +use crate::ctypes; + +impl From for Duration { + fn from(ts: ctypes::timespec) -> Self { + Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32) + } +} + +impl From for Duration { + fn from(tv: ctypes::timeval) -> Self { + Duration::new(tv.tv_sec as u64, tv.tv_usec as u32 * 1000) + } +} + +impl From for ctypes::timespec { + fn from(d: Duration) -> Self { + ctypes::timespec { + tv_sec: d.as_secs() as c_long, + tv_nsec: d.subsec_nanos() as c_long, + } + } +} + +impl From for ctypes::timeval { + fn from(d: Duration) -> Self { + ctypes::timeval { + tv_sec: d.as_secs() as c_long, + tv_usec: d.subsec_micros() as c_long, + } + } +} + +/// Get clock time since booting +pub unsafe fn sys_clock_gettime(_clk: ctypes::clockid_t, ts: *mut ctypes::timespec) -> c_int { + syscall_body!(sys_clock_gettime, { + if ts.is_null() { + return Err(LinuxError::EFAULT); + } + let now = axhal::time::current_time().into(); + unsafe { *ts = now }; + debug!("sys_clock_gettime: {}.{:09}s", now.tv_sec, now.tv_nsec); + Ok(0) + }) +} + +/// Sleep some nanoseconds +/// +/// TODO: should be woken by signals, and set errno +pub unsafe fn sys_nanosleep(req: *const ctypes::timespec, rem: *mut ctypes::timespec) -> c_int { + syscall_body!(sys_nanosleep, { + unsafe { + if req.is_null() || (*req).tv_nsec < 0 || (*req).tv_nsec > 999999999 { + return Err(LinuxError::EINVAL); + } + } + + let dur = unsafe { + debug!("sys_nanosleep <= {}.{:09}s", (*req).tv_sec, (*req).tv_nsec); + Duration::from(*req) + }; + + let now = axhal::time::current_time(); + + #[cfg(feature = "multitask")] + axtask::sleep(dur); + #[cfg(not(feature = "multitask"))] + axhal::time::busy_wait(dur); + + let after = axhal::time::current_time(); + let actual = after - now; + + if let Some(diff) = dur.checked_sub(actual) { + if !rem.is_null() { + unsafe { (*rem) = diff.into() }; + } + return Err(LinuxError::EINTR); + } + Ok(0) + }) +} diff --git a/api/arceos_posix_api/src/lib.rs b/api/arceos_posix_api/src/lib.rs new file mode 100644 index 0000000..9e5d8e9 --- /dev/null +++ b/api/arceos_posix_api/src/lib.rs @@ -0,0 +1,60 @@ +//! POSIX-compatible APIs for [ArceOS] modules +//! +//! [ArceOS]: https://github.com/rcore-os/arceos + +#![cfg_attr(all(not(test), not(doc)), no_std)] +#![feature(doc_cfg)] +#![feature(doc_auto_cfg)] +#![allow(clippy::missing_safety_doc)] + +#[macro_use] +extern crate axlog; +extern crate axruntime; + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[macro_use] +mod utils; + +mod imp; + +/// Platform-specific constants and parameters. +pub mod config { + pub use axconfig::*; +} + +/// POSIX C types. +#[rustfmt::skip] +#[path = "./ctypes_gen.rs"] +#[allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals, clippy::upper_case_acronyms, missing_docs)] +pub mod ctypes; + +pub use imp::io::{sys_read, sys_write, sys_writev}; +pub use imp::resources::{sys_getrlimit, sys_setrlimit}; +pub use imp::sys::sys_sysconf; +pub use imp::task::{sys_exit, sys_getpid, sys_sched_yield}; +pub use imp::time::{sys_clock_gettime, sys_nanosleep}; + +#[cfg(feature = "fd")] +pub use imp::fd_ops::{sys_close, sys_dup, sys_dup2, sys_dup3, sys_fcntl}; +#[cfg(feature = "fs")] +pub use imp::fs::{sys_fstat, sys_getcwd, sys_lseek, sys_lstat, sys_open, sys_rename, sys_stat}; +#[cfg(feature = "select")] +pub use imp::io_mpx::sys_select; +#[cfg(feature = "epoll")] +pub use imp::io_mpx::{sys_epoll_create, sys_epoll_ctl, sys_epoll_wait}; +#[cfg(feature = "net")] +pub use imp::net::{ + sys_accept, sys_bind, sys_connect, sys_freeaddrinfo, sys_getaddrinfo, sys_getpeername, + sys_getsockname, sys_listen, sys_recv, sys_recvfrom, sys_send, sys_sendto, sys_shutdown, + sys_socket, +}; +#[cfg(feature = "pipe")] +pub use imp::pipe::sys_pipe; +#[cfg(feature = "multitask")] +pub use imp::pthread::mutex::{ + sys_pthread_mutex_init, sys_pthread_mutex_lock, sys_pthread_mutex_unlock, +}; +#[cfg(feature = "multitask")] +pub use imp::pthread::{sys_pthread_create, sys_pthread_exit, sys_pthread_join, sys_pthread_self}; diff --git a/api/arceos_posix_api/src/utils.rs b/api/arceos_posix_api/src/utils.rs new file mode 100644 index 0000000..664bf52 --- /dev/null +++ b/api/arceos_posix_api/src/utils.rs @@ -0,0 +1,61 @@ +#![allow(dead_code)] +#![allow(unused_macros)] + +use axerrno::{LinuxError, LinuxResult}; +use core::ffi::{c_char, CStr}; + +pub fn char_ptr_to_str<'a>(str: *const c_char) -> LinuxResult<&'a str> { + if str.is_null() { + Err(LinuxError::EFAULT) + } else { + unsafe { CStr::from_ptr(str) } + .to_str() + .map_err(|_| LinuxError::EINVAL) + } +} + +pub fn check_null_ptr(ptr: *const T) -> LinuxResult { + if ptr.is_null() { + Err(LinuxError::EFAULT) + } else { + Ok(()) + } +} + +pub fn check_null_mut_ptr(ptr: *mut T) -> LinuxResult { + if ptr.is_null() { + Err(LinuxError::EFAULT) + } else { + Ok(()) + } +} + +macro_rules! syscall_body { + ($fn: ident, $($stmt: tt)*) => {{ + #[allow(clippy::redundant_closure_call)] + let res = (|| -> axerrno::LinuxResult<_> { $($stmt)* })(); + match res { + Ok(_) | Err(axerrno::LinuxError::EAGAIN) => debug!(concat!(stringify!($fn), " => {:?}"), res), + Err(_) => info!(concat!(stringify!($fn), " => {:?}"), res), + } + match res { + Ok(v) => v as _, + Err(e) => { + -e.code() as _ + } + } + }}; +} + +macro_rules! syscall_body_no_debug { + ($($stmt: tt)*) => {{ + #[allow(clippy::redundant_closure_call)] + let res = (|| -> axerrno::LinuxResult<_> { $($stmt)* })(); + match res { + Ok(v) => v as _, + Err(e) => { + -e.code() as _ + } + } + }}; +} diff --git a/api/axfeat/.gitignore b/api/axfeat/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/api/axfeat/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/api/axfeat/Cargo.toml b/api/axfeat/Cargo.toml new file mode 100644 index 0000000..2feca28 --- /dev/null +++ b/api/axfeat/Cargo.toml @@ -0,0 +1,87 @@ +[package] +name = "axfeat" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Top-level feature selection for ArceOS" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/api/axfeat" +documentation = "https://rcore-os.github.io/arceos/axfeat/index.html" +keywords = ["Starry"] + +[features] +default = [] + +img = ["axruntime/img"] + +# Monolithic kernel +monolithic = ["axruntime/monolithic", "axhal/monolithic", "dep:axprocess", "axnet/monolithic"] + +# Multicore +smp = ["axhal/smp", "axruntime/smp", "spinlock/smp"] + +# Floating point/SIMD +fp_simd = ["axhal/fp_simd", "taskctx/fp_simd"] + +# Interrupts +irq = ["axhal/irq", "axruntime/irq", "axtask?/irq"] + +# Memory +alloc = ["axalloc", "axruntime/alloc"] +alloc-tlsf = ["axalloc/tlsf"] +alloc-slab = ["axalloc/slab"] +alloc-buddy = ["axalloc/buddy"] +paging = ["alloc", "axhal/paging", "axruntime/paging"] +tls = ["alloc", "axhal/tls", "axruntime/tls", "axtask?/tls"] + +# Multi-threading and scheduler +multitask = ["alloc", "axtask/multitask", "axsync/multitask", "axruntime/multitask"] +sched_fifo = ["axtask/sched_fifo"] +sched_rr = ["axtask/sched_rr", "irq"] +sched_cfs = ["axtask/sched_cfs", "irq"] + +# File system +fs = ["alloc", "paging", "axdriver/virtio-blk", "dep:axfs", "axruntime/fs"] # TODO: try to remove "paging" +fatfs = ["axfs/fatfs"] +lwext4_rust = ["axfs/lwext4_rust"] +myfs = ["axfs?/myfs"] +ext4_rs = ["axfs/ext4_rs"] +another_ext4 = ["axfs/another_ext4"] + +# Networking +net = ["alloc", "paging", "axdriver/virtio-net", "dep:axnet", "axruntime/net"] + +# Display +display = ["alloc", "paging", "axdriver/virtio-gpu", "dep:axdisplay", "axruntime/display"] + +# Device drivers +bus-mmio = ["axdriver?/bus-mmio"] +bus-pci = ["axdriver?/bus-pci"] +driver-ramdisk = ["axdriver?/ramdisk", "axfs?/use-ramdisk"] +driver-ixgbe = ["axdriver?/ixgbe"] +driver-e1000 = ["axdriver?/e1000"] +driver-bcm2835-sdhci = ["axdriver?/bcm2835-sdhci"] + +# Logging +log-level-off = ["axlog/log-level-off"] +log-level-error = ["axlog/log-level-error"] +log-level-warn = ["axlog/log-level-warn"] +log-level-info = ["axlog/log-level-info"] +log-level-debug = ["axlog/log-level-debug"] +log-level-trace = ["axlog/log-level-trace"] + +[dependencies] +axruntime = { workspace = true } +axhal = { workspace = true } +axlog = { workspace = true } +axalloc = { workspace = true, optional = true } +axdriver = { workspace = true, optional = true } +axfs = { workspace = true, optional = true } +axnet = { workspace = true, optional = true } +axdisplay = { workspace = true, optional = true } +axsync = { workspace = true, optional = true } +axtask = { workspace = true, optional = true } +axprocess = { workspace = true, optional = true } +spinlock = { git = "https://github.com/Starry-OS/spinlock.git", optional = true } +taskctx = { git = "https://github.com/Starry-OS/taskctx.git", optional = true } \ No newline at end of file diff --git a/api/axfeat/src/lib.rs b/api/axfeat/src/lib.rs new file mode 100644 index 0000000..6192852 --- /dev/null +++ b/api/axfeat/src/lib.rs @@ -0,0 +1,40 @@ +//! Top-level feature selection for [ArceOS]. +//! +//! # Cargo Features +//! +//! - CPU +//! - `smp`: Enable SMP (symmetric multiprocessing) support. +//! - `fp_simd`: Enable floating point and SIMD support. +//! - Interrupts: +//! - `irq`: Enable interrupt handling support. +//! - Memory +//! - `alloc`: Enable dynamic memory allocation. +//! - `alloc-tlsf`: Use the TLSF allocator. +//! - `alloc-slab`: Use the slab allocator. +//! - `alloc-buddy`: Use the buddy system allocator. +//! - `paging`: Enable page table manipulation. +//! - `tls`: Enable thread-local storage. +//! - Task management +//! - `multitask`: Enable multi-threading support. +//! - `sched_fifo`: Use the FIFO cooperative scheduler. +//! - `sched_rr`: Use the Round-robin preemptive scheduler. +//! - `sched_cfs`: Use the Completely Fair Scheduler (CFS) preemptive scheduler. +//! - Upperlayer stacks (fs, net, display) +//! - `fs`: Enable file system support. +//! - `myfs`: Allow users to define their custom filesystems to override the default. +//! - `net`: Enable networking support. +//! - `display`: Enable graphics support. +//! - Device drivers +//! - `bus-mmio`: Use device tree to probe all MMIO devices. +//! - `bus-pci`: Use PCI bus to probe all PCI devices. +//! - `driver-ramdisk`: Use the RAM disk to emulate the block device. +//! - `driver-ixgbe`: Enable the Intel 82599 10Gbit NIC driver. +//! - `driver-bcm2835-sdhci`: Enable the BCM2835 SDHCI driver (Raspberry Pi SD card). +//! - Logging +//! - `log-level-off`: Disable all logging. +//! - `log-level-error`, `log-level-warn`, `log-level-info`, `log-level-debug`, +//! `log-level-trace`: Keep logging only at the specified level or higher. +//! +//! [ArceOS]: https://github.com/rcore-os/arceos + +#![no_std] diff --git a/api/linux_syscall_api/.github/workflows/build.yml b/api/linux_syscall_api/.github/workflows/build.yml new file mode 100644 index 0000000..14e5d23 --- /dev/null +++ b/api/linux_syscall_api/.github/workflows/build.yml @@ -0,0 +1,14 @@ +name: Build CI + +on: [push, pull_request] + +jobs: + build-external: + uses: Starry-OS/Starry/.github/workflows/build.yml@main + with: + # The directory to Top test, which need to be empty or not exist in this repository + TopTestDirectory: StarryTest + CallerPackage: linux_syscall_api + CallerRepository: ${{ github.repository }} + CallerCommit: ${{ github.sha }} + TopBranch: main \ No newline at end of file diff --git a/api/linux_syscall_api/.github/workflows/docs.yml b/api/linux_syscall_api/.github/workflows/docs.yml new file mode 100644 index 0000000..c19da9f --- /dev/null +++ b/api/linux_syscall_api/.github/workflows/docs.yml @@ -0,0 +1,31 @@ +name: Build & Deploy docs + +on: [push, pull_request] + +env: + rust-toolchain: nightly-2024-05-02 + +jobs: + doc: + runs-on: ubuntu-latest + strategy: + fail-fast: false + permissions: + contents: write + env: + default-branch: ${{ format('refs/heads/{0}', github.event.repository.default_branch) }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.rust-toolchain }} + - name: Build docs + continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }} + run: make doc_check_missing + - name: Deploy to Github Pages + if: ${{ github.ref == env.default-branch }} + uses: JamesIves/github-pages-deploy-action@v4 + with: + single-commit: true + branch: gh-pages + folder: target/doc diff --git a/api/linux_syscall_api/.github/workflows/test.yml b/api/linux_syscall_api/.github/workflows/test.yml new file mode 100644 index 0000000..ccb7feb --- /dev/null +++ b/api/linux_syscall_api/.github/workflows/test.yml @@ -0,0 +1,18 @@ +name: Test CI + +on: [push, pull_request] + +env: + qemu-version: 8.2.0 + rust-toolchain: nightly-2024-05-02 + +jobs: + test-external: + uses: Starry-OS/Starry/.github/workflows/test.yml@main + with: + # The directory to Top test, which need to be empty or not exist in this repository + TopTestDirectory: StarryTest + CallerPackage: linux_syscall_api + CallerRepository: ${{ github.repository }} + CallerCommit: ${{ github.sha }} + TopBranch: main \ No newline at end of file diff --git a/api/linux_syscall_api/.gitignore b/api/linux_syscall_api/.gitignore new file mode 100644 index 0000000..73fab07 --- /dev/null +++ b/api/linux_syscall_api/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/api/linux_syscall_api/Cargo.toml b/api/linux_syscall_api/Cargo.toml new file mode 100644 index 0000000..75b1a90 --- /dev/null +++ b/api/linux_syscall_api/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "linux_syscall_api" +version = "0.1.0" +edition = "2021" +authors = ["Youjie Zheng "] +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +keywords = ["Starry", "linux-syscall"] +description = "A compatibility layer for linux syscalls on different operating systems written by Rust." + + + +[features] +default = ["monolithic"] + +monolithic = ["axfeat/monolithic", "irq", "paging", "fs", "multitask", "net"] + +# Interrupts +irq = ["axfeat/irq"] + +# Memory +paging = ["axfeat/paging"] + +# Multi-threading and scheduler +multitask = ["axfeat/multitask"] + +# Fs +fs = ["axfeat/fs"] + +ip = ["axnet/ip"] +net = ["ip", "axnet/monolithic"] + +[dependencies] +cfg-if = "1.0" +axlog = { workspace = true } +axfs = { workspace = true } +axruntime = { workspace = true } +axhal = { workspace = true } +axtask = { workspace = true } +axnet = { workspace = true } +axprocess = { workspace = true } +axsignal = { workspace = true } +axconfig = { workspace = true } +axsync = { workspace = true } +axmem = { workspace = true } +axfeat = { workspace = true } +axfutex = { workspace = true } + +lazy_init = { git = "https://github.com/Starry-OS/lazy_init.git" } +spinlock = { git = "https://github.com/Starry-OS/spinlock.git" } +axerrno = { git = "https://github.com/Starry-OS/axerrno.git" } +numeric-enum-macro = { git = "https://github.com/mexus/numeric-enum-macro" } +bitflags = "2.6" +rand = { version = "0.8.5", default-features = false, features = ["small_rng"] } +num_enum = { version = "0.5.11", default-features = false } \ No newline at end of file diff --git a/api/linux_syscall_api/Makefile b/api/linux_syscall_api/Makefile new file mode 100644 index 0000000..b08b6a0 --- /dev/null +++ b/api/linux_syscall_api/Makefile @@ -0,0 +1,22 @@ +ARCH ?= x86_64 + +# Target +ifeq ($(ARCH), x86_64) + TARGET := x86_64-unknown-none +else ifeq ($(ARCH), riscv64) + TARGET := riscv64gc-unknown-none-elf +else ifeq ($(ARCH), aarch64) + TARGET := aarch64-unknown-none +endif + + +define run_cmd + @printf '$(WHITE_C)$(1)$(END_C) $(GRAY_C)$(2)$(END_C)\n' + @$(1) $(2) +endef + + +doc_check_missing: + cargo update + cargo update --precise 0.4.19 log + $(call run_cmd,cargo doc,--no-deps --all-features --workspace) diff --git a/api/linux_syscall_api/README.md b/api/linux_syscall_api/README.md new file mode 100644 index 0000000..8dcfec5 --- /dev/null +++ b/api/linux_syscall_api/README.md @@ -0,0 +1,3 @@ +# Linux_syscall_api + +The project, written by Rust, is a compatibility layer for linux syscalls on different operating systems. diff --git a/api/linux_syscall_api/src/api.rs b/api/linux_syscall_api/src/api.rs new file mode 100644 index 0000000..286b766 --- /dev/null +++ b/api/linux_syscall_api/src/api.rs @@ -0,0 +1,143 @@ +extern crate alloc; +use alloc::string::{String, ToString}; +use alloc::sync::Arc; +use alloc::vec::Vec; +use axerrno::AxResult; +use axhal::{ + arch::{flush_tlb, write_page_table_root}, + KERNEL_PROCESS_ID, +}; +use axprocess::{wait_pid, yield_now_task, Process, PID2PC, TID2TASK}; +use axruntime::KERNEL_PAGE_TABLE; +use axtask::{Processor, TaskId}; + +use axfs::api::OpenFlags; + +use crate::syscall_task::{syscall_kill, syscall_tkill}; + +/// 在完成一次系统调用之后,恢复全局目录 +pub fn init_current_dir() { + axfs::api::set_current_dir("/").expect("reset current dir failed"); +} + +/// Flags for opening a file +pub type FileFlags = OpenFlags; + +/// 释放所有非内核进程 +pub fn recycle_user_process() { + // FIXME: It doesn't wake up the task blocked. + let tid_set: Vec = TID2TASK.lock().keys().cloned().collect(); + for tid in tid_set { + let task = Arc::clone(TID2TASK.lock().get(&tid).unwrap()); + let pid = task.get_process_id(); + if pid != KERNEL_PROCESS_ID { + // kill the process + let args: [usize; 6] = [tid as usize, 9, 0, 0, 0, 0]; + let _ = syscall_tkill(args); + + yield_now_task(); + } + } + + let kernel_process = Arc::clone(PID2PC.lock().get(&KERNEL_PROCESS_ID).unwrap()); + + let childrens_num = kernel_process.children.lock().len(); + + for index in 0..childrens_num { + let children = kernel_process.children.lock().get(index).unwrap().clone(); + let pid = children.pid(); + if pid != KERNEL_PROCESS_ID { + // kill the process + let args: [usize; 6] = [pid as usize, 9, 0, 0, 0, 0]; + let _ = syscall_kill(args); + + yield_now_task(); + } + } + + kernel_process + .children + .lock() + .retain(|x| x.pid() == KERNEL_PROCESS_ID || PID2PC.lock().contains_key(&x.pid())); + + TaskId::clear(); + unsafe { + write_page_table_root(KERNEL_PAGE_TABLE.root_paddr()); + flush_tlb(None); + }; + Processor::clean_all(); + init_current_dir(); +} + +/// To read a file with the given path +pub fn read_file(path: &str) -> Option { + axfs::api::read_to_string(path).ok() +} + +#[allow(unused)] +/// 分割命令行参数 +fn get_args(command_line: &[u8]) -> Vec { + let mut args = Vec::new(); + // 需要判断是否存在引号,如busybox_cmd.txt的第一条echo指令便有引号 + // 若有引号时,不能把引号加进去,同时要注意引号内的空格不算是分割的标志 + let mut in_quote = false; + let mut arg_start = 0; // 一个新的参数的开始位置 + for pos in 0..command_line.len() { + if command_line[pos] == b'\"' { + in_quote = !in_quote; + } + if command_line[pos] == b' ' && !in_quote { + // 代表要进行分割 + // 首先要防止是否有空串 + if arg_start != pos { + args.push( + core::str::from_utf8(&command_line[arg_start..pos]) + .unwrap() + .to_string(), + ); + } + arg_start = pos + 1; + } + } + // 最后一个参数 + if arg_start != command_line.len() { + args.push( + core::str::from_utf8(&command_line[arg_start..]) + .unwrap() + .to_string(), + ); + } + args +} + +/// To run a testcase with the given name and environment variables, which will be used in initproc +pub fn run_testcase(testcase: &str, envs: Vec) -> AxResult<()> { + axlog::ax_println!("Running testcase: {}", testcase); + let args = get_args(testcase.as_bytes()); + let mut args_vec: Vec = Vec::new(); + for arg in args { + args_vec.push(arg.to_string()); + } + + let user_process = Process::init(args_vec, &envs)?; + let now_process_id = user_process.get_process_id() as i32; + let mut exit_code = 0; + loop { + if unsafe { wait_pid(now_process_id, &mut exit_code as *mut i32) }.is_ok() { + break; + } + yield_now_task(); + } + recycle_user_process(); + // unsafe { + // write_page_table_root(KERNEL_PAGE_TABLE.root_paddr()); + // flush_tlb(None); + // }; + // axlog::ax_println!( + // "Testcase {} finished with exit code {}", + // testcase, + // exit_code + // ); + + Ok(()) +} diff --git a/api/linux_syscall_api/src/ctypes.rs b/api/linux_syscall_api/src/ctypes.rs new file mode 100644 index 0000000..160b2f6 --- /dev/null +++ b/api/linux_syscall_api/src/ctypes.rs @@ -0,0 +1,713 @@ +use axhal::{ + paging::MappingFlags, + time::{current_time_nanos, nanos_to_ticks, MICROS_PER_SEC, NANOS_PER_MICROS, NANOS_PER_SEC}, +}; +use bitflags::*; +use core::panic; + +/// a flag used in sys_dup3 +#[allow(dead_code)] +pub const O_CLOEXEC: u32 = 524288; + +/// The nano seconds number per second +pub const NSEC_PER_SEC: usize = 1_000_000_000; +bitflags! { + /// 指定 sys_wait4 的选项 + pub struct WaitFlags: u32 { + /// 不挂起当前进程,直接返回 + const WNOHANG = 1 << 0; + /// 报告已执行结束的用户进程的状态 + const WIMTRACED = 1 << 1; + /// 报告还未结束的用户进程的状态 + const WCONTINUED = 1 << 3; + /// Wait for any child + const WALL = 1 << 30; + /// Wait for cloned process + const WCLONE = 1 << 31; + } +} +/// sys_times 中指定的结构体类型 +#[repr(C)] +pub struct Tms { + /// 进程用户态执行时间,单位为us + pub tms_utime: usize, + /// 进程内核态执行时间,单位为us + pub tms_stime: usize, + /// 子进程用户态执行时间和,单位为us + pub tms_cutime: usize, + /// 子进程内核态执行时间和,单位为us + pub tms_cstime: usize, +} + +/// sys_gettimeofday 中指定的类型 +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct TimeVal { + /// seconds + pub sec: usize, + /// microseconds + pub usec: usize, +} + +impl TimeVal { + /// turn the TimeVal to nano seconds + pub fn turn_to_nanos(&self) -> usize { + self.sec * NANOS_PER_SEC as usize + self.usec * NANOS_PER_MICROS as usize + } + + /// create a TimeVal from nano seconds + pub fn from_micro(micro: usize) -> Self { + TimeVal { + sec: micro / (MICROS_PER_SEC as usize), + usec: micro % (MICROS_PER_SEC as usize), + } + } + + /// turn the TimeVal to cpu ticks, which is related to cpu frequency + pub fn turn_to_ticks(&self) -> u64 { + (self.sec * axconfig::TIMER_FREQUENCY) as u64 + + nanos_to_ticks((self.usec as u64) * NANOS_PER_MICROS) + } +} + +/// sys_gettimer / sys_settimer 指定的类型,用户输入输出计时器 +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct ITimerVal { + /// The cycle of the timer + pub it_interval: TimeVal, + /// The remaining time of the timer + pub it_value: TimeVal, +} + +/// sys_nanosleep指定的结构体类型 +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct TimeSecs { + /// seconds + pub tv_sec: usize, + /// nanoseconds + pub tv_nsec: usize, +} +/// 当 nsec 为这个特殊值时,指示修改时间为现在 +pub const UTIME_NOW: usize = 0x3fffffff; +/// 当 nsec 为这个特殊值时,指示不修改时间 +pub const UTIME_OMIT: usize = 0x3ffffffe; +impl TimeSecs { + /// 根据当前的时间构造一个 TimeSecs + pub fn now() -> Self { + let nano = current_time_nanos() as usize; + let tv_sec = nano / NSEC_PER_SEC; + let tv_nsec = nano - tv_sec * NSEC_PER_SEC; + TimeSecs { tv_sec, tv_nsec } + } + + /// turn the TimeSecs to nano seconds + pub fn turn_to_nanos(&self) -> usize { + self.tv_sec * NSEC_PER_SEC + self.tv_nsec + } + + /// turn the TimeSecs to cpu ticks, which is related to cpu frequency + pub fn get_ticks(&self) -> usize { + self.tv_sec * axconfig::TIMER_FREQUENCY + (nanos_to_ticks(self.tv_nsec as u64) as usize) + } + + /// set the Timesecs to the given time + /// + /// If the nsec is UTIME_NOW, set the time to now + /// + /// If the nsec is UTIME_OMIT, ignore the setting operation + pub fn set_as_utime(&mut self, other: &TimeSecs) { + match other.tv_nsec { + UTIME_NOW => { + *self = TimeSecs::now(); + } // 设为当前时间 + UTIME_OMIT => {} // 忽略 + _ => { + *self = *other; + } // 设为指定时间 + } + } +} + +bitflags! { + #[derive(Debug)] + /// 指定 mmap 的选项 + pub struct MMAPPROT: u32 { + /// 区域内容可读取 + const PROT_READ = 1 << 0; + /// 区域内容可修改 + const PROT_WRITE = 1 << 1; + /// 区域内容可执行 + const PROT_EXEC = 1 << 2; + } +} + +impl From for MappingFlags { + fn from(value: MMAPPROT) -> Self { + let mut flags = MappingFlags::USER; + if value.contains(MMAPPROT::PROT_READ) { + flags |= MappingFlags::READ; + } + if value.contains(MMAPPROT::PROT_WRITE) { + flags |= MappingFlags::WRITE; + } + if value.contains(MMAPPROT::PROT_EXEC) { + flags |= MappingFlags::EXECUTE; + } + flags + } +} + +bitflags! { + #[derive(Debug)] + /// 指定 mmap 的选项 + pub struct MMAPFlags: u32 { + /// 对这段内存的修改是共享的 + const MAP_SHARED = 1 << 0; + /// 对这段内存的修改是私有的 + const MAP_PRIVATE = 1 << 1; + // 以上两种只能选其一 + + /// 取消原来这段位置的映射,即一定要映射到指定位置 + const MAP_FIXED = 1 << 4; + /// 不映射到实际文件 + const MAP_ANONYMOUS = 1 << 5; + /// 映射时不保留空间,即可能在实际使用mmp出来的内存时内存溢出 + const MAP_NORESERVE = 1 << 14; + /// Allocation is for a stack. + const MAP_STACK = 0x20000; + } +} + +bitflags! { + #[derive(Debug)] + /// 指定 mremap 的选项 + pub struct MREMAPFlags: u32 { + /// 允许将映射重新定位到新地址 + const MREMAP_MAYMOVE = 1 << 0; + /// 指定映射必须移动到的页面对齐地址,必须和MREMAP_MAYMOVE一起使用 + const MREMAP_FIXED = 1 << 1; + + /// 将映射重新映射到新地址,但不会取消旧地址的映射,必须和MREMAP_MAYMOVE一起使用 + const MREMAP_DONTUNMAP = 1 << 2; + } +} + +/// sys_uname 中指定的结构体类型 +#[repr(C)] +pub struct UtsName { + /// 系统名称 + pub sysname: [u8; 65], + /// 网络上的主机名称 + pub nodename: [u8; 65], + /// 发行编号 + pub release: [u8; 65], + /// 版本 + pub version: [u8; 65], + /// 硬件类型 + pub machine: [u8; 65], + /// 域名 + pub domainname: [u8; 65], +} + +impl Default for UtsName { + fn default() -> Self { + Self { + sysname: Self::from_str("Starry"), + nodename: Self::from_str("Starry - machine[0]"), + release: Self::from_str("10.0.0"), + version: Self::from_str("10.0.0"), + machine: Self::from_str("RISC-V 64 on SIFIVE FU740"), + domainname: Self::from_str("https://github.com/Azure-stars/arceos"), + } + } +} + +impl UtsName { + fn from_str(info: &str) -> [u8; 65] { + let mut data: [u8; 65] = [0; 65]; + data[..info.len()].copy_from_slice(info.as_bytes()); + data + } +} + +/// specifies the size in bytes of the signal sets in set and oldset, which is equal to sizeof(kernel_sigset_t) +pub const SIGSET_SIZE_IN_BYTE: usize = 8; + +/// sys_sigprocmask 中指定的结构体类型 +pub enum SigMaskFlag { + /// add the mask to the block mask + Block = 0, + /// unblock the mask from the block mask + Unblock = 1, + /// set the mask as the new block mask + Setmask = 2, +} + +impl SigMaskFlag { + /// turn a usize to SigMaskFlag + pub fn from(value: usize) -> Self { + match value { + 0 => SigMaskFlag::Block, + 1 => SigMaskFlag::Unblock, + 2 => SigMaskFlag::Setmask, + _ => panic!("SIG_MASK_FLAG::from: invalid value"), + } + } +} + +/// sys_prlimit64 使用的数组 +#[repr(C)] +pub struct RLimit { + /// 软上限 + pub rlim_cur: u64, + /// 硬上限 + pub rlim_max: u64, +} +// sys_prlimit64 使用的选项 +/// 用户栈大小 +pub const RLIMIT_STACK: i32 = 3; +/// 可以打开的 fd 数 +pub const RLIMIT_NOFILE: i32 = 7; +/// 用户地址空间的最大大小 +pub const RLIMIT_AS: i32 = 9; + +/// robust list +#[repr(C)] +pub struct RobustList { + head: usize, + off: usize, + pending: usize, +} + +#[derive(Debug)] +#[repr(C)] +pub struct MessageHeader { + /// Pointer to the socket address structure. + pub name: *mut u8, + pub name_len: u32, + pub iovec: *mut IoVec, + pub iovec_len: i32, + pub control: *const u8, + pub control_len: u32, + pub flags: i32, +} + +/// readv/writev使用的结构体 +#[repr(C)] +pub struct IoVec { + /// base address of the buffer + pub base: *mut u8, + /// length of the buffer + pub len: usize, +} + +/// mask for futex_bitset, which means match any bit +#[allow(unused)] +pub(crate) const FUTEX_BITSET_MATCH_ANY: u32 = 0xffffffff; + +bitflags::bitflags! { + /// 对 futex 的操作 + #[derive(PartialEq, Eq)] + pub struct FutexFlags: i32 { + /// 检查用户地址 uaddr 处的值。如果不是要求的值则等待 wake + const WAIT = 0; + /// 唤醒最多 val 个在等待 uaddr 位置的线程。 + const WAKE = 1; + /// 将等待 uaddr 的线程移动到 uaddr2 + const REQUEUE = 3; + /// WAIT_BITSET + const WAIT_BITSET = 9; + /// WAKT_BITSET + const WAKE_BITSET = 10; + /// FUTEX_PRIVATE_FLAG + const FUTEX_PRIVATE_FLAG = 128; + } +} + +numeric_enum_macro::numeric_enum! { + #[repr(usize)] + #[allow(non_camel_case_types)] + #[derive(Debug)] + /// sys_fcntl64 使用的选项 + pub enum Fcntl64Cmd { + /// 复制这个 fd,相当于 sys_dup + F_DUPFD = 0, + /// 获取 cloexec 信息,即 exec 成功时是否删除该 fd + F_GETFD = 1, + /// 设置 cloexec 信息,即 exec 成功时删除该 fd + F_SETFD = 2, + /// 获取 flags 信息 + F_GETFL = 3, + /// 设置 flags 信息 + F_SETFL = 4, + /// 复制 fd,然后设置 cloexec 信息,即 exec 成功时删除该 fd + F_DUPFD_CLOEXEC = 1030, + } +} + +/// syscall_info 用到的 结构体 +#[repr(C)] +#[derive(Debug)] +pub struct SysInfo { + /// 启动时间(以秒计) + pub uptime: isize, + /// 1 / 5 / 15 分钟平均负载 + pub loads: [usize; 3], + /// 内存总量,单位为 mem_unit Byte(见下) + pub totalram: usize, + /// 当前可用内存,单位为 mem_unit Byte(见下) + pub freeram: usize, + /// 共享内存大小,单位为 mem_unit Byte(见下) + pub sharedram: usize, + /// 用于缓存的内存大小,单位为 mem_unit Byte(见下) + pub bufferram: usize, + /// swap空间大小,即主存上用于替换内存中非活跃部分的空间大小,单位为 mem_unit Byte(见下) + pub totalswap: usize, + /// 可用的swap空间大小,单位为 mem_unit Byte(见下) + pub freeswap: usize, + /// 当前进程数,单位为 mem_unit Byte(见下) + pub procs: u16, + /// 高地址段的内存大小,单位为 mem_unit Byte(见下) + pub totalhigh: usize, + /// 可用的高地址段的内存大小,单位为 mem_unit Byte(见下) + pub freehigh: usize, + /// 指定 sys_info 的结构中用到的内存值的单位。 + /// 如 mem_unit = 1024, totalram = 100, 则指示总内存为 100K + pub mem_unit: u32, +} + +/// sys_getrusage 用到的选项 +#[allow(non_camel_case_types)] +pub enum RusageFlags { + /// 获取当前进程的资源统计 + RUSAGE_SELF = 0, + /// 获取当前进程的所有 **已结束并等待资源回收的** 子进程资源统计 + RUSAGE_CHILDREN = -1, + /// 获取当前线程的资源统计 + RUSAGE_THREAD = 1, +} + +impl RusageFlags { + /// Create a RusageFlags from a i32 value + pub fn from(val: i32) -> Option { + match val { + 0 => Some(RusageFlags::RUSAGE_SELF), + -1 => Some(RusageFlags::RUSAGE_CHILDREN), + 1 => Some(RusageFlags::RUSAGE_THREAD), + _ => None, + } + } +} + +#[allow(unused)] +/// sched_setscheduler时指定子进程是否继承父进程的调度策略 +pub const SCHED_RESET_ON_FORK: usize = 0x40000000; + +#[repr(C)] +#[derive(Clone, Copy)] +/// sys_sched_setparam 使用的结构体 +pub struct SchedParam { + /// The scheduling policy of the param argument + pub sched_priority: usize, +} + +numeric_enum_macro::numeric_enum! { + #[repr(usize)] + #[allow(non_camel_case_types)] + #[derive(PartialEq,Eq,Debug)] + /// sys_fcntl64 使用的选项 + pub enum ClockId { + /// real-time clock + CLOCK_REALTIME = 0, + /// monotonic clock + CLOCK_MONOTONIC = 1, + } +} + +/// 目录项 +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct DirEnt { + /// 索引结点号 + pub d_ino: u64, + /// 到下一个dirent的偏移 + pub d_off: u64, + /// 当前dirent的长度 + pub d_reclen: u16, + /// 文件类型 + pub d_type: u8, + /// 文件名 + pub d_name: [u8; 0], +} + +#[allow(unused)] +/// 目录项类型 +pub enum DirEntType { + /// 未知类型文件 + Unknown = 0, + /// 先进先出的文件/队列 + Fifo = 1, + /// 字符设备 + Chr = 2, + /// 目录 + Dir = 4, + /// 块设备 + Blk = 6, + /// 常规文件 + Reg = 8, + /// 符号链接 + Lnk = 10, + /// socket + Socket = 12, + /// whiteout + Wht = 14, +} + +/// Structure describing a generic socket address in libc. +#[repr(C)] +pub struct LibcSocketAddr { + common: u16, + sa_data: [u8; 14], +} + +impl DirEnt { + /// 定长部分大小 + pub fn fixed_size() -> usize { + 8 + 8 + 2 + 1 + } + /// 设置定长部分 + pub fn set_fixed_part(&mut self, ino: u64, off: u64, reclen: usize, type_: DirEntType) { + self.d_ino = ino; + self.d_off = off; + self.d_reclen = reclen as u16; + self.d_type = type_ as u8; + } +} +bitflags! { + /// sys_renameat2 用到的选项 + pub struct RenameFlags: u32 { + /// Nothing + const NONE = 0; + /// 不要替换目标位置的文件,如果预定位置已经有文件,不要删除它 + const NOREPLACE = 1 << 0; + /// 交换原位置和目标位置的文件 + const EXCHANGE = 1 << 1; + /// 替换后在原位置放一个 "whiteout" 类型对象,仅在一些文件系统中有用,这里不考虑 + const WHITEOUT = 1 << 2; + } +} + +/// 文件系统的属性 +/// 具体参数定义信息来自 `https://man7.org/linux/man-pages/man2/statfs64.2.html` +#[repr(C)] +#[derive(Debug)] +pub struct FsStat { + /// 是个 magic number,每个知名的 fs 都各有定义,但显然我们没有 + pub f_type: i64, + /// 最优传输块大小 + pub f_bsize: i64, + /// 总的块数 + pub f_blocks: u64, + /// 还剩多少块未分配 + pub f_bfree: u64, + /// 对用户来说,还有多少块可用 + pub f_bavail: u64, + /// 总的 inode 数 + pub f_files: u64, + /// 空闲的 inode 数 + pub f_ffree: u64, + /// 文件系统编号,但实际上对于不同的OS差异很大,所以不会特地去用 + pub f_fsid: [i32; 2], + /// 文件名长度限制,这个OS默认FAT已经使用了加长命名 + pub f_namelen: isize, + /// 片大小 + pub f_frsize: isize, + /// 一些选项,但其实也没用到 + pub f_flags: isize, + /// 空余 padding + pub f_spare: [isize; 4], +} + +/// 获取一个基础的fsstat +pub fn get_fs_stat() -> FsStat { + FsStat { + f_type: 0, + f_bsize: 1024, + f_blocks: 0x4000_0000 / 512, + f_bfree: 1, + f_bavail: 1, + f_files: 1, + f_ffree: 1, + f_fsid: [0, 0], + f_namelen: 256, + f_frsize: 0x1000, + f_flags: 0, + f_spare: [0, 0, 0, 0], + } +} + +/// statx - get file status (extended) +/// Standard C library (libc, -lc) +/// https://man7.org/linux/man-pages/man2/statx.2.html +#[repr(C)] +#[derive(Debug, Default)] +pub struct FsStatx { + pub stx_mask: u32, + pub stx_blksize: u32, + pub stx_attributes: u64, + pub stx_nlink: u32, + pub stx_uid: u32, + pub stx_gid: u32, + pub stx_mode: u16, + pub stx_ino: u64, + pub stx_size: u64, + pub stx_blocks: u64, + pub stx_attributes_mask: u64, + pub stx_atime: FsStatxTimestamp, + pub stx_btime: FsStatxTimestamp, + pub stx_ctime: FsStatxTimestamp, + pub stx_mtime: FsStatxTimestamp, + pub stx_rdev_major: u32, + pub stx_rdev_minor: u32, + pub stx_dev_major: u32, + pub stx_dev_minor: u32, + pub stx_mnt_id: u64, + pub stx_dio_mem_align: u32, + pub stx_dio_offset_align: u32, +} + +impl FsStatx { + pub fn new() -> Self { + Self { + stx_mask: 0, + stx_blksize: 1024, + stx_attributes: 0, + stx_nlink: 0, + stx_uid: 0, + stx_gid: 0, + stx_mode: 0, + stx_ino: 0, + stx_size: 0, + stx_blocks: 0x4000_0000 / 512, + stx_attributes_mask: 0, + stx_atime: FsStatxTimestamp::new(), + stx_btime: FsStatxTimestamp::new(), + stx_ctime: FsStatxTimestamp::new(), + stx_mtime: FsStatxTimestamp::new(), + stx_rdev_major: 0, + stx_rdev_minor: 0, + stx_dev_major: 0, + stx_dev_minor: 0, + stx_mnt_id: 0, + stx_dio_mem_align: 0, + stx_dio_offset_align: 0, + } + } +} + +#[repr(C)] +#[derive(Debug, Default)] +pub struct FsStatxTimestamp { + pub tv_sec: i64, + pub tv_nsec: u32, +} + +impl FsStatxTimestamp { + pub fn new() -> Self { + Self { + tv_sec: 0, + tv_nsec: 0, + } + } +} + +bitflags! { + /// 指定 st_mode 的选项 + pub struct StMode: u32 { + /// regular file + const S_IFREG = 1 << 15; + /// directory + const S_IFDIR = 1 << 14; + /// character device + const S_IFCHR = 1 << 13; + /// 是否设置 uid/gid/sticky + //const S_ISUID = 1 << 14; + //const S_ISGID = 1 << 13; + //const S_ISVTX = 1 << 12; + /// user-read permission + const S_IRUSR = 1 << 8; + /// user-write permission + const S_IWUSR = 1 << 7; + /// user-execute permission + const S_IXUSR = 1 << 6; + /// group-read permission + const S_IRGRP = 1 << 5; + /// group-write permission + const S_IWGRP = 1 << 4; + /// group-execute permission + const S_IXGRP = 1 << 3; + /// other-read permission + const S_IROTH = 1 << 2; + /// other-write permission + const S_IWOTH = 1 << 1; + /// other-execute permission + const S_IXOTH = 1 << 0; + /// exited-user-process status + const WIMTRACED = 1 << 1; + /// continued-process status + const WCONTINUED = 1 << 3; + } +} +/// 文件类型,输入 IFCHR / IFDIR / IFREG 等具体类型, +/// 输出这些类型加上普遍的文件属性后得到的 mode 参数 +pub fn normal_file_mode(file_type: StMode) -> StMode { + file_type | StMode::S_IWUSR | StMode::S_IRUSR | StMode::S_IRGRP | StMode::S_IROTH +} + +/// prctl 中 PR_NAME_SIZE 要求的缓冲区长度 +#[allow(unused)] +pub const PR_NAME_SIZE: usize = 16; + +numeric_enum_macro::numeric_enum! { + #[repr(usize)] + #[allow(missing_docs)] + #[allow(non_camel_case_types)] + #[derive(Eq, PartialEq, Debug, Copy, Clone)] + /// syscall_prctl的结构体 + pub enum PrctlOption { + /// set the name of the process + PR_SET_NAME = 15, + /// get the name of the process + PR_GET_NAME = 16, + } +} + +#[repr(C)] +#[derive(Debug)] +/// sys_clone3 中使用的结构体 +pub struct CloneArgs { + /// 符号位,对应 axprocess 的 CloneFlags + pub flags: u64, + /// 存储 PID 的文件描述符。Starry 暂未使用 + pub pidfd: u64, + /// 同 sys_clone 的 ctid + pub child_tid: u64, + /// 同 sys_clone 的 ptid + pub parent_tid: u64, + /// 子进程退出时向父进程发送的信号 + pub exit_signal: u64, + /// 同 sys_clone 的 stack + pub stack: u64, + /// 指定子进程的栈的大小 + pub stack_size: u64, + /// 同 sys_clone 的 tls + pub tls: u64, + /// 该信息 Starry 暂未支持 + pub set_tid: u64, + /// 该信息 Starry 暂未支持 + pub set_tid_size: u64, + /// 该信息 Starry 暂未支持 + pub cgroup: u64, +} diff --git a/api/linux_syscall_api/src/lib.rs b/api/linux_syscall_api/src/lib.rs new file mode 100644 index 0000000..9166d14 --- /dev/null +++ b/api/linux_syscall_api/src/lib.rs @@ -0,0 +1,42 @@ +//! This crate provides a safe interface to the Linux syscall API for Starry modules. +#![cfg_attr(all(not(test), not(doc)), no_std)] +#![feature(stmt_expr_attributes)] +mod ctypes; +use ctypes::*; +mod syscall; +mod syscall_fs; +mod syscall_mem; +mod syscall_net; +mod syscall_task; + +pub use axfs::api::{File, OpenFlags}; +pub use axprocess::link::{create_link, FilePath}; +pub use syscall_fs::new_file; + +mod api; +pub use api::*; + +// These interfaces is exposed to the trap handler +pub mod trap; + +extern crate alloc; +/// 需要手动引入这个库,否则会报错:`#[panic_handler]` function required, but not found. +extern crate axruntime; + +/// The error of a syscall, which is a `LinuxError` +pub type SyscallError = axerrno::LinuxError; + +/// The result of a syscall +/// +/// * `Ok(x)` - The syscall is successful, and the return value is `x` +/// +/// * `Err(error)` - The syscall failed, and the error is related to `linux_error` +pub type SyscallResult = Result; + +/// Accept the result of a syscall, and return the isize to the user +pub(crate) fn deal_result(result: SyscallResult) -> isize { + match result { + Ok(x) => x, + Err(error) => -(error.code() as isize), + } +} diff --git a/api/linux_syscall_api/src/syscall.rs b/api/linux_syscall_api/src/syscall.rs new file mode 100644 index 0000000..bce8e81 --- /dev/null +++ b/api/linux_syscall_api/src/syscall.rs @@ -0,0 +1,60 @@ +use crate::{deal_result, SyscallResult}; +use axlog::info; + +#[no_mangle] +pub fn syscall(syscall_id: usize, args: [usize; 6]) -> isize { + #[allow(unused_mut, unused_assignments)] + let mut ans: Option = None; + + if let Ok(net_syscall_id) = crate::syscall_net::NetSyscallId::try_from(syscall_id) { + info!( + "[syscall] id = {:#?}, args = {:?}, entry", + net_syscall_id, args + ); + + (#[allow(unused_assignments)] + ans) = Some(crate::syscall_net::net_syscall(net_syscall_id, args)); + } + + if let Ok(mem_syscall_id) = crate::syscall_mem::MemSyscallId::try_from(syscall_id) { + info!( + "[syscall] id = {:#?}, args = {:?}, entry", + mem_syscall_id, args + ); + (#[allow(unused_assignments)] + ans) = Some(crate::syscall_mem::mem_syscall(mem_syscall_id, args)); + } + + if let Ok(fs_syscall_id) = crate::syscall_fs::FsSyscallId::try_from(syscall_id) { + if syscall_id != 281 { + info!( + "[syscall] id = {:#?}, args = {:?}, entry", + fs_syscall_id, args + ); + } + + (#[allow(unused_assignments)] + ans) = Some(crate::syscall_fs::fs_syscall(fs_syscall_id, args)); + } + + if let Ok(task_syscall_id) = crate::syscall_task::TaskSyscallId::try_from(syscall_id) { + if syscall_id != 228 { + info!( + "[syscall] id = {:#?}, args = {:?}, entry", + task_syscall_id, args + ); + } + + (#[allow(unused_assignments)] + ans) = Some(crate::syscall_task::task_syscall(task_syscall_id, args)); + } + + if ans.is_none() { + panic!("unknown syscall id: {}", syscall_id); + } + let ans = deal_result(ans.unwrap()); + if syscall_id != 281 && syscall_id != 228 { + info!("[syscall] id = {},return {}", syscall_id, ans); + } + ans +} diff --git a/api/linux_syscall_api/src/syscall_fs/ctype/dir.rs b/api/linux_syscall_api/src/syscall_fs/ctype/dir.rs new file mode 100644 index 0000000..c7fb904 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_fs/ctype/dir.rs @@ -0,0 +1,84 @@ +use crate::{normal_file_mode, StMode}; +extern crate alloc; +use alloc::string::{String, ToString}; +use axerrno::{AxError, AxResult}; +use axfs::api::{self, FileIO, FileIOType, Kstat, OpenFlags, SeekFrom}; +use axlog::debug; + +/// 目录描述符 +pub struct DirDesc { + /// 目录 + pub dir_path: String, +} + +/// 目录描述符的实现 +impl DirDesc { + /// 创建一个新的目录描述符 + pub fn new(path: String) -> Self { + Self { dir_path: path } + } +} + +/// 为DirDesc实现FileIO trait +impl FileIO for DirDesc { + fn read(&self, _: &mut [u8]) -> AxResult { + Err(AxError::IsADirectory) + } + fn write(&self, _: &[u8]) -> AxResult { + Err(AxError::IsADirectory) + } + fn flush(&self) -> AxResult { + Err(AxError::IsADirectory) + } + fn seek(&self, _: SeekFrom) -> AxResult { + Err(AxError::IsADirectory) + } + fn get_type(&self) -> FileIOType { + FileIOType::DirDesc + } + fn readable(&self) -> bool { + false + } + fn writable(&self) -> bool { + false + } + fn executable(&self) -> bool { + false + } + fn get_path(&self) -> String { + self.dir_path.to_string().clone() + } + + fn get_stat(&self) -> AxResult { + let kstat = Kstat { + st_dev: 1, + st_ino: 0, + st_mode: normal_file_mode(StMode::S_IFDIR).bits(), + st_nlink: 1, + st_uid: 0, + st_gid: 0, + st_rdev: 0, + _pad0: 0, + st_size: 0, + st_blksize: 0, + _pad1: 0, + st_blocks: 0, + st_atime_sec: 0, + st_atime_nsec: 0, + st_mtime_sec: 0, + st_mtime_nsec: 0, + st_ctime_sec: 0, + st_ctime_nsec: 0, + }; + Ok(kstat) + } +} + +pub fn new_dir(dir_path: String, _flags: OpenFlags) -> AxResult { + debug!("Into function new_dir, dir_path: {}", dir_path); + if !api::path_exists(dir_path.as_str()) { + // api::create_dir_all(dir_path.as_str())?; + api::create_dir(dir_path.as_str())?; + } + Ok(DirDesc::new(dir_path)) +} diff --git a/api/linux_syscall_api/src/syscall_fs/ctype/epoll.rs b/api/linux_syscall_api/src/syscall_fs/ctype/epoll.rs new file mode 100644 index 0000000..8e705f6 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_fs/ctype/epoll.rs @@ -0,0 +1,273 @@ +use axhal::time::current_ticks; +use bitflags::bitflags; +extern crate alloc; +use alloc::{ + collections::{BTreeMap, BTreeSet}, + sync::Arc, + vec::Vec, +}; +use axerrno::{AxError, AxResult}; + +use axfs::api::{FileIO, FileIOType, OpenFlags, SeekFrom}; + +use crate::SyscallError; +use axprocess::{current_process, yield_now_task}; +use axsync::Mutex; + +bitflags! { + /// 定义epoll事件的类别 + #[derive(Clone, Copy,Debug)] + pub struct EpollEventType: u32{ + const EPOLLIN = 0x001; + const EPOLLOUT = 0x004; + const EPOLLERR = 0x008; + const EPOLLHUP = 0x010; + const EPOLLPRI = 0x002; + const EPOLLRDNORM = 0x040; + const EPOLLRDBAND = 0x080; + const EPOLLWRNORM = 0x100; + const EPOLLWRBAND= 0x200; + const EPOLLMSG = 0x400; + const EPOLLRDHUP = 0x2000; + const EPOLLEXCLUSIVE = 0x1000_0000; + const EPOLLWAKEUP = 0x2000_0000; + const EPOLLONESHOT = 0x4000_0000; + const EPOLLET = 0x8000_0000; + } +} + +#[repr(packed(1))] +#[derive(Debug, Clone, Copy)] +/// 定义一个epoll事件 +pub struct EpollEvent { + /// 事件类型 + pub event_type: EpollEventType, + /// 事件中使用到的数据,如fd等 + pub data: u64, +} + +numeric_enum_macro::numeric_enum! { + #[repr(i32)] + #[derive(Clone, Copy, Debug)] + pub enum EpollCtl { + /// 添加一个文件对应的事件 + ADD = 1, + /// 删除一个文件对应的事件 + DEL = 2, + /// 修改一个文件对应的事件 + MOD = 3, + } +} + +pub struct EpollFile { + /// 定义内部可变变量 + /// 由于存在clone,所以要用arc指针包围 + pub inner: Arc>, + + /// 文件打开的标志位 + pub flags: Mutex, +} + +pub struct EpollFileInner { + /// 监控的所有事件,通过map来进行映射,根据fd找到对应的event + monitor_list: BTreeMap, + /// 响应的事件集 + _response_list: BTreeSet, +} + +impl EpollFile { + /// 新建一个epoll文件 + pub fn new() -> Self { + Self { + inner: Arc::new(Mutex::new(EpollFileInner { + monitor_list: BTreeMap::new(), + _response_list: BTreeSet::new(), + })), + flags: Mutex::new(OpenFlags::empty()), + } + } + + /// 获取另外一份epoll文件,存储在fd manager中 + /// 这是对Arc的clone,即获取指针副本 + pub fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + flags: Mutex::new(*self.flags.lock()), + } + } + + /// 判断fd是否在monitor_list中 + pub fn contains(&self, fd: i32) -> bool { + let inner = self.inner.lock(); + if inner.monitor_list.contains_key(&fd) { + return true; + } + false + } + + /// 控制指定的事件,改变其对应的事件内容 + /// + /// 成功返回0,错误返回对应的编号 + pub fn epoll_ctl( + &self, + op: EpollCtl, + fd: i32, + event: EpollEvent, + ) -> Result { + let mut inner = self.inner.lock(); + match op { + // 添加事件 + EpollCtl::ADD => { + if inner.monitor_list.contains_key(&fd) { + // return Err(SyscallError::EEXIST); + // TODO : fd close callback ? + inner.monitor_list.insert(fd, event); + } + inner.monitor_list.insert(fd, event); + } + // 删除事件 + EpollCtl::DEL => { + if !inner.monitor_list.contains_key(&fd) { + return Err(SyscallError::ENOENT); + } + inner.monitor_list.remove(&fd); + } + // 修改对应事件 + EpollCtl::MOD => { + // 对于不存在的事件,返回错误 + // 即modify要求原先文件存在对应事件,才能进行“修改” + if !inner.monitor_list.contains_key(&fd) { + return Err(SyscallError::ENOENT); + } + inner.monitor_list.insert(fd, event); + } + } + Ok(0) + } + + /// 实现epoll wait,在规定超时时间内收集达到触发条件的事件 + /// + /// 实现原理和ppoll很像 + pub fn epoll_wait(&self, expire_time: usize) -> AxResult> { + let mut ret_events = Vec::new(); + loop { + let current_process = current_process(); + for (fd, req_event) in self.inner.lock().monitor_list.iter() { + let fd_table = current_process.fd_manager.fd_table.lock(); + if let Some(file) = &fd_table[*fd as usize] { + let mut ret_event_type = EpollEventType::empty(); + // read unalign: copy the field contents to a local variable + let req_type = req_event.event_type; + if file.is_hang_up() { + ret_event_type |= EpollEventType::EPOLLHUP; + } + if file.in_exceptional_conditions() { + ret_event_type |= EpollEventType::EPOLLERR; + } + if file.ready_to_read() && req_type.contains(EpollEventType::EPOLLIN) { + ret_event_type |= EpollEventType::EPOLLIN; + } + if file.ready_to_write() && req_type.contains(EpollEventType::EPOLLOUT) { + ret_event_type |= EpollEventType::EPOLLOUT; + } + if !ret_event_type.is_empty() { + let mut ret_event = *req_event; + ret_event.event_type = ret_event_type; + ret_events.push(ret_event); + } + // 若文件存在但未响应,此时不加入到ret中,并以此作为是否终止的条件 + } else { + // 若文件不存在,认为不存在也是一种响应,所以要加入到ret中,并以此作为是否终止的条件 + ret_events.push(EpollEvent { + event_type: EpollEventType::EPOLLERR, + data: req_event.data, + }); + } + } + if !ret_events.is_empty() { + // 此时收到了响应,直接返回 + return Ok(ret_events); + } + // 否则直接block + if current_ticks() as usize > expire_time { + return Ok(ret_events); + } + yield_now_task(); + + if current_process.have_signals().is_some() { + return Err(AxError::Timeout); + } + } + } +} + +/// EpollFile也是一种文件,应当为其实现一个file io trait +impl FileIO for EpollFile { + fn read(&self, _buf: &mut [u8]) -> AxResult { + Err(AxError::Unsupported) + } + fn write(&self, _buf: &[u8]) -> AxResult { + Err(AxError::Unsupported) + } + fn flush(&self) -> AxResult { + Err(AxError::Unsupported) + } + fn seek(&self, _pos: SeekFrom) -> AxResult { + Err(AxError::Unsupported) + } + fn readable(&self) -> bool { + false + } + fn writable(&self) -> bool { + false + } + fn executable(&self) -> bool { + false + } + /// epoll file也是一个文件描述符 + fn get_type(&self) -> FileIOType { + FileIOType::FileDesc + } + + fn set_close_on_exec(&self, is_set: bool) -> bool { + if is_set { + // 设置close_on_exec位置 + *self.flags.lock() |= OpenFlags::CLOEXEC; + } else { + *self.flags.lock() &= !OpenFlags::CLOEXEC; + } + true + } + + fn get_status(&self) -> OpenFlags { + *self.flags.lock() + } + + fn ready_to_read(&self) -> bool { + // 如果当前epoll事件确实正在等待事件响应,那么可以认为事件准备好read,尽管无法读到实际内容 + let process = current_process(); + let fd_table = process.fd_manager.fd_table.lock(); + for (fd, req_event) in self.inner.lock().monitor_list.iter() { + if let Some(file) = fd_table[*fd as usize].as_ref() { + let mut ret_event_type = EpollEventType::empty(); + let req_type = req_event.event_type; + if file.is_hang_up() { + ret_event_type |= EpollEventType::EPOLLHUP; + } + if file.in_exceptional_conditions() { + ret_event_type |= EpollEventType::EPOLLERR; + } + if file.ready_to_read() && req_type.contains(EpollEventType::EPOLLIN) { + ret_event_type |= EpollEventType::EPOLLIN; + } + if file.ready_to_write() && req_type.contains(EpollEventType::EPOLLOUT) { + ret_event_type |= EpollEventType::EPOLLOUT; + } + if !ret_event_type.is_empty() { + return true; + } + } + } + false + } +} diff --git a/api/linux_syscall_api/src/syscall_fs/ctype/eventfd.rs b/api/linux_syscall_api/src/syscall_fs/ctype/eventfd.rs new file mode 100644 index 0000000..772e445 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_fs/ctype/eventfd.rs @@ -0,0 +1,191 @@ +use alloc::sync::Arc; +use axerrno::{AxError, AxResult}; +use axfs::api::{FileIO, FileIOType, OpenFlags}; +use axsync::Mutex; +use axtask::yield_now; +use bitflags::bitflags; + +bitflags! { + // https://sites.uclouvain.be/SystInfo/usr/include/sys/eventfd.h.html + #[derive(Clone, Copy, Debug)] + pub struct EventFdFlag: u32 { + const EFD_SEMAPHORE = 0x1; + const EFD_NONBLOCK = 0x800; + const EFD_CLOEXEC = 0x80000; + } +} + +// https://man7.org/linux/man-pages/man2/eventfd2.2.html +pub struct EventFd { + value: Arc>, + flags: u32, +} + +impl EventFd { + pub fn new(initval: u64, flags: u32) -> EventFd { + EventFd { + value: Arc::new(Mutex::new(initval)), + flags, + } + } + + fn should_block(&self) -> bool { + self.flags & EventFdFlag::EFD_NONBLOCK.bits() == 0 + } + + fn has_semaphore_set(&self) -> bool { + self.flags & EventFdFlag::EFD_SEMAPHORE.bits() != 0 + } +} + +impl FileIO for EventFd { + fn read(&self, buf: &mut [u8]) -> AxResult { + let len: usize = core::mem::size_of::(); + if buf.len() < len { + return Err(AxError::InvalidInput); + } + + loop { + let mut value_guard = self.value.lock(); + // If EFD_SEMAPHORE was not specified and the eventfd counter has a nonzero value, then a read returns 8 bytes containing that value, + // and the counter's value is reset to zero. + if !self.has_semaphore_set() && *value_guard != 0 { + buf[0..len].copy_from_slice(&value_guard.to_ne_bytes()); + *value_guard = 0; + return Ok(len); + } + + // If EFD_SEMAPHORE was specified and the eventfd counter has a nonzero value, then a read returns 8 bytes containing the value 1, + // and the counter's value is decremented by 1. + if self.has_semaphore_set() && *value_guard != 0 { + let result: u64 = 1; + buf[0..len].copy_from_slice(&result.to_ne_bytes()); + let _ = value_guard.checked_add_signed(-1); + return Ok(len); + } + + // If the eventfd counter is zero at the time of the call to read, + // then the call either blocks until the counter becomes nonzero (at which time, the read proceeds as described above) + // or fails with the error EAGAIN if the file descriptor has been made nonblocking. + if *value_guard != 0 { + buf[0..len].copy_from_slice(&value_guard.to_ne_bytes()); + return Ok(len); + } + + if self.should_block() { + drop(value_guard); + yield_now() + } else { + return Err(AxError::WouldBlock); + } + } + } + + fn write(&self, buf: &[u8]) -> AxResult { + let len: usize = core::mem::size_of::(); + + // A write fails with the error EINVAL if the size of the supplied buffer is less than 8 bytes, + // or if an attempt is made to write the value 0xffffffffffffffff. + let val = u64::from_ne_bytes(buf[0..len].try_into().unwrap()); + if buf.len() < 8 || val == u64::MAX { + return Err(AxError::InvalidInput); + } + + loop { + let mut value_guard = self.value.lock(); + // The maximum value that may be stored in the counter is the largest unsigned 64-bit value minus 1 (i.e., 0xfffffffffffffffe). + match value_guard.checked_add(val + 1) { + // no overflow + Some(_) => { + *value_guard += val; + return Ok(len); + } + // overflow + None => { + if self.should_block() { + drop(value_guard); + yield_now() + } else { + return Err(AxError::WouldBlock); + } + } + } + } + } + + fn readable(&self) -> bool { + true + } + + fn writable(&self) -> bool { + true + } + + fn executable(&self) -> bool { + false + } + + fn get_type(&self) -> FileIOType { + FileIOType::Other + } + + // The file descriptor is readable if the counter has a value greater than 0 + fn ready_to_read(&self) -> bool { + *self.value.lock() > 0 + } + + // The file descriptor is writable if it is possible to write a value of at least "1" without blocking. + fn ready_to_write(&self) -> bool { + *self.value.lock() < u64::MAX - 1 + } + + fn get_status(&self) -> OpenFlags { + let mut status = OpenFlags::RDWR; + if self.flags & EventFdFlag::EFD_NONBLOCK.bits() != 0 { + status |= OpenFlags::NON_BLOCK; + } + if self.flags & EventFdFlag::EFD_CLOEXEC.bits() != 0 { + status |= OpenFlags::CLOEXEC; + } + + status + } +} + +#[cfg(test)] +mod tests { + use super::EventFd; + use axerrno::AxError; + use axfs::api::FileIO; + + #[test] + fn test_read() { + let event_fd = EventFd::new(42, 0); + let event_fd_val = 0u64; + let len = event_fd.read(&mut event_fd_val.to_ne_bytes()).unwrap(); + + assert_eq!(42, event_fd_val); + assert_eq!(4, len); + } + + #[test] + fn test_read_with_bad_input() { + let event_fd = EventFd::new(42, 0); + let event_fd_val = 0u32; + let result = event_fd.read(&mut event_fd_val.to_ne_bytes()); + assert_eq!(Err(AxError::InvalidInput), result); + } + + #[test] + fn test_write() { + let event_fd = EventFd::new(42, 0); + let val = 12u64; + event_fd + .write(&val.to_ne_bytes()[0..core::mem::size_of::()]) + .unwrap(); + + let event_fd_val = 0u64; + event_fd.read(&mut event_fd_val.to_ne_bytes()).unwrap(); + assert_eq!(54, event_fd_val); + } +} diff --git a/api/linux_syscall_api/src/syscall_fs/ctype/file.rs b/api/linux_syscall_api/src/syscall_fs/ctype/file.rs new file mode 100644 index 0000000..c426428 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_fs/ctype/file.rs @@ -0,0 +1,218 @@ +extern crate alloc; + +use alloc::collections::BTreeMap; +use alloc::string::{String, ToString}; +use alloc::sync::Arc; +use alloc::vec; +use alloc::vec::Vec; +use axerrno::AxResult; +use axfs::api::{File, FileIO, FileIOType, Kstat, OpenFlags, Read, Seek, SeekFrom, Write}; + +use axlog::debug; + +use crate::{new_file, normal_file_mode, StMode, TimeSecs}; +use axprocess::link::get_link_count; +use axsync::Mutex; + +pub static INODE_NAME_MAP: Mutex> = Mutex::new(BTreeMap::new()); + +/// 文件描述符 +pub struct FileDesc { + /// 文件路径 + pub path: String, + /// 文件 + pub file: Arc>, + /// 文件打开的标志位 + pub flags: Mutex, + /// 文件信息 + pub stat: Mutex, +} + +/// 文件在os中运行时的可变信息 +/// TODO: 暂时全部记为usize +pub struct FileMetaData { + /// 最后一次访问时间 + pub atime: TimeSecs, + /// 最后一次改变(modify)内容的时间 + pub mtime: TimeSecs, + /// 最后一次改变(change)属性的时间 + pub ctime: TimeSecs, + // /// 打开时的选项。 + // /// 主要用于判断 CLOEXEC,即 exec 时是否关闭。默认为 false。 + // pub flags: OpenFlags, +} + +/// 为FileDesc实现FileIO trait +impl FileIO for FileDesc { + fn read(&self, buf: &mut [u8]) -> AxResult { + self.file.lock().read(buf) + } + + fn write(&self, buf: &[u8]) -> AxResult { + // 如果seek时超出了文件原有大小,则在write的时候进行补零操作 + let mut file = self.file.lock(); + let old_offset = file.seek(SeekFrom::Current(0)).unwrap(); + let size = file.metadata().unwrap().size(); + if old_offset > size { + file.seek(SeekFrom::Start(size)).unwrap(); + let temp_buf: Vec = vec![0u8; (old_offset - size) as usize]; + file.write(&temp_buf)?; + } + file.write(buf) + } + + fn flush(&self) -> AxResult { + self.file.lock().flush() + } + + fn seek(&self, pos: SeekFrom) -> AxResult { + self.file.lock().seek(pos) + } + + fn readable(&self) -> bool { + self.flags.lock().readable() + } + fn writable(&self) -> bool { + self.flags.lock().writable() + } + fn executable(&self) -> bool { + self.file.lock().executable() + } + + fn get_type(&self) -> FileIOType { + FileIOType::FileDesc + } + fn get_path(&self) -> String { + self.path.clone() + } + + fn truncate(&self, len: usize) -> AxResult<()> { + self.file.lock().truncate(len) + } + + fn get_stat(&self) -> AxResult { + let file = self.file.lock(); + let attr = file.get_attr()?; + let stat = self.stat.lock(); + let inode_map = INODE_NAME_MAP.lock(); + let inode_number = if let Some(inode_number) = inode_map.get(&self.path) { + *inode_number + } else { + // return Err(axerrno::AxError::NotFound); + // Now the file exists but it wasn't opened + drop(inode_map); + new_inode(self.path.clone())?; + let inode_map = INODE_NAME_MAP.lock(); + assert!(inode_map.contains_key(&self.path)); + let number = *(inode_map.get(&self.path).unwrap()); + drop(inode_map); + number + }; + let kstat = Kstat { + st_dev: 1, + st_ino: inode_number, + st_mode: normal_file_mode(StMode::S_IFREG).bits() | 0o777, + st_nlink: get_link_count(&(self.path.as_str().to_string())) as _, + st_uid: 0, + st_gid: 0, + st_rdev: 0, + _pad0: 0, + st_size: attr.size(), + st_blksize: axfs::BLOCK_SIZE as u32, + _pad1: 0, + st_blocks: attr.blocks(), + st_atime_sec: stat.atime.tv_sec as isize, + st_atime_nsec: stat.atime.tv_nsec as isize, + st_mtime_sec: stat.mtime.tv_sec as isize, + st_mtime_nsec: stat.mtime.tv_nsec as isize, + st_ctime_sec: stat.ctime.tv_sec as isize, + st_ctime_nsec: stat.ctime.tv_nsec as isize, + }; + Ok(kstat) + } + + fn set_status(&self, flags: OpenFlags) -> bool { + *self.flags.lock() = flags; + true + } + + fn get_status(&self) -> OpenFlags { + *self.flags.lock() + } + + fn set_close_on_exec(&self, is_set: bool) -> bool { + if is_set { + // 设置close_on_exec位置 + *self.flags.lock() |= OpenFlags::CLOEXEC; + } else { + *self.flags.lock() &= !OpenFlags::CLOEXEC; + } + true + } + + fn ready_to_read(&self) -> bool { + if !self.readable() { + return false; + } + // 获取当前的位置 + let now_pos = self.seek(SeekFrom::Current(0)).unwrap(); + // 获取最后的位置 + let len = self.seek(SeekFrom::End(0)).unwrap(); + // 把文件指针复原,因为获取len的时候指向了尾部 + self.seek(SeekFrom::Start(now_pos)).unwrap(); + now_pos != len + } + + fn ready_to_write(&self) -> bool { + if !self.writable() { + return false; + } + // 获取当前的位置 + let now_pos = self.seek(SeekFrom::Current(0)).unwrap(); + // 获取最后的位置 + let len = self.seek(SeekFrom::End(0)).unwrap(); + // 把文件指针复原,因为获取len的时候指向了尾部 + self.seek(SeekFrom::Start(now_pos)).unwrap(); + now_pos != len + } +} + +impl FileDesc { + /// debug + + /// 创建一个新的文件描述符 + pub fn new(path: &str, file: Arc>, flags: OpenFlags) -> Self { + Self { + path: path.to_string(), + file, + flags: Mutex::new(flags), + stat: Mutex::new(FileMetaData { + atime: TimeSecs::default(), + mtime: TimeSecs::default(), + ctime: TimeSecs::default(), + }), + } + } +} + +/// 新建一个文件描述符 +pub fn new_fd(path: String, flags: OpenFlags) -> AxResult { + debug!("Into function new_fd, path: {}", path); + let file = new_file(path.as_str(), &flags)?; + // let file_size = file.metadata()?.len(); + + let fd = FileDesc::new(path.as_str(), Arc::new(Mutex::new(file)), flags); + Ok(fd) +} + +/// 当新建一个文件或者目录节点时,需要为其分配一个新的inode号 +/// 由于我们不涉及删除文件,因此我们可以简单地使用一个全局增的计数器来分配inode号 +pub fn new_inode(path: String) -> AxResult<()> { + let mut inode_name_map = INODE_NAME_MAP.lock(); + if inode_name_map.contains_key(&path) { + return Ok(()); + } + let inode_number = inode_name_map.len() as u64 + 1; + inode_name_map.insert(path, inode_number); + Ok(()) +} diff --git a/api/linux_syscall_api/src/syscall_fs/ctype/mod.rs b/api/linux_syscall_api/src/syscall_fs/ctype/mod.rs new file mode 100644 index 0000000..c277b4a --- /dev/null +++ b/api/linux_syscall_api/src/syscall_fs/ctype/mod.rs @@ -0,0 +1,15 @@ +pub mod dir; + +pub mod file; + +pub mod mount; + +pub mod pipe; + +pub use file::FileDesc; + +pub mod epoll; + +pub mod eventfd; + +pub mod pidfd; diff --git a/api/linux_syscall_api/src/syscall_fs/ctype/mount.rs b/api/linux_syscall_api/src/syscall_fs/ctype/mount.rs new file mode 100644 index 0000000..6d7cc22 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_fs/ctype/mount.rs @@ -0,0 +1,200 @@ +extern crate alloc; +use crate::{normal_file_mode, StMode, SyscallError}; +use alloc::string::ToString; +use alloc::vec::Vec; +use axfs::api::{lookup, path_exists, FileIO, Kstat, OpenFlags}; +use axlog::{debug, info}; +use axprocess::link::FilePath; +use axsync::Mutex; + +use super::{dir::new_dir, file::new_fd}; + +// use crate::{ +// dir::new_dir, +// file::new_fd, +// link::{deal_with_path, AT_FDCWD}, +// }; + +// use crate::link::{real_path}; + +/// 挂载的文件系统。 +/// 目前"挂载"的语义是,把一个文件当作文件系统读写 +pub struct MountedFs { + //pub inner: Arc>, + pub device: FilePath, + pub mnt_dir: FilePath, +} + +impl MountedFs { + pub fn new(device: &FilePath, mnt_dir: &FilePath) -> Self { + assert!( + device.is_file() && mnt_dir.is_dir(), + "device must be a file and mnt_dir must be a dir" + ); + Self { + device: device.clone(), + mnt_dir: mnt_dir.clone(), + } + } + #[allow(unused)] + pub fn device(&self) -> FilePath { + self.device.clone() + } + + pub fn mnt_dir(&self) -> FilePath { + self.mnt_dir.clone() + } +} + +/// 已挂载的文件系统(设备)。 +/// 注意启动时的文件系统不在这个 vec 里,它在 mod.rs 里。 +static MOUNTED: Mutex> = Mutex::new(Vec::new()); + +/// 挂载一个fatfs类型的设备 +pub fn mount_fat_fs(device_path: &FilePath, mount_path: &FilePath) -> bool { + // // device_path需要链接转换, mount_path不需要, 因为目前目录没有链接 // 暂时只有Open过的文件会加入到链接表,所以这里先不转换 + // debug!("mounting {} to {}", device_path.path(), mount_path.path()); + // if let Some(true_device_path) = real_path(device_path) { + if path_exists(mount_path.path()) { + MOUNTED.lock().push(MountedFs::new(device_path, mount_path)); + info!("mounted {} to {}", device_path.path(), mount_path.path()); + return true; + } + // } + info!( + "mount failed: {} to {}", + device_path.path(), + mount_path.path() + ); + false +} + +/// 卸载一个fatfs类型的设备 +pub fn umount_fat_fs(mount_path: &FilePath) -> bool { + let mut mounted = MOUNTED.lock(); + let mut i = 0; + while i < mounted.len() { + if mounted[i].mnt_dir().equal_to(mount_path) { + mounted.remove(i); + info!("umounted {}", mount_path.path()); + return true; + } + i += 1; + } + info!("umount failed: {}", mount_path.path()); + false +} + +/// 检查一个路径是否已经被挂载 +pub fn check_mounted(path: &FilePath) -> bool { + let mounted = MOUNTED.lock(); + for m in mounted.iter() { + if path.start_with(&m.mnt_dir()) { + debug!("{} is mounted", path.path()); + return true; + } + } + false +} + +/// 根据给定的路径获取对应的文件stat +pub fn get_stat_in_fs(path: &FilePath) -> Result { + // 根目录算作一个简单的目录文件,不使用特殊的stat + // 否则在fat32中查找 + let real_path = path.path(); + let mut ans = Kstat::default(); + info!("get_stat_in_fs: {}", real_path); + if real_path.starts_with("/var") + || real_path.starts_with("/dev") + || real_path.starts_with("/tmp") + || real_path.starts_with("/proc") + || real_path.starts_with("/sys") + { + if path.is_dir() { + ans.st_dev = 2; + ans.st_mode = normal_file_mode(StMode::S_IFDIR).bits(); + return Ok(ans); + } + if let Ok(node) = lookup(path.path()) { + let mut stat = Kstat { + st_nlink: 1, + ..Kstat::default() + }; + // 先检查是否在vfs中存在对应文件 + // 判断是在哪个vfs中 + if node + .as_any() + .downcast_ref::() + .is_some() + || node + .as_any() + .downcast_ref::() + .is_some() + { + stat.st_dev = 2; + stat.st_mode = normal_file_mode(StMode::S_IFDIR).bits(); + return Ok(stat); + } + if node + .as_any() + .downcast_ref::() + .is_some() + || node + .as_any() + .downcast_ref::() + .is_some() + || node + .as_any() + .downcast_ref::() + .is_some() + { + stat.st_mode = normal_file_mode(StMode::S_IFCHR).bits(); + return Ok(stat); + } + if node + .as_any() + .downcast_ref::() + .is_some() + { + stat.st_mode = normal_file_mode(StMode::S_IFREG).bits(); + stat.st_size = node.get_attr().unwrap().size(); + return Ok(stat); + } + } + } + // 是文件 + let metadata = axfs::api::metadata(path.path()).unwrap(); + if metadata.is_file() { + if let Ok(file) = new_fd(real_path.to_string(), 0.into()) { + match file.get_stat() { + Ok(stat) => Ok(stat), + Err(e) => { + debug!("get stat error: {:?}", e); + Err(SyscallError::EINVAL) + } + } + } else { + Err(SyscallError::ENOENT) + } + } else if metadata.is_dir() { + // 是目录 + if let Ok(dir) = new_dir(real_path.to_string(), OpenFlags::DIR) { + match dir.get_stat() { + Ok(stat) => Ok(stat), + Err(e) => { + debug!("get stat error: {:?}", e); + Err(SyscallError::EINVAL) + } + } + } else { + Err(SyscallError::ENOENT) + } + } else { + // 是字符设备 + Ok(Kstat { + st_nlink: 1, + st_mode: normal_file_mode(StMode::S_IFCHR).bits(), + ..Kstat::default() + }) + } +} diff --git a/api/linux_syscall_api/src/syscall_fs/ctype/pidfd.rs b/api/linux_syscall_api/src/syscall_fs/ctype/pidfd.rs new file mode 100644 index 0000000..5d9677c --- /dev/null +++ b/api/linux_syscall_api/src/syscall_fs/ctype/pidfd.rs @@ -0,0 +1,92 @@ +extern crate alloc; +use alloc::sync::Arc; +use axfs::api::{FileIO, OpenFlags}; +use axprocess::{current_process, Process, PID2PC}; +use axsync::Mutex; + +use crate::{SyscallError, SyscallResult}; + +pub struct PidFd { + flags: Mutex, + process: Arc, +} + +impl PidFd { + /// Create a new PidFd + pub fn new(process: Arc, flags: OpenFlags) -> Self { + Self { + flags: Mutex::new(flags), + process, + } + } + + pub fn pid(&self) -> u64 { + self.process.pid() + } +} + +impl FileIO for PidFd { + fn read(&self, _buf: &mut [u8]) -> axerrno::AxResult { + Err(axerrno::AxError::Unsupported) + } + fn write(&self, _buf: &[u8]) -> axerrno::AxResult { + Err(axerrno::AxError::Unsupported) + } + fn seek(&self, _pos: axfs::api::SeekFrom) -> axerrno::AxResult { + Err(axerrno::AxError::Unsupported) + } + /// To check whether the target process is still alive + fn readable(&self) -> bool { + self.process.get_zombie() + } + + fn writable(&self) -> bool { + false + } + + fn executable(&self) -> bool { + false + } + + fn get_type(&self) -> axfs::api::FileIOType { + axfs::api::FileIOType::Other + } + + fn get_status(&self) -> OpenFlags { + *self.flags.lock() + } + + fn set_status(&self, flags: OpenFlags) -> bool { + *self.flags.lock() = flags; + true + } + + fn set_close_on_exec(&self, is_set: bool) -> bool { + if is_set { + // 设置close_on_exec位置 + *self.flags.lock() |= OpenFlags::CLOEXEC; + } else { + *self.flags.lock() &= !OpenFlags::CLOEXEC; + } + true + } +} + +pub fn new_pidfd(pid: u64, mut flags: OpenFlags) -> SyscallResult { + // It is set to close the file descriptor on exec + flags |= OpenFlags::CLOEXEC; + let pid2fd = PID2PC.lock(); + + let pidfd = pid2fd + .get(&pid) + .map(|target_process| PidFd::new(Arc::clone(target_process), flags)) + .ok_or(SyscallError::EINVAL)?; + drop(pid2fd); + let process = current_process(); + let mut fd_table = process.fd_manager.fd_table.lock(); + let fd = process + .alloc_fd(&mut fd_table) + .map_err(|_| SyscallError::EMFILE)?; + fd_table[fd] = Some(Arc::new(pidfd)); + Ok(fd as isize) +} diff --git a/api/linux_syscall_api/src/syscall_fs/ctype/pipe.rs b/api/linux_syscall_api/src/syscall_fs/ctype/pipe.rs new file mode 100644 index 0000000..c5b48a4 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_fs/ctype/pipe.rs @@ -0,0 +1,269 @@ +use axfs::api::{FileIO, FileIOType, OpenFlags}; +extern crate alloc; +use alloc::sync::{Arc, Weak}; +use axerrno::AxResult; +use axlog::{info, trace}; + +use axsync::Mutex; +use axtask::yield_now; + +/// IPC pipe +pub struct Pipe { + #[allow(unused)] + readable: bool, + #[allow(unused)] + writable: bool, + buffer: Arc>, + #[allow(unused)] + flags: Mutex, +} + +impl Pipe { + /// create readable pipe + pub fn read_end_with_buffer(buffer: Arc>, flags: OpenFlags) -> Self { + Self { + readable: true, + writable: false, + buffer, + flags: Mutex::new(flags | OpenFlags::RDONLY), + } + } + /// create writable pipe + pub fn write_end_with_buffer(buffer: Arc>, flags: OpenFlags) -> Self { + Self { + readable: false, + writable: true, + buffer, + flags: Mutex::new(flags | OpenFlags::WRONLY), + } + } + /// is it set non block? + pub fn is_non_block(&self) -> bool { + self.flags.lock().contains(OpenFlags::NON_BLOCK) + } +} + +const RING_BUFFER_SIZE: usize = 0x4000; + +#[derive(Copy, Clone, PartialEq)] +enum RingBufferStatus { + Full, + Empty, + Normal, +} + +pub struct PipeRingBuffer { + arr: [u8; RING_BUFFER_SIZE], + head: usize, + tail: usize, + status: RingBufferStatus, + write_end: Option>, +} + +impl PipeRingBuffer { + pub fn new() -> Self { + Self { + arr: [0; RING_BUFFER_SIZE], + head: 0, + tail: 0, + status: RingBufferStatus::Empty, + write_end: None, + } + } + + pub fn set_write_end(&mut self, write_end: &Arc) { + self.write_end = Some(Arc::downgrade(write_end)); + } + + pub fn write_byte(&mut self, byte: u8) { + self.status = RingBufferStatus::Normal; + self.arr[self.tail] = byte; + self.tail = (self.tail + 1) % RING_BUFFER_SIZE; + if self.tail == self.head { + self.status = RingBufferStatus::Full; + } + } + pub fn read_byte(&mut self) -> u8 { + self.status = RingBufferStatus::Normal; + let c = self.arr[self.head]; + self.head = (self.head + 1) % RING_BUFFER_SIZE; + if self.head == self.tail { + self.status = RingBufferStatus::Empty; + } + c + } + pub fn available_read(&self) -> usize { + if self.status == RingBufferStatus::Empty { + 0 + } else if self.tail > self.head { + self.tail - self.head + } else { + self.tail + RING_BUFFER_SIZE - self.head + } + } + pub fn available_write(&self) -> usize { + if self.status == RingBufferStatus::Full { + 0 + } else { + RING_BUFFER_SIZE - self.available_read() + } + } + pub fn all_write_ends_closed(&self) -> bool { + self.write_end.as_ref().unwrap().upgrade().is_none() + } +} + +/// Return (read_end, write_end) +pub fn make_pipe(flags: OpenFlags) -> (Arc, Arc) { + trace!("kernel: make_pipe"); + let buffer = Arc::new(Mutex::new(PipeRingBuffer::new())); + let read_end = Arc::new(Pipe::read_end_with_buffer(buffer.clone(), flags)); + let write_end = Arc::new(Pipe::write_end_with_buffer(buffer.clone(), flags)); + buffer.lock().set_write_end(&write_end); + (read_end, write_end) +} + +impl FileIO for Pipe { + fn read(&self, buf: &mut [u8]) -> AxResult { + assert!(self.readable()); + let want_to_read = buf.len(); + let mut buf_iter = buf.iter_mut(); + let mut already_read = 0usize; + loop { + let mut ring_buffer = self.buffer.lock(); + let loop_read = ring_buffer.available_read(); + info!("kernel: Pipe::read: loop_read = {}", loop_read); + if loop_read == 0 { + if axprocess::current_process().have_signals().is_some() { + return Err(axerrno::AxError::Interrupted); + } + info!( + "kernel: Pipe::read: all_write_ends_closed = {}", + ring_buffer.all_write_ends_closed() + ); + if Arc::strong_count(&self.buffer) < 2 || ring_buffer.all_write_ends_closed() { + return Ok(already_read); + } + + if self.is_non_block() { + yield_now(); + return Err(axerrno::AxError::WouldBlock); + } + drop(ring_buffer); + yield_now(); + continue; + } + for _ in 0..loop_read { + if let Some(byte_ref) = buf_iter.next() { + *byte_ref = ring_buffer.read_byte(); + already_read += 1; + if already_read == want_to_read { + return Ok(want_to_read); + } + } else { + break; + } + } + + return Ok(already_read); + } + } + + fn write(&self, buf: &[u8]) -> AxResult { + info!("kernel: Pipe::write"); + assert!(self.writable()); + let want_to_write = buf.len(); + let mut buf_iter = buf.iter(); + let mut already_write = 0usize; + loop { + let mut ring_buffer = self.buffer.lock(); + let loop_write = ring_buffer.available_write(); + if loop_write == 0 { + drop(ring_buffer); + + if Arc::strong_count(&self.buffer) < 2 || self.is_non_block() { + // 读入端关闭 + return Ok(already_write); + } + yield_now(); + continue; + } + + // write at most loop_write bytes + for _ in 0..loop_write { + if let Some(byte_ref) = buf_iter.next() { + ring_buffer.write_byte(*byte_ref); + already_write += 1; + if already_write == want_to_write { + drop(ring_buffer); + return Ok(want_to_write); + } + } else { + break; + } + } + return Ok(already_write); + } + } + + fn executable(&self) -> bool { + false + } + fn readable(&self) -> bool { + self.readable + } + fn writable(&self) -> bool { + self.writable + } + + fn get_type(&self) -> FileIOType { + FileIOType::Pipe + } + + fn is_hang_up(&self) -> bool { + if self.readable { + if self.buffer.lock().available_read() == 0 + && self.buffer.lock().all_write_ends_closed() + { + // 写入端关闭且缓冲区读完了 + true + } else { + false + } + } else { + // 否则在写入端,只关心读入端是否被关闭 + Arc::strong_count(&self.buffer) < 2 + } + } + + fn ready_to_read(&self) -> bool { + self.readable && self.buffer.lock().available_read() != 0 + } + + fn ready_to_write(&self) -> bool { + self.writable && self.buffer.lock().available_write() != 0 + } + + /// 设置文件状态 + fn set_status(&self, flags: OpenFlags) -> bool { + *self.flags.lock() = flags; + true + } + + /// 获取文件状态 + fn get_status(&self) -> OpenFlags { + *self.flags.lock() + } + + /// 设置 close_on_exec 位 + /// 设置成功返回false + fn set_close_on_exec(&self, is_set: bool) -> bool { + if is_set { + // 设置close_on_exec位置 + *self.flags.lock() |= OpenFlags::CLOEXEC; + } else { + *self.flags.lock() &= !OpenFlags::CLOEXEC; + } + true + } +} diff --git a/api/linux_syscall_api/src/syscall_fs/fs_syscall_id.rs b/api/linux_syscall_api/src/syscall_fs/fs_syscall_id.rs new file mode 100644 index 0000000..786caa6 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_fs/fs_syscall_id.rs @@ -0,0 +1,141 @@ +//! 记录该模块使用到的系统调用 id +//! +//! +//! +#[cfg(any( + target_arch = "riscv32", + target_arch = "riscv64", + target_arch = "aarch64" +))] +numeric_enum_macro::numeric_enum! { +#[repr(usize)] +#[allow(non_camel_case_types)] +#[allow(missing_docs)] +#[derive(Eq, PartialEq, Debug, Copy, Clone)] +pub enum FsSyscallId { + // fs + GETCWD = 17, + EVENTFD = 19, + EPOLL_CREATE = 20, + EPOLL_CTL = 21, + EPOLL_PWAIT = 22, + DUP = 23, + DUP3 = 24, + FCNTL64 = 25, + IOCTL = 29, + MKDIRAT = 34, + SYMLINKAT = 36, + UNLINKAT = 35, + LINKAT = 37, + RENAMEAT = 38, + UNMOUNT = 39, + MOUNT = 40, + STATFS = 43, + FTRUNCATE64 = 46, + FACCESSAT = 48, + CHDIR = 49, + FCHMODAT = 53, + FCHOWN = 54, + OPENAT = 56, + CLOSE = 57, + PIPE2 = 59, + GETDENTS64 = 61, + LSEEK = 62, + READ = 63, + WRITE = 64, + READV = 65, + WRITEV = 66, + PPOLL = 73, + FSTATAT = 79, + PREAD64 = 67, + PWRITE64 = 68, + SENDFILE64 = 71, + PSELECT6 = 72, + PREADLINKAT = 78, + FSTAT = 80, + SYNC = 81, + FSYNC = 82, + UTIMENSAT = 88, + RENAMEAT2 = 276, + COPYFILERANGE = 285, + STATX = 291, + PIDFD_OPEN = 434, +} +} + +#[cfg(target_arch = "x86_64")] +numeric_enum_macro::numeric_enum! { + #[repr(usize)] + #[allow(non_camel_case_types)] + #[allow(missing_docs)] + #[derive(Eq, PartialEq, Debug, Copy, Clone)] + pub enum FsSyscallId { + // fs + OPEN = 2, + STAT = 4, + EVENTFD = 284, + EVENTFD2 = 290, + GETCWD = 79, + UNLINK = 87, + EPOLL_CREATE = 213, + EPOLL_CTL = 233, + EPOLL_WAIT = 232, + DUP = 32, + DUP2 = 33, + DUP3 = 292, + FCNTL64 = 72, + IOCTL = 16, + MKDIRAT = 258, + SYMLINKAT = 266, + RENAME = 82, + MKDIR = 83, + RMDIR = 84, + UNLINKAT = 263, + LINKAT = 265, + UNMOUNT = 166, + MOUNT = 165, + STATFS = 137, + FTRUNCATE64 = 77, + FACCESSAT = 269, + ACCESS = 21, + CHDIR = 80, + FCHMODAT = 268, + OPENAT = 257, + CLOSE = 3, + PIPE = 22, + PIPE2 = 293, + GETDENTS64 = 217, + LSEEK = 8, + READ = 0, + WRITE = 1, + READV = 19, + WRITEV = 20, + PPOLL = 271, + POLL = 7, + CREAT = 85, + FSTATAT = 262, + PREAD64 = 17, + PWRITE64 = 18, + SENDFILE64 = 40, + SELECT = 23, + PSELECT6 = 270, + READLINK = 89, + CHMOD = 90, + PREADLINKAT = 267, + FSTAT = 5, + LSTAT = 6, + SYNC = 162, + FSYNC = 74, + UTIMENSAT = 280, + RENAMEAT = 264, + RENAMEAT2 = 316, + COPYFILERANGE = 326, + EPOLL_CREATE1 = 291, + EPOLL_PWAIT = 281, + STATX = 332, + CHOWN = 92, + MKNOD = 259, + FCHOWN = 93, + PIDFD_OPEN = 434, + } +} diff --git a/api/linux_syscall_api/src/syscall_fs/imp/ctl.rs b/api/linux_syscall_api/src/syscall_fs/imp/ctl.rs new file mode 100644 index 0000000..4579f77 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_fs/imp/ctl.rs @@ -0,0 +1,673 @@ +//! 对文件系统的管理,包括目录项的创建、文件权限设置等内容 +use axfs::api::{ + remove_dir, remove_file, rename, ConsoleWinSize, OpenFlags, Permissions, FIOCLEX, FIONBIO, + TCGETS, TIOCGPGRP, TIOCGWINSZ, TIOCSPGRP, +}; +use axlog::{debug, error, info}; +use core::ptr::{self, copy_nonoverlapping}; + +use crate::{ + syscall_fs::{ + ctype::{file::new_fd, pidfd::new_pidfd, FileDesc}, + solve_path, + }, + DirEnt, DirEntType, Fcntl64Cmd, RenameFlags, SyscallError, SyscallResult, TimeSecs, +}; +use axhal::mem::VirtAddr; +use axprocess::{ + current_process, + link::{FilePath, AT_FDCWD}, +}; + +extern crate alloc; +use alloc::string::ToString; + +/// 功能:获取当前工作目录; +/// # Arguments +/// * `buf`: *mut u8, 一块缓存区,用于保存当前工作目录的字符串。当buf设为NULL,由系统来分配缓存区。 +/// * `len`: usize, buf缓存区的大小。 +/// # Return +/// 成功执行,则返回当前工作目录的字符串的指针。失败,则返回NULL。 +/// 暂时:成功执行,则返回当前工作目录的字符串的指针 as isize。失败,返回0。 +/// +/// TODO: 当前写法存在问题,cwd应当是各个进程独立的,而这里修改的是整个fs的目录 +pub fn syscall_getcwd(args: [usize; 6]) -> SyscallResult { + let buf = args[0] as *mut u8; + let len = args[1]; + debug!("Into syscall_getcwd. buf: {}, len: {}", buf as usize, len); + let mut cwd = current_process().get_cwd(); + + cwd.push('\0'); + info!("[syscall_getcwd]current dir: {:?}", cwd); + // todo: 如果buf为NULL,则系统分配缓存区 + // let process = current_process(); + // let process_inner = process.inner.lock(); + // if buf.is_null() { + // buf = allocate_buffer(cwd.len()); // 分配缓存区 allocate_buffer + // } + + let cwd = cwd.as_bytes(); + + if len >= cwd.len() { + let process = current_process(); + let start: VirtAddr = (buf as usize).into(); + let end = start + len; + if process.manual_alloc_range_for_lazy(start, end).is_ok() { + unsafe { + core::ptr::copy_nonoverlapping(cwd.as_ptr(), buf, cwd.len()); + } + Ok(buf as isize) + } else { + // ErrorNo::EINVAL as isize + Err(SyscallError::EINVAL) + } + } else { + debug!("getcwd: buf size is too small"); + Err(SyscallError::ERANGE) + } +} + +/// 功能:创建目录; +/// # Arguments +/// * dirfd: usize, 要创建的目录所在的目录的文件描述符。 +/// * path: *const u8, 要创建的目录的名称。如果path是相对路径,则它是相对于dirfd目录而言的。如果path是相对路径,且dirfd的值为AT_FDCWD,则它是相对于当前路径而言的。如果path是绝对路径,则dirfd被忽略。 +/// * mode: u32, 文件的所有权描述。详见`man 7 inode `。 +/// 返回值:成功执行,返回0。失败,返回-1。 +pub fn syscall_mkdirat(args: [usize; 6]) -> SyscallResult { + let dir_fd = args[0]; + let path = args[1] as *const u8; + let mode = args[2] as u32; + // info!("signal module: {:?}", process_inner.signal_module.keys()); + let path = solve_path(dir_fd, Some(path), true)?; + debug!( + "Into syscall_mkdirat. dirfd: {}, path: {:?}, mode: {}", + dir_fd, + path.path(), + mode + ); + if axfs::api::path_exists(path.path()) { + // 文件已存在 + return Err(SyscallError::EEXIST); + } + let _ = axfs::api::create_dir(path.path()); + // 只要文件夹存在就返回0 + if axfs::api::path_exists(path.path()) { + Ok(0) + } else { + Err(SyscallError::EPERM) + } +} + +/// 功能:创建目录; +/// # Arguments +/// * `path`: *const u8, 要创建的目录的名称。如果path是相对路径,则它是相对于dirfd目录而言的。如果path是相对路径,且dirfd的值为AT_FDCWD,则它是相对于当前路径而言的。如果path是绝对路径,则dirfd被忽略。 +/// * `mode`: u32, 文件的所有权描述。详见`man 7 inode `。 +/// # Return +/// 成功执行,返回0。失败,返回-1。 +#[cfg(target_arch = "x86_64")] +pub fn syscall_mkdir(args: [usize; 6]) -> SyscallResult { + let path = args[0]; + let mode = args[1]; + let temp_args = [AT_FDCWD, path, mode, 0, 0, 0]; + syscall_mkdirat(temp_args) +} + +/// 功能:切换工作目录; +/// # Arguments +/// * `path``: *const u8, 需要切换到的目录。 +/// # Return +/// 成功执行:返回0。失败, 返回-1。 +pub fn syscall_chdir(args: [usize; 6]) -> SyscallResult { + let path_address = args[0] as *const u8; + // 从path中读取字符串 + + let path = solve_path(AT_FDCWD, Some(path_address), true)?; + debug!("Into syscall_chdir. path: {:?}", path.path()); + + current_process().set_cwd(alloc::string::String::from(path.path())); + Ok(0) +} + +/// To get the dirent structures from the directory referred to by the open file descriptor fd into the buffer +/// # Arguments +/// * `fd`: usize, the file descriptor of the directory to be read +/// * `buf`: *mut u8, the buffer to store the dirent structures +/// * `len`: usize, the size of the buffer +/// +/// # Return +/// * On success, the number of bytes read is returned. On end of directory, 0 is returned. +/// * On error, -1 is returned. +pub fn syscall_getdents64(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let buf = args[1] as *mut u8; + let len = args[2]; + let path = solve_path(fd, None, true)?; + let process = current_process(); + // 注意是否分配地址 + let start: VirtAddr = (buf as usize).into(); + let end = start + len; + if process.manual_alloc_range_for_lazy(start, end).is_err() { + return Err(SyscallError::EFAULT); + } + if len < DirEnt::fixed_size() { + return Err(SyscallError::EINVAL); + } + // let entry_id_from = unsafe { (*(buf as *const DirEnt)).d_off }; + // error!("entry_id_from: {}", entry_id_from); + // 先获取buffer里面最后一个长度 + let mut all_offset = 0; // 记录上一次调用时进行到的目录项距离文件夹开始时的偏移量 + let mut buf_offset = 0; // 记录当前buf里面的目录项的指针偏移量 + loop { + if buf_offset + DirEnt::fixed_size() >= len { + break; + } + let dir_ent = unsafe { *(buf.add(buf_offset) as *const DirEnt) }; + if dir_ent.d_reclen == 0 { + break; + } + buf_offset += dir_ent.d_reclen as usize; + // all_offset = dir_ent.d_off; // 记录最新的 offset + if all_offset < dir_ent.d_off { + all_offset = dir_ent.d_off; + } else { + break; + } + } + + let buf = unsafe { core::slice::from_raw_parts_mut(buf, len) }; + let dir_iter = axfs::api::read_dir(path.path()).unwrap(); + let mut count = 0; // buf中已经写入的字节数 + let mut offset: u64 = 0; // 当前目录项在文件夹中的偏移 + for entry in dir_iter { + let entry = entry.unwrap(); + let mut name = entry.file_name(); + name.push('\0'); + let name = name.as_bytes(); + let name_len = name.len(); + let file_type = entry.file_type(); + let entry_size = DirEnt::fixed_size() + name_len + 1; + + // buf不够大,写不下新的entry + if count + entry_size + DirEnt::fixed_size() + 1 > len { + debug!("buf not big enough"); + break; + } + offset += entry_size as u64; + if offset <= all_offset { + continue; + } + // 转换为DirEnt + let dirent: &mut DirEnt = unsafe { &mut *(buf.as_mut_ptr().add(count) as *mut DirEnt) }; + // 设置定长部分 + if file_type.is_dir() { + dirent.set_fixed_part(1, offset, entry_size, DirEntType::Dir); + } else if file_type.is_file() { + dirent.set_fixed_part(1, offset, entry_size, DirEntType::Reg); + } else { + dirent.set_fixed_part(1, offset, entry_size, DirEntType::Unknown); + } + + // 写入文件名 + unsafe { copy_nonoverlapping(name.as_ptr(), dirent.d_name.as_mut_ptr(), name_len) }; + + count += entry_size; + } + + // 为了保证下一次访问的时候边界是存在的,因此需要手动写入一个空的目录项 + if count != 0 && count + DirEnt::fixed_size() <= len { + // 转换为DirEnt + let dirent: &mut DirEnt = unsafe { &mut *(buf.as_mut_ptr().add(count) as *mut DirEnt) }; + // 设置定长部分 + dirent.set_fixed_part(1, offset, DirEnt::fixed_size(), DirEntType::Reg); + count += DirEnt::fixed_size(); + return Ok((count - DirEnt::fixed_size()) as isize); + } + Ok(count as isize) +} + +/// 276 +/// 重命名文件或目录 +// todo! +// 1. 权限检查 +// 调用进程必须对源目录和目标目录都有写权限,才能完成重命名。 +// 2. 目录和文件在同一个文件系统 +// 如果目录和文件不在同一个文件系统,重命名会失败。renameat2不能跨文件系统重命名。 +// 3. 源文件不是目标目录的子目录 +// 如果源文件是目标目录的子孙目录,也会导致重命名失败。不能将目录重命名到自己的子目录中。 +// 4. 目标名称不存在 +// 目标文件名在目标目录下必须不存在,否则会失败。 +// 5. 源文件被打开 +// 如果源文件正被进程打开,默认情况下重命名也会失败。可以通过添加RENAME_EXCHANGE标志位实现原子交换。 +// 6. 目录不是挂载点 +// 如果源目录是一个挂载点,也不允许重命名。 +/// # Arguments +/// * `old_dirfd`: usize, 旧文件所在的目录的文件描述符。 +/// * `old_path`: *const u8, 旧文件的名称。如果old_path是相对路径,则它是相对于old_dirfd目录而言的。如果old_path是相对路径,且old_dirfd的值为AT_FDCWD,则它是相对于当前路径而言的。如果old_path是绝对路径,则old_dirfd被忽略。 +/// * `new_dirfd`: usize, 新文件所在的目录的文件描述符。 +/// * `new_path`: *const u8, 新文件的名称。如果new_path是相对路径,则它是相对于new_dirfd目录而言的。如果new_path是相对路径,且new_dirfd的值为AT_FDCWD,则它是相对于当前路径而言的。如果new_path是绝对路径,则new_dirfd被忽略。 +/// * `flags`: usize, 重命名的标志位。目前只支持RENAME_NOREPLACE、RENAME_EXCHANGE和RENAME_WHITEOUT。 +pub fn syscall_renameat2(args: [usize; 6]) -> SyscallResult { + let old_dirfd = args[0]; + let _old_path = args[1] as *const u8; + let new_dirfd = args[2]; + let _new_path = args[3] as *const u8; + let flags = args[4]; + let old_path = solve_path(old_dirfd, Some(_old_path), false)?; + axlog::error!("syscall_renameat2 old_path : {:?}", args); + let new_path = solve_path(new_dirfd, Some(_new_path), false)?; + axlog::error!("syscall_renameat2 new_path: {:?}", args); + + let proc_path = FilePath::new("/proc").unwrap(); + if old_path.start_with(&proc_path) || new_path.start_with(&proc_path) { + return Err(SyscallError::EPERM); + } + let flags = if let Some(ans) = RenameFlags::from_bits(flags as u32) { + ans + } else { + return Err(SyscallError::EINVAL); + }; + // 如果重命名后的文件已存在 + if flags.contains(RenameFlags::NOREPLACE) { + if flags.contains(RenameFlags::EXCHANGE) { + return Err(SyscallError::EINVAL); + } + if axfs::api::path_exists(new_path.path()) { + debug!("new_path_ already exist"); + return Err(SyscallError::EEXIST); + } + } + + if !flags.contains(RenameFlags::EXCHANGE) { + // 此时不是交换,而是移动,那么需要 + if axfs::api::path_exists(new_path.path()) { + let old_metadata = axfs::api::metadata(old_path.path()).unwrap(); + let new_metadata = axfs::api::metadata(new_path.path()).unwrap(); + if old_metadata.is_dir() ^ new_metadata.is_dir() { + debug!("old_path_ and new_path_ is not the same type"); + if old_metadata.is_dir() { + return Err(SyscallError::ENOTDIR); + } + return Err(SyscallError::EISDIR); + } + } + } else if flags.contains(RenameFlags::WHITEOUT) { + return Err(SyscallError::EINVAL); + } + + // 做实际重命名操作 + if !axfs::api::path_exists(old_path.path()) { + return Err(SyscallError::ENOENT); + } + + if old_path.path() == new_path.path() { + // 相同文件不用改 + return Ok(0); + } + if !flags.contains(RenameFlags::EXCHANGE) { + // 当新文件存在,先删掉新文件 + // 此时若存在新文件,默认是没有 NOREPLACE 的 + if axfs::api::path_exists(new_path.path()) { + let new_metadata = axfs::api::metadata(new_path.path()).unwrap(); + if new_metadata.is_dir() { + if let Err(err) = remove_dir(new_path.path()) { + error!("error: {:?}", err); + return Err(SyscallError::EPERM); + } + } else if new_metadata.is_file() { + if let Err(err) = remove_file(new_path.path()) { + error!("error: {:?}", err); + return Err(SyscallError::EPERM); + } + } + } + if let Err(err) = rename(old_path.path(), new_path.path()) { + error!("error: {:?}", err); + return Err(SyscallError::EPERM); + } + } else { + // 当前不支持交换 + axlog::warn!("renameat2 exchange not implemented"); + return Err(SyscallError::EPERM); + } + Ok(0) +} + +/// 重命名文件或目录 +/// # Arguments +/// * `old_path`: *const u8 +/// * `new_path`: *const u8 +/// To rename the file from old_path to new_path +#[cfg(target_arch = "x86_64")] +pub fn syscall_rename(args: [usize; 6]) -> SyscallResult { + let old_path = args[0]; + let new_path = args[1]; + let temp_args = [AT_FDCWD, old_path, AT_FDCWD, new_path, 1, 0]; + syscall_renameat2(temp_args) +} + +/// # Arguments +/// * `fd`: usize +/// * `cmd`: usize +/// * `arg`: usize +pub fn syscall_fcntl64(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let cmd = args[1]; + let arg = args[2]; + let process = current_process(); + let mut fd_table = process.fd_manager.fd_table.lock(); + + if fd >= fd_table.len() { + debug!("fd {} is out of range", fd); + return Err(SyscallError::EBADF); + } + if fd_table[fd].is_none() { + debug!("fd {} is none", fd); + return Err(SyscallError::EBADF); + } + let file = fd_table[fd].clone().unwrap(); + info!("fd: {}, cmd: {}", fd, cmd); + match Fcntl64Cmd::try_from(cmd) { + Ok(Fcntl64Cmd::F_DUPFD) => { + let new_fd = if let Ok(fd) = process.alloc_fd(&mut fd_table) { + fd + } else { + // 文件描述符达到上限了 + return Err(SyscallError::EMFILE); + }; + fd_table[new_fd] = fd_table[fd].clone(); + Ok(new_fd as isize) + } + Ok(Fcntl64Cmd::F_GETFD) => { + if file.get_status().contains(OpenFlags::CLOEXEC) { + Ok(1) + } else { + Ok(0) + } + } + Ok(Fcntl64Cmd::F_SETFD) => { + if file.set_close_on_exec((arg & 1) != 0) { + Ok(0) + } else { + //error!("file.set_close_on_exec"); + Err(SyscallError::EINVAL) + } + } + Ok(Fcntl64Cmd::F_GETFL) => Ok(file.get_status().bits() as isize), + Ok(Fcntl64Cmd::F_SETFL) => { + if let Some(flags) = OpenFlags::from_bits(arg as u32) { + let _ = file.set_status(flags); + Ok(0) + } else { + Err(SyscallError::EINVAL) + } + } + Ok(Fcntl64Cmd::F_DUPFD_CLOEXEC) => { + let new_fd = if let Ok(fd) = process.alloc_fd(&mut fd_table) { + fd + } else { + // 文件描述符达到上限了 + return Err(SyscallError::EMFILE); + }; + + if file.set_close_on_exec((arg & 1) != 0) { + fd_table[new_fd] = fd_table[fd].clone(); + Ok(new_fd as isize) + } else { + //error!("file.F_DUPFD_CLOEXEC"); + Err(SyscallError::EINVAL) + } + } + _ => { + error!("error fd: {}, cmd: {}", fd, cmd); + Err(SyscallError::EINVAL) + } + } +} + +/// 29 +/// 执行各种设备相关的控制功能 +/// todo: 未实现 +/// # Arguments +/// * `fd`: usize, 文件描述符 +/// * `request`: usize, 控制命令 +/// * `argp`: *mut usize, 参数 +pub fn syscall_ioctl(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let request = args[1]; + let argp = args[2]; + let process = current_process(); + let fd_table = process.fd_manager.fd_table.lock(); + info!("fd: {}, request: {}, argp: {}", fd, request, argp); + if fd >= fd_table.len() { + debug!("fd {} is out of range", fd); + return Err(SyscallError::EBADF); + } + if fd_table[fd].is_none() { + debug!("fd {} is none", fd); + return Err(SyscallError::EBADF); + } + if process.manual_alloc_for_lazy(argp.into()).is_err() { + return Err(SyscallError::EFAULT); // 地址不合法 + } + + let file = fd_table[fd].clone().unwrap(); + match request { + TIOCGWINSZ => { + let winsize = argp as *mut ConsoleWinSize; + unsafe { + *winsize = ConsoleWinSize::default(); + } + Ok(0) + } + TCGETS | TIOCSPGRP => Ok(0), + TIOCGPGRP => { + unsafe { + *(argp as *mut u32) = 0; + } + Ok(0) + } + FIONBIO => { + let ptr_argp = argp as *const u32; + let nonblock = unsafe { ptr::read(ptr_argp) }; + if nonblock == 1 { + let old_status = file.get_status(); + let _ = file.set_status(old_status | OpenFlags::NON_BLOCK); + } + Ok(0) + } + FIOCLEX => Ok(0), + _ => Err(SyscallError::EOPNOTSUPP), + } +} + +/// 53 +/// 修改文件权限 +/// mode: 0o777, 3位八进制数字 +/// path为相对路径: +/// 1. 若dir_fd为AT_FDCWD,则相对于当前工作目录 +/// 2. 若dir_fd为AT_FDCWD以外的值,则相对于dir_fd所指的目录 +/// path为绝对路径: +/// 忽视dir_fd,直接根据path访问 +/// # Arguments +/// * `dir_fd`: usize, 目录的文件描述符 +/// * `path`: *const u8, 文件的路径 +/// * `mode`: usize, 文件的权限 +pub fn syscall_fchmodat(args: [usize; 6]) -> SyscallResult { + let dir_fd = args[0]; + let path = args[1] as *const u8; + let mode = args[2]; + let file_path = solve_path(dir_fd, Some(path), false)?; + axfs::api::metadata(file_path.path()) + .map(|mut metadata| { + metadata.set_permissions(Permissions::from_bits_truncate(mode as u16)); + Ok(0) + }) + .unwrap_or_else(|_| Err(SyscallError::ENOENT)) +} + +/// 48 +/// 获取文件权限 +/// 类似上面的fchmodat +/// The mode specifies the accessibility check(s) to be performed, +/// and is either the value F_OK, or a mask consisting of the bitwise +/// OR of one or more of R_OK, W_OK, and X_OK. F_OK tests for the +/// existence of the file. R_OK, W_OK, and X_OK test whether the +/// file exists and grants read, write, and execute permissions, +/// respectively. +/// 0: F_OK, 1: X_OK, 2: W_OK, 4: R_OK +/// # Arguments +/// * `dir_fd`: usize, 目录的文件描述符 +/// * `path`: *const u8, 文件的路径 +/// * `mode`: usize, 文件的权限 +pub fn syscall_faccessat(args: [usize; 6]) -> SyscallResult { + let dir_fd = args[0]; + let path = args[1] as *const u8; + let mode = args[2]; + // todo: 有问题,实际上需要考虑当前进程对应的用户UID和文件拥有者之间的关系 + // 现在一律当作root用户处理 + let file_path = solve_path(dir_fd, Some(path), false)?; + axlog::info!("syscall_faccessat file_path : {:?}", file_path); + axfs::api::metadata(file_path.path()) + .map(|metadata| { + if mode == 0 { + //F_OK + // 文件存在返回0,不存在返回-1 + if axfs::api::path_exists(file_path.path()) { + Ok(0) + } else { + Err(SyscallError::ENOENT) + } + } else { + // 逐位对比 + let mut ret = true; + if mode & 1 != 0 { + // X_OK + ret &= metadata.permissions().contains(Permissions::OWNER_EXEC) + } + if mode & 2 != 0 { + // W_OK + ret &= metadata.permissions().contains(Permissions::OWNER_WRITE) + } + if mode & 4 != 0 { + // R_OK + ret &= metadata.permissions().contains(Permissions::OWNER_READ) + } + Ok(ret as isize - 1) + } + }) + .unwrap_or_else(|_| Err(SyscallError::ENOENT)) +} + +/// 48 +/// 获取文件权限 +/// 0: F_OK, 1: X_OK, 2: W_OK, 4: R_OK +/// # Arguments +/// * `path`: *const u8, 文件的路径 +/// * `mode`: usize, 文件的权限 +#[cfg(target_arch = "x86_64")] +pub fn syscall_access(args: [usize; 6]) -> SyscallResult { + let path = args[0]; + let mode = args[1]; + let temp_args = [AT_FDCWD, path, mode, 0, 0, 0]; + // todo: 有问题,实际上需要考虑当前进程对应的用户UID和文件拥有者之间的关系 + // 现在一律当作root用户处理 + syscall_faccessat(temp_args) +} + +/// 删除目录 +/// # Arguments +/// * `path`: *const u8, 文件的路径 +#[cfg(target_arch = "x86_64")] +pub fn syscall_rmdir(args: [usize; 6]) -> SyscallResult { + use super::AT_REMOVEDIR; + + let path = args[0]; + let temp_args = [AT_FDCWD, path, AT_REMOVEDIR, 0, 0, 0]; + super::syscall_unlinkat(temp_args) +} + +/// 88 +/// 用于修改文件或目录的时间戳(timestamp) +/// 如果 fir_fd < 0,它和 path 共同决定要找的文件; +/// 如果 fir_fd >=0,它就是文件对应的 fd +/// # Arguments +/// * `dir_fd`: usize, 目录的文件描述符 +/// * `path`: *const u8, 文件的路径 +/// * `times`: *const TimeSecs, 时间戳 +/// * `flags`: usize, 选项 +pub fn syscall_utimensat(args: [usize; 6]) -> SyscallResult { + let dir_fd = args[0]; + let path = args[1] as *const u8; + let times = args[2] as *const TimeSecs; + let _flags = args[3]; + let process = current_process(); + // info!("dir_fd: {}, path: {}", dir_fd as usize, path as usize); + if dir_fd != AT_FDCWD && (dir_fd as isize) < 0 { + return Err(SyscallError::EBADF); // 错误的文件描述符 + } + + if dir_fd == AT_FDCWD + && process + .manual_alloc_for_lazy((path as usize).into()) + .is_err() + { + return Err(SyscallError::EFAULT); // 地址不合法 + } + // 需要设置的时间 + let (new_atime, new_mtime) = if times.is_null() { + (TimeSecs::now(), TimeSecs::now()) + } else { + if process.manual_alloc_type_for_lazy(times).is_err() { + return Err(SyscallError::EFAULT); + } + unsafe { (*times, *(times.add(1))) } // 注意传入的TimeVal中 sec和nsec都是usize, 但TimeValue中nsec是u32 + }; + // 感觉以下仿照maturin的实现不太合理,并没有真的把时间写给文件,只是写给了一个新建的临时的fd + if (dir_fd as isize) > 0 { + // let file = process_inner.fd_manager.fd_table[dir_fd].clone(); + // if !file.unwrap().lock().set_time(new_atime, new_mtime) { + // error!("Set time failed: unknown reason."); + // return ErrorNo::EPERM as isize; + // } + let fd_table = process.fd_manager.fd_table.lock(); + if dir_fd > fd_table.len() || fd_table[dir_fd].is_none() { + return Err(SyscallError::EBADF); + } + if let Some(file) = fd_table[dir_fd].as_ref() { + if let Some(fat_file) = file.as_any().downcast_ref::() { + // if !fat_file.set_time(new_atime, new_mtime) { + // error!("Set time failed: unknown reason."); + // return ErrorNo::EPERM as isize; + // } + fat_file.stat.lock().atime.set_as_utime(&new_atime); + fat_file.stat.lock().mtime.set_as_utime(&new_mtime); + } else { + return Err(SyscallError::EPERM); + } + } + Ok(0) + } else { + let file_path = solve_path(dir_fd, Some(path), false)?; + if !axfs::api::path_exists(file_path.path()) { + if !axfs::api::path_exists(file_path.dir().unwrap()) { + return Err(SyscallError::ENOTDIR); + } else { + return Err(SyscallError::ENOENT); + } + } + let file = new_fd(file_path.path().to_string(), 0.into()).unwrap(); + file.stat.lock().atime.set_as_utime(&new_atime); + file.stat.lock().mtime.set_as_utime(&new_mtime); + Ok(0) + } +} + +/// To open a file descriptor that refers to the PID directory of the given process +pub fn syscall_pidfd_open(args: [usize; 6]) -> SyscallResult { + let pid = args[0] as u32; + let flags = args[1] as u32; + new_pidfd( + pid as u64, + OpenFlags::from_bits(flags).ok_or(SyscallError::EINVAL)?, + ) +} diff --git a/api/linux_syscall_api/src/syscall_fs/imp/epoll.rs b/api/linux_syscall_api/src/syscall_fs/imp/epoll.rs new file mode 100644 index 0000000..7399bec --- /dev/null +++ b/api/linux_syscall_api/src/syscall_fs/imp/epoll.rs @@ -0,0 +1,171 @@ +//! The epoll API performs a similar task to poll: monitoring +//! multiple file descriptors to see if I/O is possible on any of +//! them. +extern crate alloc; +use crate::{SigMaskFlag, SyscallError, SyscallResult}; +use alloc::sync::Arc; +use axhal::{mem::VirtAddr, time::current_ticks}; +use axprocess::current_process; + +use crate::syscall_fs::ctype::epoll::{EpollCtl, EpollEvent, EpollFile}; + +/// For epoll_create, Since Linux 2.6.8, the size argument is ignored, but must be greater than zero; +/// +/// +/// For epoll_create1, If flags is 0, then, other than the fact that the obsolete size argument is dropped, epoll_create1() +/// is the same as epoll_create(). +/// +/// If flag equals to EPOLL_CLOEXEC, than set the cloexec flag for the fd +/// # Arguments +/// * `flag` - usize +pub fn syscall_epoll_create1(args: [usize; 6]) -> SyscallResult { + let _flag = args[0]; + let file = EpollFile::new(); + let process = current_process(); + let mut fd_table = process.fd_manager.fd_table.lock(); + if let Ok(num) = process.alloc_fd(&mut fd_table) { + fd_table[num] = Some(Arc::new(file)); + Ok(num as isize) + } else { + // ErrorNo::EMFILE as isize + Err(SyscallError::EMFILE) + } +} + +/// 执行syscall_epoll_ctl,修改文件对应的响应事件 +/// +/// 需要一个epoll事件的fd,用来执行修改操作 +/// +/// # Arguments +/// * `epfd`: i32, epoll文件的fd +/// * `op`: i32, 修改操作的类型 +/// * `fd`: i32, 接受事件的文件的fd +/// * `event`: *const EpollEvent, 接受的事件 +pub fn syscall_epoll_ctl(args: [usize; 6]) -> SyscallResult { + let epfd = args[0] as i32; + let op = args[1] as i32; + let fd = args[2] as i32; + let event = args[3] as *const EpollEvent; + let process = current_process(); + if process.manual_alloc_type_for_lazy(event).is_err() { + return Err(SyscallError::EFAULT); + } + let fd_table = process.fd_manager.fd_table.lock(); + let event = unsafe { *event }; + if fd_table[fd as usize].is_none() { + return Err(SyscallError::EBADF); + } + let op = if let Ok(val) = EpollCtl::try_from(op) { + val + } else { + return Err(SyscallError::EINVAL); + }; + if let Some(file) = fd_table[epfd as usize].as_ref() { + if let Some(epoll_file) = file.as_any().downcast_ref::() { + epoll_file.epoll_ctl(op, fd, event) + } else { + Err(SyscallError::EBADF) + } + } else { + Err(SyscallError::EBADF) + } +} + +/// 执行syscall_epoll_wait系统调用 +/// +/// # Arguments +/// * `epfd`: i32, epoll文件的fd +/// * `event`: *mut EpollEvent, 接受事件的数组 +/// * `max_event`: i32, 最大的响应事件数量,必须大于0 +/// * `timeout`: i32, 超时时间,是一段相对时间,需要手动转化为绝对时间 +/// +/// ret: 实际写入的响应事件数目 +pub fn syscall_epoll_wait(args: [usize; 6]) -> SyscallResult { + let epfd = args[0] as i32; + let event = args[1] as *mut EpollEvent; + let max_event = args[2] as i32; + let timeout = args[3] as i32; + if max_event <= 0 { + return Err(SyscallError::EINVAL); + } + let max_event = max_event as usize; + let process = current_process(); + let start: VirtAddr = (event as usize).into(); + // FIXME: this is a temporary solution + // the memory will out of mapped memory if the max_event is too large + // maybe give the max_event a limit is a better solution + let max_event = core::cmp::min(max_event, 400); + let end = start + max_event * core::mem::size_of::(); + if process.manual_alloc_range_for_lazy(start, end).is_err() { + return Err(SyscallError::EFAULT); + } + + let epoll_file = { + let fd_table = process.fd_manager.fd_table.lock(); + if let Some(file) = fd_table[epfd as usize].as_ref() { + if let Some(epoll_file) = file.as_any().downcast_ref::() { + epoll_file.clone() + } else { + return Err(SyscallError::EBADF); + } + } else { + return Err(SyscallError::EBADF); + } + }; + + let timeout = if timeout > 0 { + current_ticks() as usize + timeout as usize + } else { + usize::MAX + }; + let ret_events = epoll_file.epoll_wait(timeout); + if ret_events.is_err() { + return Err(SyscallError::EINTR); + } + let ret_events = ret_events.unwrap(); + let real_len = ret_events.len().min(max_event); + for (i, e) in ret_events.iter().enumerate().take(real_len) { + unsafe { + *(event.add(i)) = *e; + } + } + Ok(real_len as isize) +} + +/// Implement syscall_epoll_pwait system call +/// +/// - Set the signal mask of the current process to the value pointed to by sigmask +/// - Invoke syscall_epoll_wait +/// - Restore the signal mask of the current process +pub fn syscall_epoll_pwait(args: [usize; 6]) -> SyscallResult { + let sigmask = args[4] as *const usize; + + let process = current_process(); + if sigmask.is_null() { + return syscall_epoll_wait(args); + } + if process.manual_alloc_type_for_lazy(sigmask).is_err() { + return Err(SyscallError::EFAULT); + } + let old_mask: usize = 0; + let temp_args = [ + SigMaskFlag::Setmask as usize, + sigmask as usize, + (&old_mask) as *const _ as usize, + 8, + 0, + 0, + ]; + crate::syscall_task::syscall_sigprocmask(temp_args)?; + let ret = syscall_epoll_wait(args)?; + let temp_args = [ + SigMaskFlag::Setmask as usize, + &old_mask as *const _ as usize, + 0, + 8, + 0, + 0, + ]; + crate::syscall_task::syscall_sigprocmask(temp_args)?; + Ok(ret) +} diff --git a/api/linux_syscall_api/src/syscall_fs/imp/eventfd.rs b/api/linux_syscall_api/src/syscall_fs/imp/eventfd.rs new file mode 100644 index 0000000..a3ce5c4 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_fs/imp/eventfd.rs @@ -0,0 +1,22 @@ +use alloc::sync::Arc; +use axprocess::current_process; + +use crate::syscall_fs::ctype::eventfd::EventFd; +use crate::{SyscallError, SyscallResult}; + +pub fn syscall_eventfd(args: [usize; 6]) -> SyscallResult { + let initval = args[0] as u64; + let flags = args[1] as u32; + + let process = current_process(); + let mut fd_table = process.fd_manager.fd_table.lock(); + let fd_num = if let Ok(fd) = process.alloc_fd(&mut fd_table) { + fd + } else { + return Err(SyscallError::EPERM); + }; + + fd_table[fd_num] = Some(Arc::new(EventFd::new(initval, flags))); + + Ok(fd_num as isize) +} diff --git a/api/linux_syscall_api/src/syscall_fs/imp/io.rs b/api/linux_syscall_api/src/syscall_fs/imp/io.rs new file mode 100644 index 0000000..e79f26c --- /dev/null +++ b/api/linux_syscall_api/src/syscall_fs/imp/io.rs @@ -0,0 +1,846 @@ +//! 负责与 IO 相关的系统调用 +extern crate alloc; +use crate::syscall_fs::solve_path; +use crate::syscall_net::Socket; +use crate::{IoVec, SyscallError, SyscallResult}; +use alloc::string::ToString; +use alloc::sync::Arc; +use alloc::vec; +use axerrno::AxError; +use axfs::api::{FileIOType, OpenFlags, SeekFrom}; + +use axlog::{debug, info}; +use axprocess::current_process; +use axprocess::link::{create_link, real_path}; + +use crate::syscall_fs::ctype::{ + dir::new_dir, + epoll::{EpollCtl, EpollEvent, EpollEventType, EpollFile}, + file::{new_fd, new_inode}, + pipe::make_pipe, +}; +/// 功能:从一个文件描述符中读取; +/// # Arguments +/// * `fd`: usize, 要读取文件的文件描述符。 +/// * `buf`: *mut u8, 一个缓存区,用于存放读取的内容。 +/// * `count`: usize, 要读取的字节数。 +/// 返回值:成功执行,返回读取的字节数。如为0,表示文件结束。错误,则返回-1。 +pub fn syscall_read(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let buf = args[1] as *mut u8; + let count = args[2]; + info!("[read()] fd: {fd}, buf: {buf:?}, len: {count}",); + + if buf.is_null() { + return Err(SyscallError::EFAULT); + } + + let process = current_process(); + + // TODO: 左闭右开 + let buf = match process.manual_alloc_range_for_lazy( + (buf as usize).into(), + (unsafe { buf.add(count) as usize } - 1).into(), + ) { + Ok(_) => unsafe { core::slice::from_raw_parts_mut(buf, count) }, + Err(_) => return Err(SyscallError::EFAULT), + }; + + let file = match process.fd_manager.fd_table.lock().get(fd) { + Some(Some(f)) => f.clone(), + _ => return Err(SyscallError::EBADF), + }; + + if file.get_type() == FileIOType::DirDesc { + axlog::error!("fd is a dir"); + return Err(SyscallError::EISDIR); + } + if !file.readable() { + // 1. nonblocking socket + // + // Normal socket will block while trying to read, so we don't return here. + + if let Some(socket) = file.as_any().downcast_ref::() { + if socket.is_nonblocking() && socket.is_connected() { + return Err(SyscallError::EAGAIN); + } + } else { + // 2. nonblock file + // return ErrorNo::EAGAIN as isize; + // 3. regular file + return Err(SyscallError::EBADF); + } + + #[cfg(not(feature = "net"))] + return Err(SyscallError::EBADF); + } + + // for sockets: + // Sockets are "readable" when: + // - have some data to read without blocking + // - remote end send FIN packet, local read half is closed (this will return 0 immediately) + // this will return Ok(0) + // - ready to accept new connections + + match file.read(buf) { + Ok(len) => Ok(len as isize), + Err(AxError::WouldBlock) => Err(SyscallError::EAGAIN), + Err(AxError::InvalidInput) => Err(SyscallError::EINVAL), + Err(_) => Err(SyscallError::EPERM), + } +} + +/// 功能:从一个文件描述符中写入; +/// # Arguments: +/// * `fd`: usize, 要写入文件的文件描述符。 +/// * `buf`: *const u8, 一个缓存区,用于存放要写入的内容。 +/// * `count`: usize, 要写入的字节数。 +/// 返回值:成功执行,返回写入的字节数。错误,则返回-1。 +pub fn syscall_write(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let buf = args[1] as *const u8; + let count = args[2]; + + info!("[write()] fd: {}, buf: {buf:?}, len: {count}", fd as i32); + if buf.is_null() { + return Err(SyscallError::EFAULT); + } + + let process = current_process(); + + // TODO: 左闭右开 + let buf = match process.manual_alloc_range_for_lazy( + (buf as usize).into(), + (unsafe { buf.add(count) as usize } - 1).into(), + ) { + Ok(_) => unsafe { core::slice::from_raw_parts(buf, count) }, + Err(_) => return Err(SyscallError::EFAULT), + }; + + let file = match process.fd_manager.fd_table.lock().get(fd) { + Some(Some(f)) => f.clone(), + _ => return Err(SyscallError::EBADF), + }; + + if file.get_type() == FileIOType::DirDesc { + debug!("fd is a dir"); + return Err(SyscallError::EBADF); + } + if !file.writable() { + // 1. socket + // + // Normal socket will block while trying to write, so we don't return here. + + if let Some(socket) = file.as_any().downcast_ref::() { + if socket.is_nonblocking() && socket.is_connected() { + return Err(SyscallError::EAGAIN); + } + } else { + // 2. nonblock file + // return ErrorNo::EAGAIN as isize; + + // 3. regular file + return Err(SyscallError::EBADF); + } + } + + // for sockets: + // Sockets are "writable" when: + // - connected and have space in tx buffer to write + // - sent FIN packet, local send half is closed (this will return 0 immediately) + // this will return Err(ConnectionReset) + + match file.write(buf) { + Ok(len) => Ok(len as isize), + // socket with send half closed + // TODO: send a SIGPIPE signal to the process + Err(axerrno::AxError::ConnectionReset) => Err(SyscallError::EPIPE), + Err(AxError::WouldBlock) => Err(SyscallError::EAGAIN), + Err(AxError::InvalidInput) => Err(SyscallError::EINVAL), + Err(_) => Err(SyscallError::EPERM), + } +} + +/// 从同一个文件描述符读取多个字符串 +/// # Arguments +/// * `fd`: usize, 要读取文件的文件描述符。 +/// * `iov`: *mut IoVec, 一个缓存区,用于存放读取的内容。 +/// * `iov_cnt`: usize, 要读取的字节数。 +pub fn syscall_readv(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let iov = args[1] as *mut IoVec; + let iov_cnt = args[2]; + let mut read_len = 0; + // 似乎要判断iov是否分配,但是懒了,反正能过测例 + for i in 0..iov_cnt { + let io: &IoVec = unsafe { &*iov.add(i) }; + if io.base.is_null() || io.len == 0 { + continue; + } + let temp_args = [fd, io.base as usize, io.len, 0, 0, 0]; + match syscall_read(temp_args) { + len if len.is_ok() => read_len += len.unwrap(), + + err => return err, + } + } + Ok(read_len) +} + +/// 从同一个文件描述符写入多个字符串 +/// # Arguments +/// * `fd`: usize, 要写入文件的文件描述符。 +/// * `iov`: *mut IoVec, 一个缓存区,用于存放要写入的内容。 +/// * `iov_cnt`: usize, 要写入的字节数。 +pub fn syscall_writev(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let iov = args[1] as *mut IoVec; + let iov_cnt = args[2]; + let mut write_len = 0; + // 似乎要判断iov是否分配,但是懒了,反正能过测例 + for i in 0..iov_cnt { + let io: &IoVec = unsafe { &(*iov.add(i)) }; + if io.base.is_null() || io.len == 0 { + continue; + } + let temp_args = [fd, io.base as usize, io.len, 0, 0, 0]; + match syscall_write(temp_args) { + len if len.is_ok() => write_len += len.unwrap(), + + err => return err, + } + } + Ok(write_len) +} + +/// 功能:创建管道; +/// # Arguments +/// * `fd[2]`: *mut u32, 用于保存2个文件描述符。其中,`fd[0]`为管道的读出端,`fd[1]`为管道的写入端。 +/// * `flags`: usize, 用于指定管道的属性。 +/// 返回值:成功执行,返回0。失败,返回-1。 +/// +/// 注意:`fd[2]`是32位数组,所以这里的 fd 是 u32 类型的指针,而不是 usize 类型的指针。 +pub fn syscall_pipe2(args: [usize; 6]) -> SyscallResult { + let fd = args[0] as *mut u32; + let flags = args[1] as u32; + axlog::info!("Into syscall_pipe2. fd: {} flags: {}", fd as usize, flags); + let process = current_process(); + if process.manual_alloc_for_lazy((fd as usize).into()).is_err() { + return Err(SyscallError::EINVAL); + } + let (read, write) = make_pipe(OpenFlags::from_bits_truncate(flags)); + let mut fd_table = process.fd_manager.fd_table.lock(); + let fd_num = if let Ok(fd) = process.alloc_fd(&mut fd_table) { + fd + } else { + return Err(SyscallError::EPERM); + }; + fd_table[fd_num] = Some(read); + let fd_num2 = if let Ok(fd) = process.alloc_fd(&mut fd_table) { + fd + } else { + return Err(SyscallError::EPERM); + }; + fd_table[fd_num2] = Some(write); + info!("read end: {} write: end: {}", fd_num, fd_num2); + unsafe { + core::ptr::write(fd, fd_num as u32); + core::ptr::write(fd.offset(1), fd_num2 as u32); + } + Ok(0) +} + +/// 功能:创建管道; +/// # Arguments +/// * `fd[2]`: *mut u32, 用于保存2个文件描述符。其中,`fd[0]`为管道的读出端,`fd[1]`为管道的写入端。 +/// 返回值:成功执行,返回0。失败,返回-1。 +/// +/// 注意:`fd[2]`是32位数组,所以这里的 fd 是 u32 类型的指针,而不是 usize 类型的指针。 +#[cfg(target_arch = "x86_64")] +pub fn syscall_pipe(mut args: [usize; 6]) -> SyscallResult { + args[1] = 0; + syscall_pipe2(args) +} + +/// 功能:复制文件描述符; +/// # Arguments +/// * `fd`: usize, 被复制的文件描述符。 +/// 返回值:成功执行,返回新的文件描述符。失败,返回-1。 +pub fn syscall_dup(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let process = current_process(); + let mut fd_table = process.fd_manager.fd_table.lock(); + if fd >= fd_table.len() { + debug!("fd {} is out of range", fd); + return Err(SyscallError::EBADF); + } + if fd_table[fd].is_none() { + debug!("fd {} is a closed fd", fd); + return Err(SyscallError::EBADF); + } + + let new_fd = if let Ok(fd) = process.alloc_fd(&mut fd_table) { + fd + } else { + // 文件描述符达到上限了 + return Err(SyscallError::EMFILE); + }; + fd_table[new_fd] = fd_table[fd].clone(); + + Ok(new_fd as isize) +} + +/// 功能: 将一个文件从一个文件描述符复制到另一个文件描述符 +/// # Arguments +/// * fd: usize, 原文件所在的文件描述符 +/// * new_fd: usize, 新的文件描述符 +/// 返回值:成功执行,返回新的文件描述符。失败,返回-1。 +#[cfg(target_arch = "x86_64")] +pub fn syscall_dup2(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let new_fd = args[1]; + let process = current_process(); + let mut fd_table = process.fd_manager.fd_table.lock(); + if fd >= fd_table.len() { + debug!("fd {} is out of range", fd); + return Err(SyscallError::EPERM); + } + if fd_table[fd].is_none() { + debug!("fd {} is not opened", fd); + return Err(SyscallError::EPERM); + } + if new_fd >= fd_table.len() { + if new_fd >= (process.fd_manager.get_limit() as usize) { + // 超出了资源限制 + return Err(SyscallError::EBADF); + } + for _i in fd_table.len()..new_fd + 1 { + fd_table.push(None); + } + } + // if process_inner.fd_manager.fd_table[new_fd].is_some() { + // debug!("new_fd {} is already opened", new_fd); + // return ErrorNo::EINVAL as isize; + // } + info!("dup2 fd {} to new fd {}", fd, new_fd); + // 就算new_fd已经被打开了,也可以被重新替代掉 + fd_table[new_fd] = fd_table[fd].clone(); + Ok(new_fd as isize) +} + +/// 功能:复制文件描述符,并指定了新的文件描述符; +/// # Arguments +/// * fd: usize, 原文件所在的文件描述符 +/// * new_fd: usize, 新的文件描述符 +/// * flags: usize, 文件描述符标志 +/// 返回值:成功执行,返回新的文件描述符。失败,返回-1。 +pub fn syscall_dup3(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let new_fd = args[1]; + let flags = args[2]; + let process = current_process(); + let mut fd_table = process.fd_manager.fd_table.lock(); + if fd >= fd_table.len() { + debug!("fd {} is out of range", fd); + return Err(SyscallError::EPERM); + } + if fd_table[fd].is_none() { + debug!("fd {} is not opened", fd); + return Err(SyscallError::EPERM); + } + if fd == new_fd { + debug!("oldfd is equal to newfd"); + return Err(SyscallError::EINVAL); + } + if new_fd >= fd_table.len() { + if new_fd >= (process.fd_manager.get_limit() as usize) { + // 超出了资源限制 + return Err(SyscallError::EBADF); + } + for _i in fd_table.len()..new_fd + 1 { + fd_table.push(None); + } + } + info!("dup3 fd {} to new fd {} with flags {}", fd, new_fd, flags); + fd_table[new_fd] = fd_table[fd].clone(); + if flags as u32 & crate::ctypes::O_CLOEXEC != 0 { + fd_table[new_fd].as_mut().unwrap().set_close_on_exec(true); + } + Ok(new_fd as isize) +} + +/// 功能:打开或创建一个文件; +/// # Arguments +/// * `fd`: usize, 文件所在目录的文件描述符。 +/// * `path`: *const u8, 要打开或创建的文件名。如为绝对路径,则忽略fd。如为相对路径,且fd是AT_FDCWD,则filename是相对于当前工作目录来说的。如为相对路径,且fd是一个文件描述符,则filename是相对于fd所指向的目录来说的。 +/// * `flags`: usize, 必须包含如下访问模式的其中一种:O_RDONLY,O_WRONLY,O_RDWR。还可以包含文件创建标志和文件状态标志。 +/// * `mode`: u8, 文件的所有权描述。详见`man 7 inode `。 +/// 返回值:成功执行,返回新的文件描述符。失败,返回-1。 +/// +/// 说明:如果打开的是一个目录,那么返回的文件描述符指向的是该目录的描述符。(后面会用到针对目录的文件描述符) +/// flags: O_RDONLY: 0, O_WRONLY: 1, O_RDWR: 2, O_CREAT: 64, O_DIRECTORY: 65536 +pub fn syscall_openat(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let path = args[1] as *const u8; + let flags = args[2]; + let _mode = args[3] as u8; + let force_dir = OpenFlags::from(flags).is_dir(); + let path = solve_path(fd, Some(path), force_dir)?; + let process = current_process(); + let mut fd_table = process.fd_manager.fd_table.lock(); + let fd_num: usize = if let Ok(fd) = process.alloc_fd(&mut fd_table) { + fd + } else { + return Err(SyscallError::EMFILE); + }; + debug!("allocated fd_num: {}", fd_num); + // 分配 inode + new_inode(path.path().to_string()).unwrap(); + // 如果是DIR + info!("path: {:?}", path.path()); + if path.is_dir() { + debug!("open dir"); + if let Ok(dir) = new_dir(path.path().to_string(), flags.into()) { + debug!("new dir_desc successfully allocated: {}", path.path()); + fd_table[fd_num] = Some(Arc::new(dir)); + Ok(fd_num as isize) + } else { + debug!("open dir failed"); + Err(SyscallError::ENOENT) + } + } + // 如果是FILE,注意若创建了新文件,需要添加链接 + else { + debug!("open file"); + if let Ok(file) = new_fd(path.path().to_string(), flags.into()) { + debug!("new file_desc successfully allocated"); + fd_table[fd_num] = Some(Arc::new(file)); + let _ = create_link(&path, &path); // 不需要检查是否成功,因为如果成功,说明是新建的文件,如果失败,说明已经存在了 + Ok(fd_num as isize) + } else { + debug!("open file failed"); + Err(SyscallError::ENOENT) + } + } +} + +/// 功能:打开或创建一个文件; +/// # Arguments +/// * `path`: *const u8, filename是相对于当前工作目录来说的。 +/// * `flags`: usize, 必须包含如下访问模式的其中一种:O_RDONLY,O_WRONLY,O_RDWR。还可以包含文件创建标志和文件状态标志。 +/// * `mode`: u8, 文件的所有权描述。详见`man 7 inode `。 +/// 返回值:成功执行,返回新的文件描述符。失败,返回-1。 +/// +/// 说明:如果打开的是一个目录,那么返回的文件描述符指向的是该目录的描述符。(后面会用到针对目录的文件描述符) +/// flags: O_RDONLY: 0, O_WRONLY: 1, O_RDWR: 2, O_CREAT: 64, O_DIRECTORY: 65536 +#[cfg(target_arch = "x86_64")] +pub fn syscall_open(args: [usize; 6]) -> SyscallResult { + use axprocess::link::AT_FDCWD; + let temp_args = [AT_FDCWD, args[0], args[1], args[2], 0, 0]; + syscall_openat(temp_args) +} + +/// To create a file +/// which is equivalent to calling open() with flags equal to O_CREAT|O_WRONLY|O_TRUNC. +#[cfg(target_arch = "x86_64")] +pub fn syscall_creat(args: [usize; 6]) -> SyscallResult { + use axprocess::link::AT_FDCWD; + let path = args[0] as *const u8; + let mode = args[1] as u8; + let flags = OpenFlags::CREATE.bits() | OpenFlags::WRONLY.bits() | OpenFlags::TRUNC.bits(); + let temp_args = [AT_FDCWD, path as usize, flags as usize, mode as usize, 0, 0]; + syscall_openat(temp_args) +} + +/// 功能:关闭一个文件描述符; +/// # Arguments +/// * `fd`: usize, 要关闭的文件描述符。 +/// 返回值:成功执行,返回0。失败,返回-1。 +pub fn syscall_close(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + info!("Into syscall_close. fd: {}", fd); + + let process = current_process(); + let mut fd_table = process.fd_manager.fd_table.lock(); + if fd >= fd_table.len() { + debug!("fd {} is out of range", fd); + return Err(SyscallError::EPERM); + } + // if fd == 3 { + // debug!("fd {} is reserved for cwd", fd); + // return -1; + // } + if fd_table[fd].is_none() { + debug!("fd {} is none", fd); + return Err(SyscallError::EPERM); + } + // let file = process_inner.fd_manager.fd_table[fd].unwrap(); + for i in 0..fd_table.len() { + if let Some(file) = fd_table[i].as_ref() { + if let Some(epoll_file) = file.as_any().downcast_ref::() { + if epoll_file.contains(fd as i32) { + let ev = EpollEvent { + event_type: EpollEventType::EPOLLMSG, + data: 0, + }; + epoll_file.epoll_ctl(EpollCtl::DEL, fd as i32, ev)?; + } + } + } + } + + fd_table[fd] = None; + // for i in 0..process_inner.fd_table.len() { + // if let Some(file) = process_inner.fd_table[i].as_ref() { + // debug!("fd: {} has file", i); + // } + // } + + Ok(0) +} + +/// 67 +/// pread64 +/// 从文件的指定位置读取数据,并且不改变文件的读写指针 +/// # Arguments +/// * `fd`: usize +/// * `buf`: *mut u8 +/// * `count`: usize +/// * `offset`: usize +pub fn syscall_pread64(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let buf = args[1] as *mut u8; + let count = args[2]; + let offset = args[3]; + let process = current_process(); + // todo: 把check fd整合到fd_manager中 + let file = process.fd_manager.fd_table.lock()[fd].clone().unwrap(); + + let old_offset = file.seek(SeekFrom::Current(0)).unwrap(); + let ret = file + .seek(SeekFrom::Start(offset as u64)) + .and_then(|_| file.read(unsafe { core::slice::from_raw_parts_mut(buf, count) })); + file.seek(SeekFrom::Start(old_offset)).unwrap(); + ret.map(|size| Ok(size as isize)) + .unwrap_or_else(|_| Err(SyscallError::EINVAL)) +} + +/// 68 +/// pwrite64 +/// 向文件的指定位置写入数据,并且不改变文件的读写指针 +/// # Arguments +/// * `fd`: usize +/// * `buf`: *const u8 +/// * `count`: usize +/// * `offset`: usize +pub fn syscall_pwrite64(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let buf = args[1] as *const u8; + let count = args[2]; + let offset = args[3]; + let process = current_process(); + + let file = process.fd_manager.fd_table.lock()[fd].clone().unwrap(); + + let old_offset = file.seek(SeekFrom::Current(0)).unwrap(); + + let ret = file.seek(SeekFrom::Start(offset as u64)).and_then(|_| { + let res = file.write(unsafe { core::slice::from_raw_parts(buf, count) }); + res + }); + + file.seek(SeekFrom::Start(old_offset)).unwrap(); + drop(file); + + ret.map(|size| Ok(size as isize)) + .unwrap_or_else(|_| Err(SyscallError::EINVAL)) +} + +/// 71 +/// sendfile64 +/// 将一个文件的内容发送到另一个文件中 +/// 如果offset为NULL,则从当前读写指针开始读取,读取完毕后会更新读写指针 +/// 如果offset不为NULL,则从offset指定的位置开始读取,读取完毕后不会更新读写指针,但是会更新offset的值 +/// # Arguments +/// * `out_fd`: usize +/// * `in_fd`: usize +/// * `offset`: *mut usize +/// * `count`: usize +pub fn syscall_sendfile64(args: [usize; 6]) -> SyscallResult { + let out_fd = args[0]; + let in_fd = args[1]; + let offset = args[2] as *mut usize; + let count = args[3]; + info!("send from {} to {}, count: {}", in_fd, out_fd, count); + let process = current_process(); + let out_file = process.fd_manager.fd_table.lock()[out_fd].clone().unwrap(); + let in_file = process.fd_manager.fd_table.lock()[in_fd].clone().unwrap(); + let old_in_offset = in_file.seek(SeekFrom::Current(0)).unwrap(); + + let mut buf = vec![0u8; count]; + if !offset.is_null() { + // 如果offset不为NULL,则从offset指定的位置开始读取 + let in_offset = unsafe { *offset }; + in_file.seek(SeekFrom::Start(in_offset as u64)).unwrap(); + let ret = in_file.read(buf.as_mut_slice()); + unsafe { *offset = in_offset + ret.unwrap() }; + in_file.seek(SeekFrom::Start(old_in_offset)).unwrap(); + let buf = buf[..ret.unwrap()].to_vec(); + Ok(out_file.write(buf.as_slice()).unwrap() as isize) + } else { + // 如果offset为NULL,则从当前读写指针开始读取 + let ret = in_file.read(buf.as_mut_slice()); + info!("in fd: {}, count: {}", in_fd, count); + let buf = buf[..ret.unwrap()].to_vec(); + info!("read len: {}", buf.len()); + info!("write len: {}", buf.as_slice().len()); + Ok(out_file.write(buf.as_slice()).unwrap() as isize) + } +} + +/// 78 +/// readlinkat +/// 读取符号链接文件的内容 +/// * 如果buf为NULL,则返回符号链接文件的长度 +/// * 如果buf不为NULL,则将符号链接文件的内容写入buf中 +/// 如果写入的内容超出了buf_size则直接截断 +/// # Arguments +/// * `dir_fd`: usize +/// * `path`: *const u8 +/// * `buf`: *mut u8 +/// * `bufsiz`: usize +pub fn syscall_readlinkat(args: [usize; 6]) -> SyscallResult { + let dir_fd = args[0]; + let path = args[1] as *const u8; + let buf = args[2] as *mut u8; + let bufsiz = args[3]; + let process = current_process(); + if process + .manual_alloc_for_lazy((path as usize).into()) + .is_err() + { + return Err(SyscallError::EFAULT); + } + if !buf.is_null() + && process + .manual_alloc_for_lazy((buf as usize).into()) + .is_err() + { + return Err(SyscallError::EFAULT); + } + + let path = solve_path(dir_fd, Some(path), false)?; + + axlog::info!("read link at: {}", path.path()); + // 获取进程自身的符号链接信息 + if path.path() == "/proc/self/exe" { + // 获取该进程符号链接对应的真正地址 + let file_real_path = process.get_file_path(); + let len = bufsiz.min(file_real_path.len()); + let slice = unsafe { core::slice::from_raw_parts_mut(buf, len) }; + slice.copy_from_slice(&file_real_path.as_bytes()[..len]); + + return Ok(file_real_path.len() as isize); + } + + if *path.path() != real_path(&(path.path().to_string())) { + // 说明链接存在 + let path = path.path(); + let len = bufsiz.min(path.len()); + let slice = unsafe { core::slice::from_raw_parts_mut(buf, len) }; + slice.copy_from_slice(&path.as_bytes()[..len]); + return Ok(path.len() as isize); + } + Err(SyscallError::EINVAL) +} + +/// readlinkat +/// 读取符号链接文件的内容 +/// 如果buf为NULL,则返回符号链接文件的长度 +/// 如果buf不为NULL,则将符号链接文件的内容写入buf中 +/// 如果写入的内容超出了buf_size则直接截断 +/// # Arguments +/// * `path`: *const u8 +/// * `buf`: *mut u8 +/// * `bufsiz`: usize +#[cfg(target_arch = "x86_64")] +pub fn syscall_readlink(args: [usize; 6]) -> SyscallResult { + use axprocess::link::AT_FDCWD; + + let temp_args = [AT_FDCWD, args[0], args[1], args[2], 0, 0]; + syscall_readlinkat(temp_args) +} + +/// 62 +/// 移动文件描述符的读写指针 +/// # Arguments +/// * `fd`: usize +/// * `offset`: isize +/// * `whence`: usize +pub fn syscall_lseek(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let offset = args[1] as isize; + let whence = args[2]; + let process = current_process(); + info!("fd: {} offset: {} whence: {}", fd, offset, whence); + if fd >= process.fd_manager.fd_table.lock().len() || fd < 3 { + debug!("fd {} is out of range", fd); + return Err(SyscallError::EBADF); + } + let fd_table = process.fd_manager.fd_table.lock(); + if let Some(file) = fd_table[fd].as_ref() { + if file.get_type() == FileIOType::DirDesc { + debug!("fd is a dir"); + return Err(SyscallError::EISDIR); + } + let ans = if whence == 0 { + // 即SEEK_SET + file.seek(SeekFrom::Start(offset as u64)) + } else if whence == 1 { + // 即SEEK_CUR + file.seek(SeekFrom::Current(offset as i64)) + } else if whence == 2 { + // 即SEEK_END + file.seek(SeekFrom::End(offset as i64)) + } else { + return Err(SyscallError::EINVAL); + }; + if let Ok(now_offset) = ans { + Ok(now_offset as isize) + } else { + Err(SyscallError::EINVAL) + } + } else { + debug!("fd {} is none", fd); + Err(SyscallError::EBADF) + } +} + +/// 82 +/// 写回硬盘 +#[allow(unused)] +/// # Arguments +/// * `fd`: usize +pub fn syscall_fsync(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let process = current_process(); + if fd >= process.fd_manager.fd_table.lock().len() || fd < 3 { + debug!("fd {} is out of range", fd); + return Err(SyscallError::EBADF); + } + let fd_table = process.fd_manager.fd_table.lock(); + if let Some(file) = fd_table[fd].clone() { + // if file.flush().is_err() {} + Ok(0) + } else { + debug!("fd {} is none", fd); + Err(SyscallError::EBADF) + } +} + +/** +该系统调用应复制文件描述符 fd_in 中的至多 len 个字节到文件描述符 fd_out 中。 +若 off_in 为 NULL,则复制时应从文件描述符 fd_in 本身的文件偏移处开始读取,并将其文件偏移增加成功复制的字节数;否则,从 *off_in 指定的文件偏移处开始读取,不改变 fd_in 的文件偏移,而是将 *off_in 增加成功复制的字节数。 +参数 off_out 的行为类似:若 off_out 为 NULL,则复制时从文件描述符 fd_out 本身的文件偏移处开始写入,并将其文件偏移增加成功复制的字节数;否则,从 *off_out 指定的文件偏移处开始写入,不改变 fd_out 的文件偏移,而是将 *off_out 增加成功复制的字节数。 +该系统调用的返回值为成功复制的字节数,出现错误时返回负值。若读取 fd_in 时的文件偏移超过其大小,则直接返回 0,不进行复制。 +本题中,fd_in 和 fd_out 总指向文件系统中两个不同的普通文件;flags 总为 0,没有实际作用。 + */ +/// # Arguments +/// * `fd_in`: usize +/// * `off_in`: *mut usize +/// * `fd_out`: usize +/// * `off_out`: *mut usize +/// * `len`: usize +/// * `flags`: usize +pub fn syscall_copyfilerange(args: [usize; 6]) -> SyscallResult { + let fd_in = args[0]; + let off_in = args[1] as *mut usize; + let fd_out = args[2]; + let off_out = args[3] as *mut usize; + let len = args[4]; + let flags = args[5]; + let in_offset = if off_in.is_null() { + -1 + } else { + unsafe { *off_in as isize } + }; + let out_offset = if off_out.is_null() { + -1 + } else { + unsafe { *off_out as isize } + }; + if len == 0 { + return Ok(0); + } + info!( + "copyfilerange: fd_in: {}, fd_out: {}, off_in: {}, off_out: {}, len: {}, flags: {}", + fd_in, fd_out, in_offset, out_offset, len, flags + ); + let process = current_process(); + let fd_table = process.fd_manager.fd_table.lock(); + let out_file = fd_table[fd_out].clone().unwrap(); + let in_file = fd_table[fd_in].clone().unwrap(); + let old_in_offset = in_file.seek(SeekFrom::Current(0)).unwrap(); + let old_out_offset = out_file.seek(SeekFrom::Current(0)).unwrap(); + + // if in_file.lock().get_stat().unwrap().st_size < (in_offset as u64) + len as u64 { + // return 0; + // } + + // set offset + if !off_in.is_null() { + in_file.seek(SeekFrom::Start(in_offset as u64)).unwrap(); + } + + if !off_out.is_null() { + out_file.seek(SeekFrom::Start(out_offset as u64)).unwrap(); + } + + // copy + let mut buf = vec![0; len]; + let read_len = in_file.read(buf.as_mut_slice()).unwrap(); + // debug!("copy content: {:?}", &buf[..read_len]); + + let write_len = out_file.write(&buf[..read_len]).unwrap(); + // assert_eq!(read_len, write_len); // tmp + + // set offset | modify off_in & off_out + if !off_in.is_null() { + in_file.seek(SeekFrom::Start(old_in_offset)).unwrap(); + unsafe { + *off_in += read_len; + } + } + if !off_out.is_null() { + out_file.seek(SeekFrom::Start(old_out_offset)).unwrap(); + unsafe { + *off_out += write_len; + } + } + + Ok(write_len as isize) +} + +/// # Arguments +/// * `fd`: usize +/// * `len`: usize +pub fn syscall_ftruncate64(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let len = args[1]; + let process = current_process(); + info!("fd: {}, len: {}", fd, len); + let fd_table = process.fd_manager.fd_table.lock(); + if fd >= fd_table.len() { + return Err(SyscallError::EINVAL); + } + if fd_table[fd].is_none() { + return Err(SyscallError::EINVAL); + } + + if let Some(file) = fd_table[fd].as_ref() { + if file.truncate(len).is_err() { + return Err(SyscallError::EINVAL); + } + } + Ok(0) +} diff --git a/api/linux_syscall_api/src/syscall_fs/imp/link.rs b/api/linux_syscall_api/src/syscall_fs/imp/link.rs new file mode 100644 index 0000000..e2a2ec8 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_fs/imp/link.rs @@ -0,0 +1,87 @@ +// const STDIN: usize = 0; +// const STDOUT: usize = 1; +// const STDERR: usize = 2; +extern crate alloc; + +use crate::{SyscallError, SyscallResult}; +use axlog::debug; +use axprocess::link::{create_link, remove_link, FilePath}; + +use super::solve_path; + +/// Special value used to indicate openat should use the current working directory. +pub const AT_REMOVEDIR: usize = 0x200; // Remove directory instead of unlinking file. + +/// 功能:创建文件的链接; +/// # Arguments +/// * `old_dir_fd`: usize, 原来的文件所在目录的文件描述符。 +/// * `old_path`: *const u8, 文件原来的名字。如果old_path是相对路径,则它是相对于old_dir_fd目录而言的。如果old_path是相对路径,且old_dir_fd的值为AT_FDCWD,则它是相对于当前路径而言的。如果old_path是绝对路径,则old_dir_fd被忽略。 +/// * `new_dir_fd`: usize, 新文件名所在的目录。 +/// * `new_path`: *const u8, 文件的新名字。new_path的使用规则同old_path。 +/// * `flags`: usize, 在2.6.18内核之前,应置为0。其它的值详见`man 2 linkat`。 +/// # Return +/// 成功执行,返回0。失败,返回-1。 +#[allow(dead_code)] +pub fn sys_linkat(args: [usize; 6]) -> SyscallResult { + let old_dir_fd = args[0]; + let old_path = args[1] as *const u8; + let new_dir_fd = args[2]; + let new_path = args[3] as *const u8; + let _flags = args[4]; + + let old_path = solve_path(old_dir_fd, Some(old_path), false)?; + let new_path = solve_path(new_dir_fd, Some(new_path), false)?; + if create_link(&old_path, &new_path) { + Ok(0) + } else { + Err(SyscallError::EINVAL) + } +} + +/// 功能:移除指定文件的链接 +/// # Arguments +/// * `path`: *const u8, 要删除的链接的名字。 +/// # Return +/// 成功执行,返回0。失败,返回-1。 +#[cfg(target_arch = "x86_64")] +pub fn syscall_unlink(args: [usize; 6]) -> SyscallResult { + let path = args[0] as *const u8; + let temp_args = [axprocess::link::AT_FDCWD, path as usize, 0, 0, 0, 0]; + syscall_unlinkat(temp_args) +} + +/// 功能:移除指定文件的链接(可用于删除文件); +/// # Arguments +/// * `dir_fd`: usize, 要删除的链接所在的目录。 +/// * `path`: *const u8, 要删除的链接的名字。如果path是相对路径,则它是相对于dir_fd目录而言的。如果path是相对路径,且dir_fd的值为AT_FDCWD,则它是相对于当前路径而言的。如果path是绝对路径,则dir_fd被忽略。 +/// * `flags`: usize, 可设置为0或AT_REMOVEDIR。 +/// # Return +/// 成功执行,返回0。失败,返回-1。 +pub fn syscall_unlinkat(args: [usize; 6]) -> SyscallResult { + let dir_fd = args[0]; + let path = args[1] as *const u8; + let flags = args[2]; + let path = solve_path(dir_fd, Some(path), false)?; + + if path.start_with(&FilePath::new("/proc").unwrap()) { + return Ok(-1); + } + + // remove dir + if flags == AT_REMOVEDIR { + if let Err(e) = axfs::api::remove_dir(path.path()) { + debug!("rmdir error: {:?}", e); + return Err(SyscallError::EINVAL); + } + return Ok(0); + } + let metadata = axfs::api::metadata(path.path()).unwrap(); + if metadata.is_dir() { + return Err(SyscallError::EISDIR); + } + if remove_link(&path).is_none() { + debug!("unlink file error"); + return Err(SyscallError::EINVAL); + } + Ok(0) +} diff --git a/api/linux_syscall_api/src/syscall_fs/imp/mod.rs b/api/linux_syscall_api/src/syscall_fs/imp/mod.rs new file mode 100644 index 0000000..3953dbd --- /dev/null +++ b/api/linux_syscall_api/src/syscall_fs/imp/mod.rs @@ -0,0 +1,40 @@ +//! Implementations of the syscall about file system +extern crate alloc; + +mod ctl; +mod epoll; +mod eventfd; +mod io; +mod link; +mod mount; +mod poll; +mod stat; +use axerrno::AxError; +use axprocess::link::{deal_with_path, FilePath}; +pub use ctl::*; +pub use epoll::*; +pub use eventfd::*; +pub use io::*; +pub use link::*; +pub use mount::*; +pub use poll::*; +pub use stat::*; + +use crate::SyscallError; + +/// To get the real path of the directory or the file by the given path and the directory fd. +pub fn solve_path( + dir_fd: usize, + path_addr: Option<*const u8>, + force_dir: bool, +) -> Result { + match deal_with_path(dir_fd, path_addr, force_dir) { + Ok(path) => Ok(path), + // Only invalid for file descriptor + Err(AxError::InvalidInput) => Err(SyscallError::EBADF), + Err(AxError::NotFound) => Err(SyscallError::ENOENT), + Err(AxError::NotADirectory) => Err(SyscallError::ENOTDIR), + Err(AxError::BadAddress) => Err(SyscallError::EFAULT), + Err(_) => Err(SyscallError::EPERM), + } +} diff --git a/api/linux_syscall_api/src/syscall_fs/imp/mount.rs b/api/linux_syscall_api/src/syscall_fs/imp/mount.rs new file mode 100644 index 0000000..e52a881 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_fs/imp/mount.rs @@ -0,0 +1,121 @@ +use crate::{syscall_fs::solve_path, SyscallError, SyscallResult}; +use axprocess::{ + current_process, + link::{raw_ptr_to_ref_str, AT_FDCWD}, +}; + +// use super::{deal_with_path, AT_FDCWD}; +use crate::syscall_fs::ctype::mount::{check_mounted, mount_fat_fs, umount_fat_fs}; +extern crate alloc; +use alloc::string::ToString; +use axlog::debug; +/// 功能:挂载文件系统; +/// # Arguments +/// * `special`: *const u8, 挂载设备 +/// * `dir`: *const u8, 挂载点 +/// * `fs_type`: *const u8, 挂载的文件系统类型 +/// * `flags`: usize, 挂载参数 +/// * `data`: *const u8, 传递给文件系统的字符串参数,可为NULL +/// 返回值:成功返回0,失败返回-1 +pub fn syscall_mount(args: [usize; 6]) -> SyscallResult { + let special = args[0] as *const u8; + let dir = args[1] as *const u8; + let fs_type = args[2] as *const u8; + let _flags = args[3]; + let _data = args[4] as *const u8; + let device_path = solve_path(AT_FDCWD, Some(special), false)?; + axlog::error!("syscall_mount dev: {:?}", args); + // 这里dir必须以"/"结尾,但在shell中输入时,不需要以"/"结尾 + let mount_path = solve_path(AT_FDCWD, Some(dir), true)?; + axlog::error!("syscall_mount mount: {:?}", args); + + let process = current_process(); + if process + .manual_alloc_for_lazy((fs_type as usize).into()) + .is_err() + { + return Err(SyscallError::EINVAL); + } + + let fs_type = unsafe { raw_ptr_to_ref_str(fs_type).to_string() }; + let mut _data_str = "".to_string(); + if !_data.is_null() { + if process + .manual_alloc_for_lazy((_data as usize).into()) + .is_err() + { + return Err(SyscallError::EINVAL); + } + // data可以为NULL, 必须判断, 否则会panic, 发生LoadPageFault + _data_str = unsafe { raw_ptr_to_ref_str(_data) }.to_string(); + } + if device_path.is_dir() { + debug!("device_path should not be a dir"); + return Err(SyscallError::EPERM); + } + if !mount_path.is_dir() { + debug!("mount_path should be a dir"); + return Err(SyscallError::EPERM); + } + + // 如果mount_path不存在,则创建 + if !axfs::api::path_exists(mount_path.path()) { + if let Err(e) = axfs::api::create_dir(mount_path.path()) { + debug!("create mount path error: {:?}", e); + return Err(SyscallError::EPERM); + } + } + + if fs_type != "vfat" { + debug!("fs_type can only be vfat."); + return Err(SyscallError::EPERM); + } + // 检查挂载点路径是否存在 + if !axfs::api::path_exists(mount_path.path()) { + debug!("mount path not exist"); + return Err(SyscallError::EPERM); + } + // 查挂载点是否已经被挂载 + if check_mounted(&mount_path) { + debug!("mount path includes mounted fs"); + return Err(SyscallError::EPERM); + } + // 挂载 + if !mount_fat_fs(&device_path, &mount_path) { + debug!("mount error"); + return Err(SyscallError::EPERM); + } + + Ok(0) +} + +/// 功能:卸载文件系统; +/// 输入:指定卸载目录,卸载参数; +/// 返回值:成功返回0,失败返回-1 +/// # Arguments +/// * `dir`: *const u8, 指定卸载目录 +/// * `flags`: usize, 卸载参数 +pub fn syscall_umount(args: [usize; 6]) -> SyscallResult { + let dir = args[0] as *const u8; + let flags = args[1]; + let mount_path = solve_path(AT_FDCWD, Some(dir), true)?; + axlog::error!("syscall_umount: {:?}", args); + + if flags != 0 { + debug!("flags unimplemented"); + return Err(SyscallError::EPERM); + } + + // 检查挂载点路径是否存在 + if !axfs::api::path_exists(mount_path.path()) { + debug!("mount path not exist"); + return Err(SyscallError::EPERM); + } + // 从挂载点中删除 + if !umount_fat_fs(&mount_path) { + debug!("umount error"); + return Err(SyscallError::EPERM); + } + + Ok(0) +} diff --git a/api/linux_syscall_api/src/syscall_fs/imp/poll.rs b/api/linux_syscall_api/src/syscall_fs/imp/poll.rs new file mode 100644 index 0000000..86fc185 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_fs/imp/poll.rs @@ -0,0 +1,410 @@ +use axfs::api::FileIO; +use axhal::{mem::VirtAddr, time::current_ticks}; +use axprocess::{current_process, yield_now_task}; +use axsignal::signal_no::SignalNo; +use bitflags::bitflags; +extern crate alloc; +use crate::{SyscallError, SyscallResult, TimeSecs, TimeVal}; +use alloc::{sync::Arc, vec::Vec}; +bitflags! { + /// 在文件上等待或者发生过的事件 + #[derive(Clone, Copy,Debug)] + pub struct PollEvents: u16 { + /// 可读 + const IN = 0x0001; + /// 可写 + const OUT = 0x0004; + /// 错误 + const ERR = 0x0008; + /// 挂起,如pipe另一端关闭 + const HUP = 0x0010; + /// 无效的事件 + const NVAL = 0x0020; + } +} + +#[derive(Default)] +/// file set used for ppoll +struct PpollFdSet { + files: Vec>, + fds: Vec, + shadow_bitset: ShadowBitset, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +/// file descriptor used for poll +pub struct PollFd { + /// 等待的fd + pub fd: i32, + /// 等待的事件 + pub events: PollEvents, + /// 返回的事件 + pub revents: PollEvents, +} + +/// 定义一个bitset,用于查找掩码 +struct ShadowBitset { + /// start address of the bitset which is in user space + addr: *mut usize, + /// 是包含的bit数目,而不是字节数目 + len: usize, +} + +impl Default for ShadowBitset { + fn default() -> Self { + Self { + addr: core::ptr::null_mut(), + len: 0, + } + } +} + +impl ShadowBitset { + /// create a new bitset + pub fn new(addr: *mut usize, len: usize) -> Self { + Self { addr, len } + } + + /// check if the index is set + pub fn check(&self, index: usize) -> bool { + if index >= self.len { + return false; + } + // 因为一次add会移动八个字节,所以这里需要除以64,即8个字节,每一个字节8位 + let byte_index = index / 64; + let bit_index = index & 0x3f; + unsafe { *self.addr.add(byte_index) & (1 << bit_index) != 0 } + } + + /// set the index in the bitset + pub fn set(&mut self, index: usize) { + if index >= self.len { + return; + } + let byte_index = index / 64; + let bit_index = index & 0x3f; + unsafe { + *self.addr.add(byte_index) |= 1 << bit_index; + } + } + + // 清空自己 + pub fn clear(&self) { + for i in 0..=(self.len - 1) / 64 { + unsafe { + *(self.addr.add(i)) = 0; + } + } + } + + /// check if the bitset is valid + /// + /// if the addr is null, it is invalid + pub fn valid(&self) -> bool { + self.addr as usize != 0 + } +} + +/// 实现ppoll系统调用 +/// +/// fds:一个PollFd列表 +/// expire_time:时间戳,用来记录是否超时 +/// +/// 返回值:(usize, Vec) 第一个参数遵守 ppoll 系统调用的返回值约定,第二个参数为返回的 `PollFd` 列表 +fn ppoll(mut fds: Vec, expire_time: usize) -> (isize, Vec) { + loop { + // 满足事件要求而被触发的事件描述符数量 + let mut set: isize = 0; + let process = current_process(); + for poll_fd in &mut fds { + let fd_table = process.fd_manager.fd_table.lock(); + if let Some(file) = fd_table[poll_fd.fd as usize].as_ref() { + poll_fd.revents = PollEvents::empty(); + // let file = file.lock(); + if file.in_exceptional_conditions() { + poll_fd.revents |= PollEvents::ERR; + } + if file.is_hang_up() { + poll_fd.revents |= PollEvents::HUP; + } + if poll_fd.events.contains(PollEvents::IN) && file.ready_to_read() { + poll_fd.revents |= PollEvents::IN; + } + if poll_fd.events.contains(PollEvents::OUT) && file.ready_to_write() { + poll_fd.revents |= PollEvents::OUT; + } + // 如果返回事件不为空,代表有响应 + if !poll_fd.revents.is_empty() { + set += 1; + } + } else { + // 不存在也是一种响应 + poll_fd.revents = PollEvents::ERR; + set += 1; + } + } + if set > 0 { + return (set, fds); + } + if current_ticks() as usize > expire_time { + // 过期了,直接返回 + return (0, fds); + } + yield_now_task(); + + if process.have_signals().is_some() { + // 有信号,此时停止处理,直接返回 + return (0, fds); + } + } +} + +/// 实现ppoll系统调用 +/// +/// 其中timeout是一段相对时间,需要计算出相对于当前时间戳的绝对时间戳 +/// +/// # Arguments +/// * `ufds` - *mut PollFd +/// * `nfds` - usize +/// * `timeout` - *const TimeSecs +/// * `mask` - usize +pub fn syscall_ppoll(args: [usize; 6]) -> SyscallResult { + let ufds = args[0] as *mut PollFd; + let nfds = args[1]; + let timeout = args[2] as *const TimeSecs; + let _mask = args[3]; + let process = current_process(); + + let start: VirtAddr = (ufds as usize).into(); + let end = start + nfds * core::mem::size_of::(); + if process.manual_alloc_range_for_lazy(start, end).is_err() { + return Err(SyscallError::EFAULT); + } + + let mut fds: Vec = Vec::new(); + + for i in 0..nfds { + unsafe { + fds.push(*(ufds.add(i))); + } + } + + let expire_time = if timeout as usize != 0 { + if process.manual_alloc_type_for_lazy(timeout).is_err() { + return Err(SyscallError::EFAULT); + } + current_ticks() as usize + unsafe { (*timeout).get_ticks() } + } else { + usize::MAX + }; + + let (set, ret_fds) = ppoll(fds, expire_time); + // 将得到的fd存储到原先的指针中 + for (i, fd) in ret_fds.iter().enumerate() { + unsafe { + *(ufds.add(i)) = *fd; + } + } + Ok(set) +} + +/// 实现poll系统调用 +/// # Arguments +/// * `ufds` - *mut PollFd +/// * `nfds` - usize +/// * `timeout_msecs` - usize +#[cfg(target_arch = "x86_64")] +pub fn syscall_poll(args: [usize; 6]) -> SyscallResult { + let ufds = args[0] as *mut PollFd; + let nfds = args[1]; + let timeout_msecs = args[2]; + let process = current_process(); + + let start: VirtAddr = (ufds as usize).into(); + let end = start + nfds * core::mem::size_of::(); + if process.manual_alloc_range_for_lazy(start, end).is_err() { + return Err(SyscallError::EFAULT); + } + + let mut fds: Vec = Vec::new(); + + for i in 0..nfds { + unsafe { + fds.push(*(ufds.add(i))); + } + } + let expire_time = current_ticks() as usize + + crate::TimeVal::from_micro(timeout_msecs).turn_to_ticks() as usize; + + let (set, ret_fds) = ppoll(fds, expire_time); + // 将得到的fd存储到原先的指针中 + for (i, fd) in ret_fds.iter().enumerate() { + unsafe { + *(ufds.add(i)) = *fd; + } + } + Ok(set) +} + +/// 根据给定的地址和长度新建一个fd set,包括文件描述符指针数组,文件描述符数值数组,以及一个bitset +fn init_fd_set(addr: *mut usize, len: usize) -> Result { + let process = current_process(); + if len >= process.fd_manager.get_limit() as usize { + axlog::error!( + "[pselect6()] len {len} >= limit {}", + process.fd_manager.get_limit() + ); + return Err(SyscallError::EINVAL); + } + + let shadow_bitset = ShadowBitset::new(addr, len); + if addr.is_null() { + return Ok(PpollFdSet { + shadow_bitset, + ..Default::default() + }); + } + + let start: VirtAddr = (addr as usize).into(); + let end = start + (len + 7) / 8; + if process.manual_alloc_range_for_lazy(start, end).is_err() { + axlog::error!("[pselect6()] addr {addr:?} invalid"); + return Err(SyscallError::EFAULT); + } + + let mut fds = Vec::new(); + let mut files = Vec::new(); + for fd in 0..len { + if shadow_bitset.check(fd) { + let fd_table = process.fd_manager.fd_table.lock(); + if let Some(file) = fd_table[fd].as_ref() { + files.push(Arc::clone(file)); + fds.push(fd); + } else { + return Err(SyscallError::EBADF); + } + } + } + + shadow_bitset.clear(); + + Ok(PpollFdSet { + files, + fds, + shadow_bitset, + }) +} + +// 实现 select 系统调用 +/// # Arguments +/// * `nfds` - usize +/// * `readfds` - *mut usize +/// * `writefds` - *mut usize +/// * `exceptfds` - *mut usize +/// * `timeout` - *const TimeSecs +#[cfg(target_arch = "x86_64")] +pub fn syscall_select(mut args: [usize; 6]) -> SyscallResult { + args[5] = 0; + syscall_pselect6(args) +} +/// 实现pselect6系统调用 +/// # Arguments +/// * `nfds` - usize +/// * `readfds` - *mut usize +/// * `writefds` - *mut usize +/// * `exceptfds` - *mut usize +/// * `timeout` - *const TimeVal +/// * `mask` - usize +pub fn syscall_pselect6(args: [usize; 6]) -> SyscallResult { + let nfds = args[0]; + let readfds = args[1] as *mut usize; + let writefds = args[2] as *mut usize; + let exceptfds = args[3] as *mut usize; + let timeout = args[4] as *const TimeVal; + let _mask = args[5]; + let (rfiles, rfds, mut rset) = match init_fd_set(readfds, nfds) { + Ok(ans) => (ans.files, ans.fds, ans.shadow_bitset), + Err(e) => return Err(e), + }; + let (wfiles, wfds, mut wset) = match init_fd_set(writefds, nfds) { + Ok(ans) => (ans.files, ans.fds, ans.shadow_bitset), + Err(e) => return Err(e), + }; + let (efiles, efds, mut eset) = match init_fd_set(exceptfds, nfds) { + Ok(ans) => (ans.files, ans.fds, ans.shadow_bitset), + Err(e) => return Err(e), + }; + let process = current_process(); + + let expire_time = if !timeout.is_null() { + if process + .memory_set + .lock() + .lock() + .manual_alloc_type_for_lazy(timeout) + .is_err() + { + axlog::error!("[pselect6()] timeout addr {timeout:?} invalid"); + return Err(SyscallError::EFAULT); + } + current_ticks() as usize + unsafe { (*timeout).turn_to_ticks() as usize } + } else { + usize::MAX + }; + + axlog::debug!("[pselect6()]: r: {rfds:?}, w: {wfds:?}, e: {efds:?}"); + + loop { + // Why yield first? + // + // 当用户程序中出现如下结构: + // while (true) { select(); } + // 如果存在 ready 的 fd,select() 立即返回, + // 但并不完全满足用户程序的要求,可能出现死循环。 + // + // 因此先 yield 避免其他进程 starvation。 + // + // 可见 iperf 测例。 + yield_now_task(); + + let mut set = 0; + if rset.valid() { + for i in 0..rfds.len() { + if rfiles[i].ready_to_read() { + rset.set(rfds[i]); + set += 1; + } + } + } + if wset.valid() { + for i in 0..wfds.len() { + if wfiles[i].ready_to_write() { + wset.set(wfds[i]); + set += 1; + } + } + } + if eset.valid() { + for i in 0..efds.len() { + if efiles[i].in_exceptional_conditions() { + eset.set(efds[i]); + set += 1; + } + } + } + if set > 0 { + return Ok(set as isize); + } + if current_ticks() as usize > expire_time { + return Ok(0); + } + // TODO: fix this and use mask to ignore specific signal + + if let Some(signalno) = process.have_signals() { + if signalno == SignalNo::SIGKILL as usize { + return Err(SyscallError::EINTR); + } + } + } +} diff --git a/api/linux_syscall_api/src/syscall_fs/imp/stat.rs b/api/linux_syscall_api/src/syscall_fs/imp/stat.rs new file mode 100644 index 0000000..1ac4215 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_fs/imp/stat.rs @@ -0,0 +1,158 @@ +//! 获取文件系统状态信息 +//! + +use crate::{get_fs_stat, syscall_fs::solve_path, FsStat, FsStatx, SyscallError, SyscallResult}; +use axfs::api::Kstat; +use axlog::{debug, info}; +use axprocess::{ + current_process, + link::{raw_ptr_to_ref_str, FilePath, AT_FDCWD}, +}; + +use crate::syscall_fs::ctype::mount::get_stat_in_fs; + +/// 实现 stat 系列系统调用 +/// # Arguments +/// * `fd` - usize +/// * `kst` - *mut Kstat +pub fn syscall_fstat(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let kst = args[1] as *mut Kstat; + let process = current_process(); + let fd_table = process.fd_manager.fd_table.lock(); + + if fd >= fd_table.len() { + debug!("fd {} is out of range", fd); + return Err(SyscallError::EPERM); + } + if fd_table[fd].is_none() { + debug!("fd {} is none", fd); + return Err(SyscallError::EPERM); + } + let file = fd_table[fd].clone().unwrap(); + + match file.get_stat() { + Ok(stat) => { + unsafe { + *kst = stat; + info!("stat: {:?}", stat); + } + Ok(0) + } + Err(e) => { + debug!("get stat error: {:?}", e); + Err(SyscallError::EPERM) + } + } +} + +/// 获取文件状态信息,但是给出的是目录 fd 和相对路径。 +/// # Arguments +/// * `dir_fd` - usize +/// * `path` - *const u8 +/// * `kst` - *mut Kstat +pub fn syscall_fstatat(args: [usize; 6]) -> SyscallResult { + let dir_fd = args[0]; + let path = args[1] as *const u8; + let kst = args[2] as *mut Kstat; + let file_path = if let Ok(file_path) = solve_path(dir_fd, Some(path), false) { + // error!("test {:?}", file_path); + file_path + } else { + // x86 下应用会调用 newfstatat(1, "", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0xe), ...}, AT_EMPTY_PATH) = 0 + // 去尝试检查 STDOUT 的属性。这里暂时先特判,以后再改成真正的 stdout 的属性 + let path = unsafe { raw_ptr_to_ref_str(path) }; + if path.is_empty() && dir_fd == 1 { + unsafe { + (*kst).st_mode = 0o20000 | 0o220u32; + (*kst).st_ino = 1; + (*kst).st_nlink = 1; + } + return Ok(0); + } + panic!("Wrong path at syscall_fstatat: {}(dir_fd={})", path, dir_fd); + }; + info!("path : {}", file_path.path()); + if !axfs::api::path_exists(file_path.path()) { + return Err(SyscallError::ENOENT); + } + match get_stat_in_fs(&file_path) { + Ok(stat) => unsafe { + *kst = stat; + info!("stat: {:?}", stat); + Ok(0) + }, + Err(error_no) => { + debug!("get stat error: {:?}", error_no); + Err(error_no) + } + } +} + +/// 获取文件状态信息 +/// # Arguments +/// * `path` - *const u8 +/// * `kst` - *mut Kstat +#[cfg(target_arch = "x86_64")] +pub fn syscall_lstat(args: [usize; 6]) -> SyscallResult { + let path = args[0]; + let kst = args[1]; + let temp_args = [AT_FDCWD, path, kst, 0, 0, 0]; + syscall_fstatat(temp_args) +} + +/// 获取文件状态信息 +/// # Arguments +/// * `path` - *const u8 +/// * `stat_ptr` - *mut Kstat +#[cfg(target_arch = "x86_64")] +pub fn syscall_stat(args: [usize; 6]) -> SyscallResult { + let path = args[0]; + let stat_ptr = args[1]; + let temp_args = [AT_FDCWD, path, stat_ptr, 0, 0, 0]; + syscall_fstatat(temp_args) +} + +/// 获取文件系统的信息 +/// # Arguments +/// * `path` - *const u8 +/// * `stat` - *mut FsStat +pub fn syscall_statfs(args: [usize; 6]) -> SyscallResult { + let path = args[0] as *const u8; + let stat = args[1] as *mut FsStat; + let _file_path = solve_path(AT_FDCWD, Some(path), false)?; + axlog::warn!("Only support fs_stat for root"); + + unsafe { + *stat = get_fs_stat(); + } + + Ok(0) +} + +/// get file status (extended) +/// https://man7.org/linux/man-pages/man2/statx.2.html +/// This function returns information about a file, storing it in the +/// buffer pointed to by statxbuf. The returned buffer is a +/// structure of the following type +/// +/// +pub fn syscall_statx(args: [usize; 6]) -> SyscallResult { + let dir_fd = args[0]; + let path = args[1] as *const u8; + let stat = args[4] as *mut FsStatx; + let file_path = solve_path(dir_fd, Some(path), false)?; + if !axfs::api::path_exists(file_path.path()) { + return Err(SyscallError::ENOENT); + } + if let Ok(p) = FilePath::new("/") { + if file_path.equal_to(&p) { + // 目前只支持访问根目录文件系统的信息 + axlog::warn!("Only support fs_stat for root"); + unsafe { + *stat = FsStatx::new(); + } + } + } + Ok(0) +} diff --git a/api/linux_syscall_api/src/syscall_fs/mod.rs b/api/linux_syscall_api/src/syscall_fs/mod.rs new file mode 100644 index 0000000..72e8c03 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_fs/mod.rs @@ -0,0 +1,124 @@ +//! 文件系统相关系统调用 + +pub mod ctype; +pub mod imp; + +use crate::SyscallResult; +use axerrno::AxResult; +use axfs::api::{File, OpenFlags}; +pub use ctype::FileDesc; +mod fs_syscall_id; +pub use fs_syscall_id::FsSyscallId::{self, *}; +extern crate alloc; +use imp::*; + +/// 若使用多次new file打开同名文件,那么不同new file之间读写指针不共享,但是修改的内容是共享的 +pub fn new_file(path: &str, flags: &OpenFlags) -> AxResult { + let mut file = File::options(); + file.read(flags.readable()); + file.write(flags.writable()); + file.create(flags.creatable()); + file.create_new(flags.new_creatable()); + file.open(path) +} + +/// 文件系统相关系统调用 +pub fn fs_syscall(syscall_id: fs_syscall_id::FsSyscallId, args: [usize; 6]) -> SyscallResult { + match syscall_id { + OPENAT => syscall_openat(args), + CLOSE => syscall_close(args), + READ => syscall_read(args), + WRITE => syscall_write(args), + GETCWD => syscall_getcwd(args), + PIPE2 => syscall_pipe2(args), + DUP => syscall_dup(args), + DUP3 => syscall_dup3(args), + MKDIRAT => syscall_mkdirat(args), + CHDIR => syscall_chdir(args), + GETDENTS64 => syscall_getdents64(args), + MOUNT => syscall_mount(args), + UNMOUNT => syscall_umount(args), + FSTAT => syscall_fstat(args), + RENAMEAT | RENAMEAT2 => syscall_renameat2(args), + READV => syscall_readv(args), + WRITEV => syscall_writev(args), + FCNTL64 => syscall_fcntl64(args), + FSTATAT => syscall_fstatat(args), + STATFS => syscall_statfs(args), + FCHMODAT => syscall_fchmodat(args), + FACCESSAT => syscall_faccessat(args), + LSEEK => syscall_lseek(args), + PREAD64 => syscall_pread64(args), + PREADLINKAT => syscall_readlinkat(args), + PWRITE64 => syscall_pwrite64(args), + SENDFILE64 => syscall_sendfile64(args), + FSYNC => Ok(0), + FTRUNCATE64 => { + syscall_ftruncate64(args) + // 0 + } + IOCTL => syscall_ioctl(args), + // 不做处理即可 + SYNC => Ok(0), + COPYFILERANGE => syscall_copyfilerange(args), + LINKAT => sys_linkat(args), + UNLINKAT => syscall_unlinkat(args), + SYMLINKAT => Ok(0), + UTIMENSAT => syscall_utimensat(args), + EPOLL_CREATE => syscall_epoll_create1(args), + EPOLL_CTL => syscall_epoll_ctl(args), + EPOLL_PWAIT => syscall_epoll_pwait(args), + PPOLL => syscall_ppoll(args), + PSELECT6 => syscall_pselect6(args), + STATX => syscall_statx(args), + PIDFD_OPEN => syscall_pidfd_open(args), + FCHOWN => Ok(0), + #[cfg(not(target_arch = "x86_64"))] + EVENTFD => syscall_eventfd(args), + #[cfg(target_arch = "x86_64")] + // eventfd syscall in x86_64 does not support flags, use 0 instead + EVENTFD => syscall_eventfd([args[0], 0, 0, 0, 0, 0]), + #[cfg(target_arch = "x86_64")] + EVENTFD2 => syscall_eventfd(args), + #[cfg(target_arch = "x86_64")] + DUP2 => syscall_dup2(args), + #[cfg(target_arch = "x86_64")] + LSTAT => syscall_lstat(args), + #[cfg(target_arch = "x86_64")] + OPEN => syscall_open(args), + #[cfg(target_arch = "x86_64")] + PIPE => syscall_pipe(args), + #[cfg(target_arch = "x86_64")] + POLL => syscall_poll(args), + #[cfg(target_arch = "x86_64")] + STAT => syscall_stat(args), + #[cfg(target_arch = "x86_64")] + UNLINK => syscall_unlink(args), + #[cfg(target_arch = "x86_64")] + ACCESS => syscall_access(args), + #[cfg(target_arch = "x86_64")] + MKDIR => syscall_mkdir(args), + #[cfg(target_arch = "x86_64")] + RENAME => syscall_rename(args), + #[cfg(target_arch = "x86_64")] + RMDIR => syscall_rmdir(args), + #[cfg(target_arch = "x86_64")] + SELECT => syscall_select(args), + #[cfg(target_arch = "x86_64")] + READLINK => syscall_readlink(args), + #[cfg(target_arch = "x86_64")] + CREAT => syscall_creat(args), + #[cfg(target_arch = "x86_64")] + EPOLL_CREATE1 => syscall_epoll_create1(args), + // EPOLL_CREATE1 => unimplemented!("epoll_create1"), + #[cfg(target_arch = "x86_64")] + EPOLL_WAIT => syscall_epoll_wait(args), + // EPOLL_PWAIT => unimplemented!("epoll_ctl"), + #[cfg(target_arch = "x86_64")] + CHMOD => Ok(0), + #[cfg(target_arch = "x86_64")] + CHOWN => Ok(0), + #[cfg(target_arch = "x86_64")] + MKNOD => Ok(0), + } +} diff --git a/api/linux_syscall_api/src/syscall_mem/imp.rs b/api/linux_syscall_api/src/syscall_mem/imp.rs new file mode 100644 index 0000000..24e962c --- /dev/null +++ b/api/linux_syscall_api/src/syscall_mem/imp.rs @@ -0,0 +1,391 @@ +use crate::{syscall_fs::FileDesc, MMAPFlags, MREMAPFlags, SyscallError, SyscallResult, MMAPPROT}; +extern crate alloc; + +use axerrno::AxError; +use axhal::{arch::flush_tlb, mem::VirtAddr, paging::MappingFlags}; +use axlog::info; +use axmem::MemorySet; + +use axprocess::current_process; +use bitflags::bitflags; + +const MAX_HEAP_SIZE: usize = 0x20000; +/// 修改用户堆大小, +/// +/// - 如输入 brk 为 0 ,则返回堆顶地址 +/// - 重新设置堆顶地址,如成功则返回设置后的堆顶地址,否则保持不变,并返回之前的堆顶地址。 +/// +/// # Arguments +/// * `brk` - usize +pub fn syscall_brk(args: [usize; 6]) -> SyscallResult { + let brk = args[0]; + let curr_process = current_process(); + let mut return_val: isize = curr_process.get_heap_top() as isize; + let heap_bottom = curr_process.get_heap_bottom() as usize; + if brk != 0 && brk >= heap_bottom && brk <= heap_bottom + MAX_HEAP_SIZE { + curr_process.set_heap_top(brk as u64); + return_val = brk as isize; + } + Ok(return_val) +} + +/// 将文件内容映射到内存中 +/// offset参数指定了从文件区域中的哪个字节开始映射,它必须是系统分页大小的倍数 +/// len指定了映射文件的长度 +/// prot指定了页面的权限 +/// flags指定了映射的方法 +/// # Arguments +/// * `start` - usize +/// * `len` - usize +/// * `prot` - MMAPPROT +/// * `flags` - MMAPFlags +/// * `fd` - i32 +/// * `offset` - usize +pub fn syscall_mmap(args: [usize; 6]) -> SyscallResult { + let start = args[0]; + let len = args[1]; + let prot = MMAPPROT::from_bits_truncate(args[2] as u32); + let flags = MMAPFlags::from_bits_truncate(args[3] as u32); + let fd = args[4] as i32; + let offset = args[5]; + use axmem::MemBackend; + axlog::info!("flags: {:?}", flags); + let fixed = flags.contains(MMAPFlags::MAP_FIXED); + // try to map to NULL + if fixed && start == 0 { + return Err(SyscallError::EINVAL); + } + + let process = current_process(); + let shared = flags.contains(MMAPFlags::MAP_SHARED); + let result = if flags.contains(MMAPFlags::MAP_ANONYMOUS) { + // no file + if offset != 0 { + return Err(SyscallError::EINVAL); + } + process + .memory_set + .lock() + .lock() + .mmap(start.into(), len, prot.into(), shared, fixed, None) + } else { + // file backend + axlog::debug!("[mmap] fd: {}, offset: 0x{:x}", fd, offset); + if fd >= process.fd_manager.fd_table.lock().len() as i32 || fd < 0 { + return Err(SyscallError::EINVAL); + } + let file = match &process.fd_manager.fd_table.lock()[fd as usize] { + // 文件描述符表里面存的是文件描述符,这很合理罢 + Some(file) => alloc::boxed::Box::new( + file.as_any() + .downcast_ref::() + .expect("Try to mmap with a non-file backend") + .file + .lock() + .clone(), + ), + // fd not found + None => return Err(SyscallError::EINVAL), + }; + + let backend = MemBackend::new(file, offset as u64); + process.memory_set.lock().lock().mmap( + start.into(), + len, + prot.into(), + shared, + fixed, + Some(backend), + ) + }; + + flush_tlb(None); + // info!("val: {}", unsafe { *(addr as *const usize) }); + match result { + Ok(addr) => Ok(addr as isize), + Err(AxError::NoMemory) => Err(SyscallError::ENOMEM), + Err(_) => Err(SyscallError::EINVAL), + } +} + +/// # Arguments +/// * `start` - usize +/// * `len` - usize +pub fn syscall_munmap(args: [usize; 6]) -> SyscallResult { + let start = args[0]; + let len = args[1]; + let process = current_process(); + process.memory_set.lock().lock().munmap(start.into(), len); + flush_tlb(None); + Ok(0) +} + +/// # Arguments +/// * `start` - usize +/// * `len` - usize +pub fn syscall_msync(args: [usize; 6]) -> SyscallResult { + let start = args[0]; + let len = args[1]; + let process = current_process(); + process.memory_set.lock().lock().msync(start.into(), len); + + Ok(0) +} + +/// # Arguments +/// * `start` - usize +/// * `len` - usize +/// * `prot` - MMAPPROT +pub fn syscall_mprotect(args: [usize; 6]) -> SyscallResult { + let start = args[0]; + let len = args[1]; + let prot = MMAPPROT::from_bits_truncate(args[2] as u32); + let process = current_process(); + + process + .memory_set + .lock() + .lock() + .mprotect(VirtAddr::from(start), len, prot.into()); + + flush_tlb(None); + Ok(0) +} + +/// # Arguments +/// * `old_addr` - usize +/// * `old_size` - usize +/// * `new_size` - usize +/// * `flags` - usize +/// * `new_addr` - usize +pub fn syscall_mremap(args: [usize; 6]) -> SyscallResult { + use axlog::info; + + let old_addr = args[0]; + let old_size = args[1]; + let new_size = args[2]; + let flags = args[3]; + let new_addr = args[4]; + + info!( + "[mremap] old_addr: 0x{:x}, old_size: 0x{:x}, new_size: 0x{:x}, flags: {}, new_addr: {}", + old_addr, old_size, new_size, flags, new_addr, + ); + + // old_addr must be aligned + // new_size must be greater than 0 + if !(VirtAddr::from(old_addr).is_aligned_4k()) || new_size == 0 { + return Err(SyscallError::EINVAL); + }; + + // (new_addr, new_addr + size) must not overlap with (old_addr, old_addr + old_size) + if !(new_addr + new_size <= old_addr || new_addr >= old_addr + old_size) { + return Err(SyscallError::EINVAL); + }; + + let flags = MREMAPFlags::from_bits_truncate(args[3] as u32); + let maymove = flags.contains(MREMAPFlags::MREMAP_MAYMOVE); + let fixed = flags.contains(MREMAPFlags::MREMAP_FIXED); + let dontunmap = flags.contains(MREMAPFlags::MREMAP_DONTUNMAP); + + // MREMAP_FIXED was specified without MREMAP_MAYMOVE + if fixed && !maymove { + return Err(SyscallError::EINVAL); + }; + + // MREMAP_DONTUNMAP was specified without MREMAP_MAYMOVE + if dontunmap && !maymove { + return Err(SyscallError::EINVAL); + }; + + // MREMAP_DONTUNMAP was specified with a size change + if dontunmap && old_size != new_size { + return Err(SyscallError::EINVAL); + }; + + // old_size was 0 and MREMAP_MAYMOVE was not specified + if old_size == 0 && !maymove { + return Err(SyscallError::EINVAL); + }; + + // MREMAP_FIXED is not implemented + if fixed { + unimplemented!(); + } + + let process = current_process(); + let old_start: VirtAddr = old_addr.into(); + if old_size > new_size { + let old_end = old_start + new_size; + process + .memory_set + .lock() + .lock() + .munmap(old_end, old_size - new_size); + flush_tlb(None); + + return Ok(old_start.as_usize() as isize); + } + + // Only deal with MREMAP_MAYMOVE now + let new_addr = process + .memory_set + .lock() + .lock() + .mremap(old_start, old_size, new_size); + flush_tlb(None); + Ok(new_addr) +} +const IPC_PRIVATE: i32 = 0; + +bitflags! { + #[derive(Debug)] + struct ShmFlags: i32 { + const IPC_CREAT = 0o1000; + const IPC_EXCL = 0o2000; + // unimplemented: + const SHM_HUGETLB = 0o4000; + const SHM_NORESERVE = 0o10000; + } +} + +// TODO: uid and gid support +/// # Arguments +/// * `key` - i32 +/// * `size` - usize +/// * `flags` - i32 +pub fn syscall_shmget(args: [usize; 6]) -> SyscallResult { + let key = args[0] as i32; + let size = args[1]; + let flags = args[2] as i32; + + let pid = current_process().pid(); + + // 9 bits for permission + let mode: u16 = (flags as u16) & ((1 << 10) - 1); + + let Some(flags) = ShmFlags::from_bits(flags - mode as i32) else { + // return -1; + return Err(SyscallError::EINVAL); + }; + + if key == IPC_PRIVATE { + let Ok((shmid, mem)) = MemorySet::create_shared_mem(key, size, pid, 0, 0, mode) else { + return Err(SyscallError::EINVAL); + }; + + current_process() + .memory_set + .lock() + .lock() + .add_private_shared_mem(shmid, mem); + + Ok(shmid as isize) + } else { + let mut key_map = axmem::KEY_TO_SHMID.lock(); + + match key_map.get(&key) { + Some(shmid) => { + if flags.contains(ShmFlags::IPC_CREAT) && flags.contains(ShmFlags::IPC_EXCL) { + Err(SyscallError::EEXIST) + } else { + Ok(*shmid as isize) + } + } + None => { + if flags.contains(ShmFlags::IPC_CREAT) { + let Ok((shmid, mem)) = MemorySet::create_shared_mem(key, size, pid, 0, 0, mode) + else { + return Err(SyscallError::EINVAL); + }; + + key_map.insert(key, shmid); + MemorySet::add_shared_mem(shmid, mem); + Ok(shmid as isize) + } else { + Err(SyscallError::ENOENT) + } + } + } + } +} + +bitflags! { + #[derive(Debug)] + struct ShmAtFlags: i32 { + const SHM_RND = 0o20000; + const SHM_EXEC = 0o100000; + const SHM_RDONLY = 0o10000; + const SHM_REMAP = 0o40000; + } +} + +/// # Arguments +/// * `shmid` - i32 +/// * `addr` - usize +/// * `flags` - i32 +pub fn syscall_shmat(args: [usize; 6]) -> SyscallResult { + let shmid = args[0] as i32; + let addr = args[1]; + let flags = args[2] as i32; + let process = current_process(); + + let memory_set_wrapper = process.memory_set.lock(); + let mut memory = memory_set_wrapper.lock(); + + let flags = ShmAtFlags::from_bits(flags).unwrap(); + + let Some(mem) = memory + .get_private_shared_mem(shmid) + .or_else(|| MemorySet::get_shared_mem(shmid)) + else { + return Err(SyscallError::EINVAL); + }; + let size = mem.size(); + + let addr = if addr == 0 { + match memory.find_free_area(addr.into(), size) { + Some(addr) => addr, + None => return Err(SyscallError::ENOMEM), + } + } else { + let addr: VirtAddr = addr.into(); + let addr = if addr.is_aligned_4k() { + addr + } else if flags.contains(ShmAtFlags::SHM_RND) { + addr.align_up_4k() + } else { + return Err(SyscallError::EINVAL); + }; + + if flags.contains(ShmAtFlags::SHM_REMAP) { + memory.split_for_area(addr, size); + flush_tlb(None); + } else { + unimplemented!() + } + + addr + }; + + let mut map_flags = MappingFlags::USER; + if flags.contains(ShmAtFlags::SHM_RDONLY) { + map_flags |= MappingFlags::READ; + } else { + map_flags |= MappingFlags::READ | MappingFlags::WRITE; + } + if flags.contains(ShmAtFlags::SHM_EXEC) { + map_flags |= MappingFlags::EXECUTE; + } + + memory.attach_shared_mem(mem, addr, map_flags); + flush_tlb(None); + + Ok(addr.as_usize() as isize) +} + +/// # mlock +#[cfg(target_arch = "x86_64")] +pub fn syscall_mlock(_args: [usize; 6]) -> SyscallResult { + Ok(0) +} diff --git a/api/linux_syscall_api/src/syscall_mem/mem_syscall_id.rs b/api/linux_syscall_api/src/syscall_mem/mem_syscall_id.rs new file mode 100644 index 0000000..be8c33c --- /dev/null +++ b/api/linux_syscall_api/src/syscall_mem/mem_syscall_id.rs @@ -0,0 +1,49 @@ +//! 记录该模块使用到的系统调用 id +//! +//! +#[cfg(any( + target_arch = "riscv32", + target_arch = "riscv64", + target_arch = "aarch64" +))] +numeric_enum_macro::numeric_enum! { +#[repr(usize)] +#[allow(non_camel_case_types)] +#[allow(missing_docs)] +#[derive(Eq, PartialEq, Debug, Copy, Clone)] +pub enum MemSyscallId { + // mem + SHMGET = 194, + SHMCTL = 195, + SHMAT = 196, + BRK = 214, + MUNMAP = 215, + MREMAP = 216, + MMAP = 222, + MSYNC = 227, + MPROTECT = 226, + MEMBARRIER = 283, +} +} + +#[cfg(target_arch = "x86_64")] +numeric_enum_macro::numeric_enum! { + #[repr(usize)] + #[allow(non_camel_case_types)] + #[allow(missing_docs)] + #[derive(Eq, PartialEq, Debug, Copy, Clone)] + pub enum MemSyscallId { + // mem + MREMAP = 25, + SHMGET = 29, + SHMCTL = 31, + SHMAT = 30, + BRK = 12, + MUNMAP = 11, + MMAP = 9, + MSYNC = 26, + MPROTECT = 10, + MEMBARRIER = 324, + MLOCK = 149, + } +} diff --git a/api/linux_syscall_api/src/syscall_mem/mod.rs b/api/linux_syscall_api/src/syscall_mem/mod.rs new file mode 100644 index 0000000..a39f9e6 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_mem/mod.rs @@ -0,0 +1,32 @@ +//! 与内存相关的系统调用 + +use crate::SyscallResult; + +mod imp; + +mod mem_syscall_id; +pub use mem_syscall_id::MemSyscallId::{self, *}; + +use imp::*; +/// 与内存相关的系统调用 +pub fn mem_syscall(syscall_id: mem_syscall_id::MemSyscallId, args: [usize; 6]) -> SyscallResult { + match syscall_id { + BRK => syscall_brk(args), + MUNMAP => syscall_munmap(args), + MREMAP => syscall_mremap(args), + + MMAP => syscall_mmap(args), + MSYNC => syscall_msync(args), + MPROTECT => syscall_mprotect(args), + MEMBARRIER => Ok(0), + SHMGET => syscall_shmget(args), + SHMCTL => Ok(0), + SHMAT => syscall_shmat(args), + #[cfg(target_arch = "x86_64")] + MLOCK => syscall_mlock(args), + #[allow(unused)] + _ => { + panic!("Invalid Syscall Id: {:?}!", syscall_id); + } + } +} diff --git a/api/linux_syscall_api/src/syscall_net/imp.rs b/api/linux_syscall_api/src/syscall_net/imp.rs new file mode 100644 index 0000000..7d39ad1 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_net/imp.rs @@ -0,0 +1,736 @@ +//! 相关系统调用的具体实现 +extern crate alloc; +use super::socket::*; +use core::slice::{from_raw_parts, from_raw_parts_mut}; + +use alloc::sync::Arc; +use axfs::api::{FileIO, OpenFlags}; + +use crate::{syscall_fs::ctype::pipe::make_pipe, MessageHeader, SyscallError, SyscallResult}; +use axerrno::AxError; +use axlog::{debug, error, info, warn}; + +use axprocess::current_process; +use num_enum::TryFromPrimitive; + +pub const SOCKET_TYPE_MASK: usize = 0xFF; + +/// # Arguments +/// * `domain` - usize +/// * `s_type` - usize +/// * `protocol` - usize +pub fn syscall_socket(args: [usize; 6]) -> SyscallResult { + let domain = args[0]; + let s_type = args[1]; + let _protocol = args[2]; + let Ok(domain) = Domain::try_from(domain) else { + error!("[socket()] Address Family not supported: {domain}"); + // return ErrorNo::EAFNOSUPPORT as isize; + return Err(SyscallError::EAFNOSUPPORT); + }; + let Ok(socket_type) = SocketType::try_from(s_type & SOCKET_TYPE_MASK) else { + // return ErrorNo::EINVAL as isize; + return Err(SyscallError::EINVAL); + }; + let socket = Socket::new(domain, socket_type); + socket.set_nonblocking((s_type & SOCK_NONBLOCK) != 0); + socket.set_close_on_exec((s_type & SOCK_CLOEXEC) != 0); + let curr = current_process(); + let mut fd_table = curr.fd_manager.fd_table.lock(); + let Ok(fd) = curr.alloc_fd(&mut fd_table) else { + return Err(SyscallError::EMFILE); + }; + + fd_table[fd] = Some(Arc::new(socket)); + + debug!("[socket()] create socket {fd}"); + + Ok(fd as isize) +} + +/// # Arguments +/// * `fd` - usize +/// * `addr` - *const u8 +/// * `addr_len` - usize +pub fn syscall_bind(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let addr = args[1] as *const u8; + let _addr_len = args[2]; + let curr = current_process(); + + let file = match curr.fd_manager.fd_table.lock().get(fd) { + Some(Some(file)) => file.clone(), + _ => return Err(SyscallError::EBADF), + }; + + let Some(socket) = file.as_any().downcast_ref::() else { + return Err(SyscallError::ENOTSOCK); + }; + // different action for AF_INET and AF_UNIX + let addr = unsafe { socket_address_from(addr, socket) }; + + info!("[bind()] binding socket {} to {:?}", fd, addr); + + Ok(socket.bind(addr).map_or(-1, |_| 0)) +} + +// TODO: support change `backlog` for tcp socket +/// # Arguments +/// * `fd` - usize +/// * `backlog` - usize +pub fn syscall_listen(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let _backlog = args[1]; + let curr = current_process(); + + let file = match curr.fd_manager.fd_table.lock().get(fd) { + Some(Some(file)) => file.clone(), + _ => return Err(SyscallError::EBADF), + }; + + let Some(socket) = file.as_any().downcast_ref::() else { + return Err(SyscallError::ENOTSOCK); + }; + + Ok(socket.listen().map_or(-1, |_| 0)) +} + +/// # Arguments +/// * `fd` - usize +/// * `addr_buf` - *mut u8 +/// * `addr_len` - *mut u32 +/// * `flags` - usize +pub fn syscall_accept4(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let addr_buf = args[1] as *mut u8; + let addr_len = args[2] as *mut u32; + let flags = args[3]; + let curr = current_process(); + + let file = match curr.fd_manager.fd_table.lock().get(fd) { + Some(Some(file)) => file.clone(), + _ => return Err(SyscallError::EBADF), + }; + + let Some(socket) = file.as_any().downcast_ref::() else { + return Err(SyscallError::ENOTSOCK); + }; + + debug!("[accept()] socket {fd} accept"); + + // socket.accept() might block, we need to release all lock now. + if curr.manual_alloc_type_for_lazy(addr_len).is_err() { + return Err(SyscallError::EFAULT); + } + let buf_len = unsafe { *addr_len } as usize; + if (buf_len as i32) < 0 { + return Err(SyscallError::EINVAL); + } + if curr + .manual_alloc_range_for_lazy(args[1].into(), (args[1] + buf_len).into()) + .is_err() + { + return Err(SyscallError::EFAULT); + } + match socket.accept() { + Ok((s, addr)) => { + let _ = unsafe { socket_address_to(addr, addr_buf, buf_len, addr_len) }; + + let mut fd_table = curr.fd_manager.fd_table.lock(); + let Ok(new_fd) = curr.alloc_fd(&mut fd_table) else { + return Err(SyscallError::ENFILE); // Maybe ENFILE + }; + + debug!("[accept()] socket {fd} accept new socket {new_fd} {addr:?}"); + + // handle flags + + s.set_nonblocking((flags & SOCK_NONBLOCK) != 0); + + s.set_close_on_exec((flags & SOCK_CLOEXEC) != 0); + + fd_table[new_fd] = Some(Arc::new(s)); + Ok(new_fd as isize) + } + Err(AxError::Unsupported) => Err(SyscallError::EOPNOTSUPP), + Err(AxError::Interrupted) => Err(SyscallError::EINTR), + Err(AxError::ConnectionReset) => Err(SyscallError::ECONNABORTED), + Err(AxError::WouldBlock) => Err(SyscallError::EAGAIN), + Err(e) => { + error!("error: {:?}", e); + Err(SyscallError::EPERM) + } + } +} + +/// # Arguments +/// * `fd` - usize +/// * `addr_buf` - *const u8 +/// * `addr_len` - usize +pub fn syscall_connect(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let addr_buf = args[1] as *const u8; + let _addr_len = args[2]; + let curr = current_process(); + + let file = match curr.fd_manager.fd_table.lock().get(fd) { + Some(Some(file)) => file.clone(), + _ => return Err(SyscallError::EBADF), + }; + + let Some(socket) = file.as_any().downcast_ref::() else { + return Err(SyscallError::ENOTSOCK); + }; + + let addr = unsafe { socket_address_from(addr_buf, socket) }; + + info!("[connect()] socket {fd} connecting to {addr:?}"); + + match socket.connect(addr) { + Ok(_) => Ok(0), + Err(AxError::WouldBlock) => Err(SyscallError::EINPROGRESS), + Err(AxError::Interrupted) => Err(SyscallError::EINTR), + Err(AxError::AlreadyExists) => Err(SyscallError::EISCONN), + // TODO:add more error code + Err(_) => Err(SyscallError::ECONNREFUSED), + } +} + +/// NOTE: linux man 中没有说明若socket未bound应返回什么错误 +/// # Arguments +/// * `fd` - usize +/// * `addr` - *mut u8 +/// * `addr_len` - *mut u32 +pub fn syscall_get_sock_name(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let addr = args[1] as *mut u8; + let addr_len = args[2] as *mut u32; + let curr = current_process(); + + let file = match curr.fd_manager.fd_table.lock().get(fd) { + Some(Some(file)) => file.clone(), + _ => return Err(SyscallError::EBADF), + }; + + let Some(socket) = file.as_any().downcast_ref::() else { + return Err(SyscallError::ENOTSOCK); + }; + if curr.manual_alloc_type_for_lazy(addr_len).is_err() { + return Err(SyscallError::EFAULT); + } + let buf_len = unsafe { *addr_len } as usize; + if (buf_len as i32) < 0 { + return Err(SyscallError::EINVAL); + } + if curr + .manual_alloc_range_for_lazy(args[1].into(), (args[1] + buf_len).into()) + .is_err() + { + return Err(SyscallError::EFAULT); + } + + debug!("[getsockname()] socket {fd}"); + + let Ok(name) = socket.name() else { + return Err(SyscallError::EPERM); + }; + + info!("[getsockname()] socket {fd} name: {:?}", name); + Ok(unsafe { socket_address_to(name, addr, buf_len, addr_len) }.map_or(-1, |_| 0)) +} + +#[allow(unused)] +/// # Arguments +/// * `fd` - usize +/// * `addr_buf` - *mut u8 +/// * `addr_len` - *mut u32 +pub fn syscall_getpeername(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let addr_buf = args[1] as *mut u8; + let addr_len = args[2] as *mut u32; + let curr = current_process(); + + let file = match curr.fd_manager.fd_table.lock().get(fd) { + Some(Some(file)) => file.clone(), + _ => return Err(SyscallError::EBADF), + }; + + let buf_len = match curr.manual_alloc_type_for_lazy(addr_len) { + Ok(_) => unsafe { *addr_len as usize }, + Err(_) => return Err(SyscallError::EFAULT), + }; + // It seems it could be negative according to Linux man page. + if (buf_len as i32) < 0 { + return Err(SyscallError::EINVAL); + } + + if curr + .manual_alloc_range_for_lazy( + (addr_buf as usize).into(), + unsafe { addr_buf.add(buf_len as usize) as usize }.into(), + ) + .is_err() + { + return Err(SyscallError::EFAULT); + } + + let Some(socket) = file.as_any().downcast_ref::() else { + return Err(SyscallError::ENOTSOCK); + }; + if curr.manual_alloc_for_lazy(args[2].into()).is_err() { + return Err(SyscallError::EFAULT); + } + + if curr + .manual_alloc_range_for_lazy(args[1].into(), (args[1] + buf_len).into()) + .is_err() + { + return Err(SyscallError::EFAULT); + } + match socket.peer_name() { + Ok(name) => { + Ok(unsafe { socket_address_to(name, addr_buf, buf_len, addr_len) }.map_or(-1, |_| 0)) + } + Err(AxError::NotConnected) => Err(SyscallError::ENOTCONN), + Err(_) => unreachable!(), + } +} + +// TODO: flags +/// Calling sendto() will bind the socket if it's not bound. +/// # Arguments +/// * `fd` - usize +/// * `buf` - *const u8 +/// * `len` - usize +/// * `flags` - usize +/// * `addr` - *const u8 +/// * `addr_len` - usize +pub fn syscall_sendto(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let buf = args[1] as *const u8; + let len = args[2]; + let _flags = args[3]; + let addr = args[4] as *const u8; + let addr_len = args[5]; + let curr = current_process(); + + let file = match curr.fd_manager.fd_table.lock().get(fd) { + Some(Some(file)) => file.clone(), + _ => return Err(SyscallError::EBADF), + }; + + let Some(socket) = file.as_any().downcast_ref::() else { + return Err(SyscallError::ENOTSOCK); + }; + + if buf.is_null() { + return Err(SyscallError::EFAULT); + } + let Ok(buf) = curr + .manual_alloc_range_for_lazy( + (buf as usize).into(), + unsafe { buf.add(len) as usize }.into(), + ) + .map(|_| unsafe { from_raw_parts(buf, len) }) + else { + error!("[sendto()] buf address {buf:?} invalid"); + return Err(SyscallError::EFAULT); + }; + + let addr = if !addr.is_null() && addr_len != 0 { + match curr.manual_alloc_range_for_lazy( + (addr as usize).into(), + unsafe { addr.add(addr_len) as usize }.into(), + ) { + Ok(_) => Some(unsafe { socket_address_from(addr, socket) }), + Err(_) => { + error!("[sendto()] addr address {addr:?} invalid"); + return Err(SyscallError::EFAULT); + } + } + } else { + None + }; + match socket.sendto(buf, addr) { + Ok(len) => { + info!("[sendto()] socket {fd} sent {len} bytes to addr {:?}", addr); + Ok(len as isize) + } + Err(AxError::Interrupted) => Err(SyscallError::EINTR), + Err(AxError::Again) | Err(AxError::WouldBlock) => Err(SyscallError::EAGAIN), + Err(AxError::NotConnected) => Err(SyscallError::ENOTCONN), + Err(AxError::ConnectionReset) => Err(SyscallError::EPIPE), + Err(e) => { + error!("[sendto()] socket {fd} send error: {e:?}"); + Err(SyscallError::EPERM) + } + } +} + +/// # Arguments +/// * `fd` - usize +/// * `buf` - *mut u8 +/// * `len` - usize +/// * `flags` - usize +/// * `addr_buf` - *mut u8 +/// * `addr_len` - *mut u32 +pub fn syscall_recvfrom(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let buf = args[1] as *mut u8; + let len = args[2]; + let _flags = args[3]; + let addr_buf = args[4] as *mut u8; + let addr_len = args[5] as *mut u32; + let curr = current_process(); + + let file = match curr.fd_manager.fd_table.lock().get(fd) { + Some(Some(file)) => file.clone(), + _ => return Err(SyscallError::EBADF), + }; + let Some(socket) = file.as_any().downcast_ref::() else { + return Err(SyscallError::ENOTSOCK); + }; + if !addr_len.is_null() && curr.manual_alloc_type_for_lazy(addr_len).is_err() { + error!("[recvfrom()] addr_len address {addr_len:?} invalid"); + return Err(SyscallError::EFAULT); + } + + if !addr_buf.is_null() && curr.manual_alloc_type_for_lazy(addr_buf).is_err() { + return Err(SyscallError::EFAULT); + } + + if curr + .manual_alloc_range_for_lazy(args[1].into(), (args[1] + len).into()) + .is_err() + { + error!( + "[recvfrom()] addr_buf address {addr_buf:?}, len: {} invalid", + unsafe { *addr_len } + ); + return Err(SyscallError::EFAULT); + } + let buf = unsafe { from_raw_parts_mut(buf, len) }; + info!("recv addr: {:?}", socket.name().unwrap()); + match socket.recv_from(buf) { + Ok((len, addr)) => { + info!("socket {fd} recv {len} bytes from {addr:?}"); + if !addr_buf.is_null() && !addr_len.is_null() { + let buf_len = unsafe { *addr_len } as usize; + if (buf_len as i32) < 0 { + return Err(SyscallError::EINVAL); + } + Ok( + unsafe { socket_address_to(addr, addr_buf, buf_len, addr_len) } + .map_or(-1, |_| len as isize), + ) + } else { + Ok(len as isize) + } + } + Err(AxError::NotConnected) => Err(SyscallError::ENOTCONN), + Err(AxError::ConnectionRefused) => Err(SyscallError::ECONNREFUSED), + Err(AxError::Interrupted) => Err(SyscallError::EINTR), + Err(AxError::Timeout) | Err(AxError::WouldBlock) => Err(SyscallError::EAGAIN), + Err(_) => Err(SyscallError::EPERM), + } +} + +pub fn syscall_sendmsg(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let msg = args[1] as *mut MessageHeader; + let _flags = args[2]; + let curr = current_process(); + let msg = unsafe { &*msg }; + + let file = match curr.fd_manager.fd_table.lock().get(fd) { + Some(Some(file)) => file.clone(), + _ => return Err(SyscallError::EBADF), + }; + + let Some(socket) = file.as_any().downcast_ref::() else { + return Err(SyscallError::ENOTSOCK); + }; + + let msg_header = msg; + let iove = unsafe { &*msg_header.iovec }; + let buf = iove.base; + let len = iove.len; + let Ok(buf) = curr + .manual_alloc_range_for_lazy( + (buf as usize).into(), + unsafe { buf.add(len) as usize }.into(), + ) + .map(|_| unsafe { from_raw_parts(buf, len) }) + else { + error!("[sendto()] buf address {buf:?} invalid"); + return Err(SyscallError::EFAULT); + }; + + let Ok(addr) = socket.peer_name() else { + return Err(SyscallError::EPERM); + }; + + match socket.sendto(buf, Some(addr)) { + Ok(len) => Ok(len as isize), + Err(AxError::Interrupted) => Err(SyscallError::EINTR), + Err(AxError::Again) | Err(AxError::WouldBlock) => Err(SyscallError::EAGAIN), + Err(AxError::NotConnected) => Err(SyscallError::ENOTCONN), + Err(AxError::ConnectionReset) => Err(SyscallError::EPIPE), + Err(e) => { + error!("[sendmsg()] socket {fd} send error: {e:?}"); + Err(SyscallError::EPERM) + } + } +} + +/// NOTE: only support socket level options (SOL_SOCKET) +/// # Arguments +/// * `fd` - usize +/// * `level` - usize +/// * `opt_name` - usize +/// * `opt_value` - *const u8 +/// * `opt_len` - u32 +pub fn syscall_set_sock_opt(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let level = args[1]; + let opt_name = args[2]; + let opt_value = args[3] as *const u8; + let opt_len = args[4] as u32; + let Ok(level) = SocketOptionLevel::try_from(level) else { + error!("[setsockopt()] level {level} not supported"); + unimplemented!(); + }; + + let curr = current_process(); + + let file = match curr.fd_manager.fd_table.lock().get(fd) { + Some(Some(file)) => file.clone(), + _ => return Err(SyscallError::EBADF), + }; + + let Some(socket) = file.as_any().downcast_ref::() else { + return Err(SyscallError::ENOTSOCK); + }; + + let opt = unsafe { from_raw_parts(opt_value, opt_len as usize) }; + + match level { + SocketOptionLevel::IP => { + let Ok(option) = IpOption::try_from(opt_name) else { + warn!("[setsockopt()] option {opt_name} not supported in socket level"); + return Ok(0); + }; + + option.set(socket, opt) + } + SocketOptionLevel::Socket => { + let Ok(option) = SocketOption::try_from(opt_name) else { + warn!("[setsockopt()] option {opt_name} not supported in socket level"); + return Ok(0); + }; + + option.set(socket, opt) + } + SocketOptionLevel::Tcp => { + let Ok(option) = TcpSocketOption::try_from(opt_name) else { + warn!("[setsockopt()] option {opt_name} not supported in tcp level"); + return Ok(0); + }; + + option.set(socket, opt) + } + SocketOptionLevel::IPv6 => { + let Ok(option) = Ipv6Option::try_from(opt_name) else { + warn!("[setsockopt()] option {opt_name} not supported in ipv6 level"); + return Ok(0); + }; + + option.set(socket, opt) + } + } +} + +/// # Arguments +/// * `fd` - usize +/// * `level` - usize +/// * `opt_name` - usize +/// * `opt_value` - *mut u8 +/// * `opt_len` - *mut u32 +pub fn syscall_get_sock_opt(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let level = args[1]; + let opt_name = args[2]; + let opt_value = args[3] as *mut u8; + let opt_len = args[4] as *mut u32; + let Ok(level) = SocketOptionLevel::try_from(level) else { + error!("[setsockopt()] level {level} not supported"); + unimplemented!(); + }; + + if opt_value.is_null() || opt_len.is_null() { + return Err(SyscallError::EFAULT); + } + + let curr = current_process(); + + let file = match curr.fd_manager.fd_table.lock().get(fd) { + Some(Some(file)) => file.clone(), + _ => return Err(SyscallError::EBADF), + }; + + let Some(socket) = file.as_any().downcast_ref::() else { + return Err(SyscallError::ENOTSOCK); + }; + + if curr + .manual_alloc_type_for_lazy(opt_len as *const u32) + .is_err() + { + error!("[getsockopt()] opt_len address {opt_len:?} invalid"); + return Err(SyscallError::EFAULT); + } + if curr + .manual_alloc_range_for_lazy( + (opt_value as usize).into(), + (unsafe { opt_value.add(*opt_len as usize) } as usize).into(), + ) + .is_err() + { + error!( + "[getsockopt()] opt_value {opt_value:?}, len {} invalid", + unsafe { *opt_len } + ); + return Err(SyscallError::EFAULT); + } + + match level { + SocketOptionLevel::IP => {} + SocketOptionLevel::Socket => { + let Ok(option) = SocketOption::try_from(opt_name) else { + panic!("[setsockopt()] option {opt_name} not supported in socket level"); + }; + + option.get(socket, opt_value, opt_len); + } + SocketOptionLevel::Tcp => { + let Ok(option) = TcpSocketOption::try_from(opt_name) else { + panic!("[setsockopt()] option {opt_name} not supported in tcp level"); + }; + + if option == TcpSocketOption::TCP_INFO { + return Err(SyscallError::ENOPROTOOPT); + } + + option.get(socket, opt_value, opt_len); + } + // TODO: achieve the real implementation of ipv6 + SocketOptionLevel::IPv6 => {} + } + + Ok(0) +} + +#[derive(TryFromPrimitive)] +#[repr(usize)] +enum SocketShutdown { + Read = 0, + Write = 1, + ReadWrite = 2, +} + +/// # Arguments +/// * `fd` - usize +/// * `how` - usize +pub fn syscall_shutdown(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let how = args[1]; + let curr = current_process(); + + let file = match curr.fd_manager.fd_table.lock().get(fd) { + Some(Some(file)) => file.clone(), + _ => return Err(SyscallError::EBADF), + }; + + let Some(socket) = file.as_any().downcast_ref::() else { + return Err(SyscallError::ENOTSOCK); + }; + + let Ok(how) = SocketShutdown::try_from(how) else { + return Err(SyscallError::EINVAL); + }; + + match how { + SocketShutdown::Read => { + error!("[shutdown()] SHUT_RD is noop") + } + SocketShutdown::Write => socket.shutdown(), + SocketShutdown::ReadWrite => { + socket.abort(); + } + } + + Ok(0) +} + +pub fn syscall_socketpair(args: [usize; 6]) -> SyscallResult { + let fd: *mut u32 = args[3] as *mut u32; + let s_type = args[1]; + let domain = args[0]; + let process = current_process(); + if process.manual_alloc_for_lazy((fd as usize).into()).is_err() { + return Err(SyscallError::EINVAL); + } + if domain != Domain::AF_UNIX as usize { + panic!(); + } + if SocketType::try_from(s_type & SOCKET_TYPE_MASK).is_err() { + // return ErrorNo::EINVAL as isize; + return Err(SyscallError::EINVAL); + }; + + let (fd1, fd2) = make_socketpair(s_type); + let mut fd_table = process.fd_manager.fd_table.lock(); + let fd_num = if let Ok(fd) = process.alloc_fd(&mut fd_table) { + fd + } else { + return Err(SyscallError::EPERM); + }; + fd_table[fd_num] = Some(fd1); + + let fd_num2 = if let Ok(fd) = process.alloc_fd(&mut fd_table) { + fd + } else { + return Err(SyscallError::EPERM); + }; + axlog::info!("alloc fd1 {} fd2 {} as socketpair", fd_num, fd_num2); + fd_table[fd_num2] = Some(fd2); + + unsafe { + core::ptr::write(fd, fd_num as u32); + core::ptr::write(fd.offset(1), fd_num2 as u32); + } + + Ok(0) +} + +/// return sockerpair read write +pub fn make_socketpair(socket_type: usize) -> (Arc, Arc) { + let s_type = SocketType::try_from(socket_type & SOCKET_TYPE_MASK).unwrap(); + let mut fd1 = Socket::new(Domain::AF_UNIX, s_type); + let mut fd2 = Socket::new(Domain::AF_UNIX, s_type); + let mut pipe_flag = OpenFlags::empty(); + if socket_type & SOCK_NONBLOCK != 0 { + pipe_flag |= OpenFlags::NON_BLOCK; + fd1.set_nonblocking(true); + fd2.set_nonblocking(true); + } + if socket_type & SOCK_CLOEXEC != 0 { + pipe_flag |= OpenFlags::CLOEXEC; + fd1.set_close_on_exec(true); + fd2.set_close_on_exec(true); + } + let (pipe1, pipe2) = make_pipe(pipe_flag); + fd1.buffer = Some(pipe1); + fd2.buffer = Some(pipe2); + (Arc::new(fd1), Arc::new(fd2)) +} diff --git a/api/linux_syscall_api/src/syscall_net/mod.rs b/api/linux_syscall_api/src/syscall_net/mod.rs new file mode 100644 index 0000000..8ee30d0 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_net/mod.rs @@ -0,0 +1,41 @@ +//! 提供与 net work 相关的 syscall + +use crate::SyscallResult; +mod imp; + +#[allow(unused)] +mod socket; +use imp::*; +pub use socket::Socket; +mod net_syscall_id; +pub use net_syscall_id::NetSyscallId::{self, *}; + +/// 进行 syscall 的分发 +pub fn net_syscall(syscall_id: net_syscall_id::NetSyscallId, args: [usize; 6]) -> SyscallResult { + match syscall_id { + SOCKET => syscall_socket(args), + BIND => syscall_bind(args), + LISTEN => syscall_listen(args), + ACCEPT => syscall_accept4(args), + CONNECT => syscall_connect(args), + GETSOCKNAME => syscall_get_sock_name(args), + GETPEERNAME => syscall_getpeername(args), + // GETPEERNAME => 0, + SENDTO => syscall_sendto(args), + RECVFROM => syscall_recvfrom(args), + SENDMSG => syscall_sendmsg(args), + SENDMMSG => Ok(0), + SETSOCKOPT => syscall_set_sock_opt(args), + // SETSOCKOPT => 0, + GETSOCKOPT => syscall_get_sock_opt(args), + SOCKETPAIR => syscall_socketpair(args), + ACCEPT4 => syscall_accept4(args), + SHUTDOWN => syscall_shutdown(args), + #[allow(unused)] + _ => { + panic!("Invalid Syscall Id: {:?}!", syscall_id); + // return -1; + // exit(-1) + } + } +} diff --git a/api/linux_syscall_api/src/syscall_net/net_syscall_id.rs b/api/linux_syscall_api/src/syscall_net/net_syscall_id.rs new file mode 100644 index 0000000..b8f0467 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_net/net_syscall_id.rs @@ -0,0 +1,59 @@ +//! 记录该模块使用到的系统调用 id +//! +//! +#[cfg(any( + target_arch = "riscv32", + target_arch = "riscv64", + target_arch = "aarch64" +))] +numeric_enum_macro::numeric_enum! { +#[repr(usize)] +#[allow(non_camel_case_types)] +#[allow(missing_docs)] +#[derive(Eq, PartialEq, Debug, Copy, Clone)] +pub enum NetSyscallId { + // Socket + SOCKET = 198, + SOCKETPAIR = 199, + BIND = 200, + LISTEN = 201, + ACCEPT = 202, + CONNECT = 203, + GETSOCKNAME = 204, + GETPEERNAME = 205, + SENDTO = 206, + RECVFROM = 207, + SETSOCKOPT = 208, + GETSOCKOPT = 209, + SHUTDOWN = 210, + SENDMSG = 211, + ACCEPT4 = 242, +} +} + +#[cfg(target_arch = "x86_64")] +numeric_enum_macro::numeric_enum! { + #[repr(usize)] + #[allow(non_camel_case_types)] + #[allow(missing_docs)] + #[derive(Eq, PartialEq, Debug, Copy, Clone)] + pub enum NetSyscallId { + // Socket + SOCKET = 41, + BIND = 49, + LISTEN = 50, + ACCEPT = 43, + CONNECT = 42, + GETSOCKNAME = 51, + GETPEERNAME = 52, + SOCKETPAIR = 53, + SENDTO = 44, + RECVFROM = 45, + SENDMSG = 46, + SETSOCKOPT = 54, + GETSOCKOPT = 55, + SHUTDOWN = 48, + ACCEPT4 = 288, + SENDMMSG = 307, + } +} diff --git a/api/linux_syscall_api/src/syscall_net/socket.rs b/api/linux_syscall_api/src/syscall_net/socket.rs new file mode 100644 index 0000000..0c9db38 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_net/socket.rs @@ -0,0 +1,907 @@ +extern crate alloc; +use alloc::{sync::Arc, vec::Vec}; +use core::{ + mem::size_of, + net::Ipv4Addr, + ptr::copy_nonoverlapping, + sync::atomic::{AtomicBool, AtomicU64}, +}; + +use alloc::string::String; +use axerrno::{AxError, AxResult}; +use axfs::api::{FileIO, FileIOType, OpenFlags, Read, Write}; + +use axlog::{error, warn}; +use axnet::{ + add_membership, from_core_sockaddr, into_core_sockaddr, poll_interfaces, IpAddr, SocketAddr, + TcpSocket, UdpSocket, +}; +use axsync::Mutex; +use num_enum::TryFromPrimitive; + +use crate::{ + syscall_fs::ctype::pipe::{make_pipe, Pipe}, + LibcSocketAddr, SyscallError, SyscallResult, TimeVal, +}; + +pub const SOCKET_TYPE_MASK: usize = 0xFF; + +#[derive(TryFromPrimitive, Clone, PartialEq, Eq, Debug)] +#[repr(usize)] +#[allow(non_camel_case_types)] +pub enum Domain { + AF_UNIX = 1, + AF_INET = 2, + //AF_INET6 = 10, + AF_NETLINK = 16, +} + +#[derive(TryFromPrimitive, PartialEq, Eq, Copy, Clone, Debug)] +#[repr(usize)] +#[allow(non_camel_case_types)] +pub enum SocketType { + /// Provides sequenced, reliable, two-way, connection-based byte streams. + /// An out-of-band data transmission mechanism may be supported. + SOCK_STREAM = 1, + /// Supports datagrams (connectionless, unreliable messages of a fixed maximum length). + SOCK_DGRAM = 2, + /// Provides raw network protocol access. + SOCK_RAW = 3, + /// Provides a reliable datagram layer that does not guarantee ordering. + SOCK_RDM = 4, + /// Provides a sequenced, reliable, two-way connection-based data + /// transmission path for datagrams of fixed maximum length; + /// a consumer is required to read an entire packet with each input system call. + SOCK_SEQPACKET = 5, + /// Datagram Congestion Control Protocol socket + SOCK_DCCP = 6, + /// Obsolete and should not be used in new programs. + SOCK_PACKET = 10, +} + +/// Set O_NONBLOCK flag on the open fd +pub const SOCK_NONBLOCK: usize = 0x800; +/// Set FD_CLOEXEC flag on the new fd +pub const SOCK_CLOEXEC: usize = 0x80000; + +#[derive(TryFromPrimitive, Debug)] +#[repr(usize)] +#[allow(non_camel_case_types)] +pub enum SocketOptionLevel { + IP = 0, + Socket = 1, + Tcp = 6, + IPv6 = 41, +} + +#[derive(TryFromPrimitive, Debug)] +#[repr(usize)] +#[allow(non_camel_case_types)] +pub enum IpOption { + IP_MULTICAST_IF = 32, + IP_MULTICAST_TTL = 33, + IP_MULTICAST_LOOP = 34, + IP_ADD_MEMBERSHIP = 35, +} + +#[derive(TryFromPrimitive, Debug)] +#[repr(usize)] +#[allow(non_camel_case_types)] +pub enum SocketOption { + SO_REUSEADDR = 2, + SO_ERROR = 4, + SO_DONTROUTE = 5, + SO_SNDBUF = 7, + SO_RCVBUF = 8, + SO_KEEPALIVE = 9, + SO_RCVTIMEO = 20, + SO_SNDTIMEO = 21, +} + +#[derive(TryFromPrimitive, PartialEq)] +#[repr(usize)] +#[allow(non_camel_case_types)] +pub enum TcpSocketOption { + TCP_NODELAY = 1, // disable nagle algorithm and flush + TCP_MAXSEG = 2, + TCP_INFO = 11, + TCP_CONGESTION = 13, +} + +#[derive(TryFromPrimitive, Debug)] +#[repr(usize)] +#[allow(non_camel_case_types)] +pub enum Ipv6Option { + UNICAST_HOPS = 4, + MULTICAST_IF = 9, + MULTICAST_HOPS = 10, + IPV6_ONLY = 27, + PACKET_INFO = 61, + RECV_TRAFFIC_CLASS = 66, + TRAFFIC_CLASS = 67, +} + +impl IpOption { + pub fn set(&self, socket: &Socket, opt: &[u8]) -> SyscallResult { + match self { + IpOption::IP_MULTICAST_IF => { + // 我们只会使用LOOPBACK作为多播接口 + Ok(0) + } + IpOption::IP_MULTICAST_TTL => match &socket.inner { + SocketInner::Udp(s) => { + let ttl = u8::from_ne_bytes(<[u8; 1]>::try_from(&opt[0..1]).unwrap()); + s.set_socket_ttl(ttl); + Ok(0) + } + _ => panic!("setsockopt IP_MULTICAST_TTL on a non-udp socket"), + }, + IpOption::IP_MULTICAST_LOOP => Ok(0), + IpOption::IP_ADD_MEMBERSHIP => { + let multicast_addr = IpAddr::v4(opt[0], opt[1], opt[2], opt[3]); + let interface_addr = IpAddr::v4(opt[4], opt[5], opt[6], opt[7]); + // TODO add membership error handling + add_membership(multicast_addr, interface_addr); + Ok(0) + } + } + } +} + +impl SocketOption { + pub fn set(&self, socket: &Socket, opt: &[u8]) -> SyscallResult { + match self { + SocketOption::SO_REUSEADDR => { + if opt.len() < 4 { + panic!("can't read a int from socket opt value"); + } + let opt_value = i32::from_ne_bytes(<[u8; 4]>::try_from(&opt[0..4]).unwrap()); + socket.set_reuse_addr(opt_value != 0); + Ok(0) + } + SocketOption::SO_DONTROUTE => { + if opt.len() < 4 { + panic!("can't read a int from socket opt value"); + } + + let opt_value = i32::from_ne_bytes(<[u8; 4]>::try_from(&opt[0..4]).unwrap()); + + socket.set_reuse_addr(opt_value != 0); + // socket.reuse_addr = opt_value != 0; + Ok(0) + } + SocketOption::SO_SNDBUF => { + if opt.len() < 4 { + panic!("can't read a int from socket opt value"); + } + + let opt_value = i32::from_ne_bytes(<[u8; 4]>::try_from(&opt[0..4]).unwrap()); + + socket.set_send_buf_size(opt_value as u64); + // socket.send_buf_size = opt_value as usize; + Ok(0) + } + SocketOption::SO_RCVBUF => { + if opt.len() < 4 { + panic!("can't read a int from socket opt value"); + } + + let opt_value = i32::from_ne_bytes(<[u8; 4]>::try_from(&opt[0..4]).unwrap()); + + socket.set_recv_buf_size(opt_value as u64); + // socket.recv_buf_size = opt_value as usize; + Ok(0) + } + SocketOption::SO_KEEPALIVE => { + if opt.len() < 4 { + panic!("can't read a int from socket opt value"); + } + + let opt_value = i32::from_ne_bytes(<[u8; 4]>::try_from(&opt[0..4]).unwrap()); + + let interval = if opt_value != 0 { + Some(axnet::Duration::from_secs(45)) + } else { + None + }; + + match &socket.inner { + SocketInner::Udp(_) => { + warn!("[setsockopt()] set SO_KEEPALIVE on udp socket, ignored") + } + SocketInner::Tcp(s) => s.with_socket_mut(|s| match s { + Some(s) => s.set_keep_alive(interval), + None => warn!( + "[setsockopt()] set keep-alive for tcp socket not created, ignored" + ), + }), + }; + + socket.set_recv_buf_size(opt_value as u64); + // socket.recv_buf_size = opt_value as usize; + Ok(0) + } + SocketOption::SO_RCVTIMEO => { + if opt.len() < size_of::() { + panic!("can't read a timeval from socket opt value"); + } + + let timeout = unsafe { *(opt.as_ptr() as *const TimeVal) }; + socket.set_recv_timeout(if timeout.sec == 0 && timeout.usec == 0 { + None + } else { + Some(timeout) + }); + Ok(0) + } + SocketOption::SO_ERROR => { + panic!("can't set SO_ERROR"); + } + SocketOption::SO_SNDTIMEO => Err(SyscallError::EPERM), + } + } + + pub fn get(&self, socket: &Socket, opt_value: *mut u8, opt_len: *mut u32) { + let buf_len = unsafe { *opt_len } as usize; + + match self { + SocketOption::SO_REUSEADDR => { + let value: i32 = if socket.get_reuse_addr() { 1 } else { 0 }; + + if buf_len < 4 { + panic!("can't write a int to socket opt value"); + } + + unsafe { + copy_nonoverlapping(&value.to_ne_bytes() as *const u8, opt_value, 4); + *opt_len = 4; + } + } + SocketOption::SO_DONTROUTE => { + if buf_len < 4 { + panic!("can't write a int to socket opt value"); + } + + let size: i32 = if socket.dont_route { 1 } else { 0 }; + + unsafe { + copy_nonoverlapping(&size.to_ne_bytes() as *const u8, opt_value, 4); + *opt_len = 4; + } + } + SocketOption::SO_SNDBUF => { + if buf_len < 4 { + panic!("can't write a int to socket opt value"); + } + + let size: i32 = socket.get_send_buf_size() as i32; + + unsafe { + copy_nonoverlapping(&size.to_ne_bytes() as *const u8, opt_value, 4); + *opt_len = 4; + } + } + SocketOption::SO_RCVBUF => { + if buf_len < 4 { + panic!("can't write a int to socket opt value"); + } + + let size: i32 = socket.get_recv_buf_size() as i32; + + unsafe { + copy_nonoverlapping(&size.to_ne_bytes() as *const u8, opt_value, 4); + *opt_len = 4; + } + } + SocketOption::SO_KEEPALIVE => { + if buf_len < 4 { + panic!("can't write a int to socket opt value"); + } + + let keep_alive: i32 =match &socket.inner { + SocketInner::Udp(_) => { + warn!("[getsockopt()] get SO_KEEPALIVE on udp socket, returning false"); + 0 + } + SocketInner::Tcp(s) => s.with_socket(|s| match s { + Some(s) => if s.keep_alive().is_some() { 1 } else { 0 }, + None => {warn!( + "[setsockopt()] set keep-alive for tcp socket not created, returning false" + ); + 0}, + }), + }; + + unsafe { + copy_nonoverlapping(&keep_alive.to_ne_bytes() as *const u8, opt_value, 4); + *opt_len = 4; + } + } + SocketOption::SO_RCVTIMEO => { + if buf_len < size_of::() { + panic!("can't write a timeval to socket opt value"); + } + + unsafe { + match socket.get_recv_timeout() { + Some(time) => copy_nonoverlapping( + (&time) as *const TimeVal, + opt_value as *mut TimeVal, + 1, + ), + None => { + copy_nonoverlapping(&0u8 as *const u8, opt_value, size_of::()) + } + } + + *opt_len = size_of::() as u32; + } + } + SocketOption::SO_ERROR => { + // 当前没有存储错误列表,因此不做处理 + } + SocketOption::SO_SNDTIMEO => { + panic!("unimplemented!") + } + } + } +} + +impl TcpSocketOption { + pub fn set(&self, raw_socket: &Socket, opt: &[u8]) -> SyscallResult { + let socket = match &raw_socket.inner { + SocketInner::Tcp(s) => s, + _ => panic!("calling tcp option on a wrong type of socket"), + }; + + match self { + TcpSocketOption::TCP_NODELAY => { + if opt.len() < 4 { + panic!("can't read a int from socket opt value"); + } + let opt_value = i32::from_ne_bytes(<[u8; 4]>::try_from(&opt[0..4]).unwrap()); + + let _ = socket.set_nagle_enabled(opt_value == 0); + Ok(0) + } + TcpSocketOption::TCP_INFO => { + // TODO: support the protocal + Ok(0) + } + TcpSocketOption::TCP_CONGESTION => { + raw_socket.set_congestion(String::from_utf8(Vec::from(opt)).unwrap()); + Ok(0) + } + _ => { + unimplemented!() + } + } + } + + pub fn get(&self, raw_socket: &Socket, opt_value: *mut u8, opt_len: *mut u32) { + let socket = match &raw_socket.inner { + SocketInner::Tcp(ref s) => s, + _ => panic!("calling tcp option on a wrong type of socket"), + }; + + let buf_len = unsafe { *opt_len }; + + match self { + TcpSocketOption::TCP_NODELAY => { + if buf_len < 4 { + panic!("can't write a int to socket opt value"); + } + + let value: i32 = if socket.nagle_enabled() { 0 } else { 1 }; + + let value = value.to_ne_bytes(); + + unsafe { + copy_nonoverlapping(&value as *const u8, opt_value, 4); + *opt_len = 4; + } + } + TcpSocketOption::TCP_MAXSEG => { + let len = size_of::(); + + let value: usize = 1500; + + unsafe { + copy_nonoverlapping(&value as *const usize as *const u8, opt_value, len); + *opt_len = len as u32; + }; + } + TcpSocketOption::TCP_INFO => {} + TcpSocketOption::TCP_CONGESTION => { + let bytes = raw_socket.get_congestion(); + let bytes = bytes.as_bytes(); + + unsafe { + copy_nonoverlapping(bytes.as_ptr(), opt_value, bytes.len()); + *opt_len = bytes.len() as u32; + }; + } + } + } +} + +// TODO: achieve the real implementation of Ipv6Option +impl Ipv6Option { + pub fn set(&self, socket: &Socket, opt: &[u8]) -> SyscallResult { + Ok(0) + } +} + +/// 包装内部的不同协议 Socket +/// 类似 FileDesc,impl FileIO 后加入fd_list +#[allow(dead_code)] +pub struct Socket { + domain: Domain, + socket_type: SocketType, + + /// Type of the socket protocol used + pub inner: SocketInner, + /// Whether the socket is set to close on exec + pub close_exec: AtomicBool, + recv_timeout: Mutex>, + + /// FIXME: temp for socket pair + pub buffer: Option>, + + // fake options + dont_route: bool, + send_buf_size: AtomicU64, + recv_buf_size: AtomicU64, + congestion: Mutex, +} + +/// The transport protocol used by the socket +pub enum SocketInner { + /// TCP socket + Tcp(TcpSocket), + /// UDP socket + Udp(UdpSocket), +} + +impl Socket { + fn get_recv_timeout(&self) -> Option { + *self.recv_timeout.lock() + } + fn get_reuse_addr(&self) -> bool { + match &self.inner { + SocketInner::Tcp(s) => s.is_reuse_addr(), + SocketInner::Udp(s) => s.is_reuse_addr(), + } + } + + fn get_send_buf_size(&self) -> u64 { + self.send_buf_size + .load(core::sync::atomic::Ordering::Acquire) + } + + fn get_recv_buf_size(&self) -> u64 { + self.recv_buf_size + .load(core::sync::atomic::Ordering::Acquire) + } + + fn get_congestion(&self) -> String { + self.congestion.lock().clone() + } + + fn set_recv_timeout(&self, val: Option) { + *self.recv_timeout.lock() = val; + } + + fn set_reuse_addr(&self, flag: bool) { + match &self.inner { + SocketInner::Tcp(s) => s.set_reuse_addr(flag), + SocketInner::Udp(s) => s.set_reuse_addr(flag), + } + } + + fn set_send_buf_size(&self, size: u64) { + self.send_buf_size + .store(size, core::sync::atomic::Ordering::Release) + } + + fn set_recv_buf_size(&self, size: u64) { + self.recv_buf_size + .store(size, core::sync::atomic::Ordering::Release) + } + + fn set_congestion(&self, congestion: String) { + *self.congestion.lock() = congestion; + } + + /// Create a new socket with the given domain and socket type. + pub fn new(domain: Domain, socket_type: SocketType) -> Self { + let inner = match socket_type { + //fake SOCK_RAW, should not be here + SocketType::SOCK_STREAM | SocketType::SOCK_SEQPACKET | SocketType::SOCK_RAW => { + SocketInner::Tcp(TcpSocket::new()) + } + SocketType::SOCK_DGRAM => SocketInner::Udp(UdpSocket::new()), + _ => { + error!("unimplemented SocketType: {:?}", socket_type); + unimplemented!(); + } + }; + Self { + domain, + socket_type, + inner, + close_exec: AtomicBool::new(false), + buffer: None, + recv_timeout: Mutex::new(None), + dont_route: false, + send_buf_size: AtomicU64::new(64 * 1024), + recv_buf_size: AtomicU64::new(64 * 1024), + congestion: Mutex::new(String::from("reno")), + } + } + + /// set the socket to non-blocking mode + pub fn set_nonblocking(&self, nonblocking: bool) { + match &self.inner { + SocketInner::Tcp(s) => s.set_nonblocking(nonblocking), + SocketInner::Udp(s) => s.set_nonblocking(nonblocking), + } + } + + /// Return the non-blocking flag of the socket + pub fn is_nonblocking(&self) -> bool { + match &self.inner { + SocketInner::Tcp(s) => s.is_nonblocking(), + SocketInner::Udp(s) => s.is_nonblocking(), + } + } + + /// Socket may send or recv. + pub fn is_connected(&self) -> bool { + match &self.inner { + SocketInner::Tcp(s) => s.is_connected(), + SocketInner::Udp(s) => s.with_socket(|s| s.is_open()), + } + } + + /// Return bound address. + pub fn name(&self) -> AxResult { + match &self.inner { + SocketInner::Tcp(s) => s.local_addr(), + SocketInner::Udp(s) => s.local_addr(), + } + .map(from_core_sockaddr) + } + + /// Return peer address. + pub fn peer_name(&self) -> AxResult { + match &self.inner { + SocketInner::Tcp(s) => s.peer_addr(), + SocketInner::Udp(s) => s.peer_addr(), + } + .map(from_core_sockaddr) + } + + /// Bind the socket to the given address. + pub fn bind(&self, addr: SocketAddr) -> AxResult { + match &self.inner { + SocketInner::Tcp(s) => s.bind(into_core_sockaddr(addr)), + SocketInner::Udp(s) => s.bind(into_core_sockaddr(addr)), + } + } + + /// Listen to the bound address. + /// + /// Only support socket with type SOCK_STREAM or SOCK_SEQPACKET + /// + /// Err(Unsupported): EOPNOTSUPP + pub fn listen(&self) -> AxResult { + if self.socket_type != SocketType::SOCK_STREAM + && self.socket_type != SocketType::SOCK_SEQPACKET + { + return Err(AxError::Unsupported); + } + match &self.inner { + SocketInner::Tcp(s) => s.listen(), + SocketInner::Udp(_) => Err(AxError::Unsupported), + } + } + + /// Accept a new connection. + pub fn accept(&self) -> AxResult<(Self, SocketAddr)> { + if self.socket_type != SocketType::SOCK_STREAM + && self.socket_type != SocketType::SOCK_SEQPACKET + { + return Err(AxError::Unsupported); + } + + let new_socket = match &self.inner { + SocketInner::Tcp(s) => s.accept()?, + SocketInner::Udp(_) => Err(AxError::Unsupported)?, + }; + let addr = new_socket.peer_addr()?; + + Ok(( + Self { + domain: self.domain.clone(), + socket_type: self.socket_type, + inner: SocketInner::Tcp(new_socket), + close_exec: AtomicBool::new(false), + buffer: None, + recv_timeout: Mutex::new(None), + dont_route: false, + send_buf_size: AtomicU64::new(64 * 1024), + recv_buf_size: AtomicU64::new(64 * 1024), + congestion: Mutex::new(String::from("reno")), + }, + from_core_sockaddr(addr), + )) + } + + /// Connect to the given address. + pub fn connect(&self, addr: SocketAddr) -> AxResult { + match &self.inner { + SocketInner::Tcp(s) => s.connect(into_core_sockaddr(addr)), + SocketInner::Udp(s) => s.connect(into_core_sockaddr(addr)), + } + } + + #[allow(unused)] + /// whether the socket is bound with a local address + pub fn is_bound(&self) -> bool { + match &self.inner { + SocketInner::Tcp(s) => s.local_addr().is_ok(), + SocketInner::Udp(s) => s.local_addr().is_ok(), + } + } + #[allow(unused)] + /// let the socket send data to the given address + pub fn sendto(&self, buf: &[u8], addr: Option) -> AxResult { + // if self.domain == Domain::AF_UNIX { + // return self.buffer.as_ref().unwrap().write(buf); + // } + match &self.inner { + SocketInner::Udp(s) => { + // udp socket not bound + if s.local_addr().is_err() { + s.bind(into_core_sockaddr(SocketAddr::new( + IpAddr::v4(0, 0, 0, 0), + 0, + ))) + .unwrap(); + } + match addr { + Some(addr) => s.send_to(buf, into_core_sockaddr(addr)), + None => { + // not connected and no target is given + if s.peer_addr().is_err() { + // return Err(SyscallError::ENOTCONN); + return Err(AxError::NotConnected); + } + s.send(buf) + } + } + } + SocketInner::Tcp(s) => { + if s.is_closed() { + // The local socket has been closed. + return Err(AxError::ConnectionReset); + } + // if !s.is_connected() { + // return Err(SyscallError::ENOTCONN); + // } + s.send(buf) + } + } + } + + /// let the socket receive data and write it to the given buffer + pub fn recv_from(&self, buf: &mut [u8]) -> AxResult<(usize, SocketAddr)> { + if self.domain == Domain::AF_UNIX { + let ans = self.buffer.as_ref().unwrap().read(buf)?; + return Ok(( + ans, + SocketAddr::new(IpAddr::Ipv4(axnet::Ipv4Addr::new(127, 0, 0, 1)), 0), + )); + } + match &self.inner { + SocketInner::Tcp(s) => { + let addr = s.peer_addr()?; + + match self.get_recv_timeout() { + Some(time) => s.recv_timeout(buf, time.turn_to_ticks()), + None => s.recv(buf), + } + .map(|len| (len, from_core_sockaddr(addr))) + } + SocketInner::Udp(s) => match self.get_recv_timeout() { + Some(time) => s + .recv_from_timeout(buf, time.turn_to_ticks()) + .map(|(val, addr)| (val, from_core_sockaddr(addr))), + None => s + .recv_from(buf) + .map(|(val, addr)| (val, from_core_sockaddr(addr))), + }, + } + } + + /// For shutdown(fd, SHUT_WR) + pub fn shutdown(&self) { + match &self.inner { + SocketInner::Udp(s) => { + s.shutdown(); + } + SocketInner::Tcp(s) => { + s.close(); + } + }; + } + + /// For shutdown(fd, SHUT_RDWR) + pub fn abort(&self) { + match &self.inner { + SocketInner::Udp(s) => { + let _ = s.shutdown(); + } + SocketInner::Tcp(s) => s.with_socket_mut(|s| { + if let Some(s) = s { + s.abort(); + } + }), + } + } +} + +impl FileIO for Socket { + fn read(&self, buf: &mut [u8]) -> AxResult { + if self.domain == Domain::AF_UNIX { + return self.buffer.as_ref().unwrap().read(buf); + } + match &self.inner { + SocketInner::Tcp(s) => s.recv(buf), + SocketInner::Udp(s) => s.recv(buf), + } + } + + fn write(&self, buf: &[u8]) -> AxResult { + if self.domain == Domain::AF_UNIX { + return self.buffer.as_ref().unwrap().write(buf); + } + match &self.inner { + SocketInner::Tcp(s) => s.send(buf), + SocketInner::Udp(s) => s.send(buf), + } + } + + fn flush(&self) -> AxResult { + Err(AxError::Unsupported) + } + + fn readable(&self) -> bool { + if self.domain == Domain::AF_UNIX { + return self.buffer.as_ref().unwrap().readable(); + } + poll_interfaces(); + match &self.inner { + SocketInner::Tcp(s) => s.poll().map_or(false, |p| p.readable), + SocketInner::Udp(s) => s.poll().map_or(false, |p| p.readable), + } + } + + fn writable(&self) -> bool { + // if self.domain == Domain::AF_UNIX { + // return self.buffer.as_ref().unwrap().writable(); + // } + poll_interfaces(); + match &self.inner { + SocketInner::Tcp(s) => s.poll().map_or(false, |p| p.writable), + SocketInner::Udp(s) => s.poll().map_or(false, |p| p.writable), + } + } + + fn executable(&self) -> bool { + false + } + + fn get_type(&self) -> FileIOType { + FileIOType::Socket + } + + fn get_status(&self) -> OpenFlags { + let mut flags = OpenFlags::default(); + + if self.close_exec.load(core::sync::atomic::Ordering::Acquire) { + flags |= OpenFlags::CLOEXEC; + } + + if self.is_nonblocking() { + flags |= OpenFlags::NON_BLOCK; + } + + flags + } + + fn set_status(&self, flags: OpenFlags) -> bool { + self.set_nonblocking(flags.contains(OpenFlags::NON_BLOCK)); + + true + } + + fn ready_to_read(&self) -> bool { + self.readable() + } + + fn ready_to_write(&self) -> bool { + self.writable() + } + + fn set_close_on_exec(&self, _is_set: bool) -> bool { + self.close_exec + .store(_is_set, core::sync::atomic::Ordering::Release); + true + } +} + +/// Turn a socket address buffer into a SocketAddr +/// +/// Only support INET (ipv4) +pub unsafe fn socket_address_from(addr: *const u8, socket: &Socket) -> SocketAddr { + let addr = addr as *const u16; + match socket.domain { + // fake AF_UNIX and AF_NETLINK, use AF_INET temporarily + Domain::AF_INET | Domain::AF_UNIX | Domain::AF_NETLINK => { + let port = u16::from_be(*addr.add(1)); + let a = (*(addr.add(2) as *const u32)).to_le_bytes(); + + let addr = IpAddr::v4(a[0], a[1], a[2], a[3]); + SocketAddr { addr, port } + } // TODO: support ipv6 + //Domain::AF_INET6 => todo!() + } +} +/// Only support INET (ipv4) +/// +/// ipv4 socket address buffer: +/// socket_domain (address_family) u16 +/// port u16 (big endian) +/// addr u32 (big endian) +/// +/// TODO: Returns error if buf or buf_len is in invalid memory +pub unsafe fn socket_address_to( + addr: SocketAddr, + buf: *mut u8, + mut buf_len: usize, + buf_len_addr: *mut u32, +) -> AxResult { + *buf_len_addr = core::mem::size_of::() as u32; + // 写入 AF_INET + if buf_len == 0 { + return Ok(()); + } + let domain = (Domain::AF_INET as u16).to_ne_bytes(); + let write_len = buf_len.min(2); + copy_nonoverlapping(domain.as_ptr(), buf, write_len); + let buf = buf.add(write_len); + buf_len -= write_len; + + // 写入 port + if buf_len == 0 { + return Ok(()); + } + let port = &addr.port.to_be_bytes(); + let write_len = buf_len.min(2); + copy_nonoverlapping(port.as_ptr(), buf, write_len); + let buf = buf.add(write_len); + buf_len -= write_len; + + // 写入 address, only support ipv4 + axlog::warn!("Only support ipv4"); + if buf_len == 0 { + return Ok(()); + } + let address = &addr.addr.as_bytes(); + let write_len = buf_len.min(4); + copy_nonoverlapping(address.as_ptr(), buf, write_len); + + Ok(()) +} diff --git a/api/linux_syscall_api/src/syscall_task/imp/futex.rs b/api/linux_syscall_api/src/syscall_task/imp/futex.rs new file mode 100644 index 0000000..89e8710 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_task/imp/futex.rs @@ -0,0 +1,145 @@ +//! 支持 futex 相关的 syscall + +extern crate alloc; + +use core::time::Duration; + +use axlog::{debug, error}; +use axprocess::{current_process, current_task, futex::FutexRobustList}; + +use crate::{RobustList, SyscallError, SyscallResult, TimeSecs}; + +use axfutex::flags::*; +use axprocess::futex::{futex_requeue, futex_wait, futex_wake, futex_wake_bitset}; + +pub fn syscall_futex(args: [usize; 6]) -> SyscallResult { + let uaddr = args[0]; + let futex_op = args[1] as i32; + let val = args[2] as u32; + /* arg[3] is time_out_val or val2 depends on futex_op */ + let val2 = args[3]; + let uaddr2 = args[4]; + let mut val3 = args[5] as u32; + + let process = current_process(); + // convert `TimeSecs` struct to `timeout` nanoseconds + let timeout = if val2 != 0 && process.manual_alloc_for_lazy(val2.into()).is_ok() { + let time_sepc: TimeSecs = unsafe { *(val2 as *const TimeSecs) }; + time_sepc.turn_to_nanos() + } else { + // usize::MAX + 0 + }; + let mut flags = FutexFlags::from_bits(futex_op).ok_or(SyscallError::EINVAL)?; + + // TODO: shared futex and real time clock + // It's Ok for ananonymous mmap to use private futex + if flags.contains(FutexFlags::PRIVATE) { + if flags.contains(FutexFlags::SHARED) { + return Err(SyscallError::EINVAL); + } + } else { + flags |= FutexFlags::SHARED; + debug!( + "shared futex is not supported, but it's ok for anonymous mmap to use private futex" + ); + } + + match FutexOp::try_from(futex_op) { + Ok(FutexOp::WAIT) => { + val3 = FutexFlags::BITSET_MATCH_ANY.bits() as u32; + // convert relative timeout to absolute timeout + let deadline: Option = if timeout != 0 { + Some(Duration::from_nanos(timeout as u64) + axhal::time::current_time()) + } else { + None + }; + futex_wait(uaddr.into(), flags, val, deadline, val3) + } + Ok(FutexOp::WAIT_BITSET) => { + let deadline: Option = if timeout != 0 { + Some(Duration::from_nanos(timeout as u64)) + } else { + None + }; + futex_wait(uaddr.into(), flags, val, deadline, val3) + } + Ok(FutexOp::WAKE) => futex_wake(uaddr.into(), flags, val), + Ok(FutexOp::WAKE_BITSET) => futex_wake_bitset(uaddr.into(), flags, val, val3), + Ok(FutexOp::REQUEUE) => futex_requeue(uaddr.into(), flags, val, uaddr2.into(), val2 as u32), + Ok(FutexOp::CMP_REQUEUE) => { + error!("[linux_syscall_api] futex: unsupported futex operation: FUTEX_CMP_REQUEUE"); + Err(SyscallError::ENOSYS) + } + Ok(FutexOp::WAKE_OP) => { + // futex_wake(uaddr, flags, uaddr2, val, val2, val3) + error!("[linux_syscall_api] futex: unsupported futex operation: FUTEX_WAKE_OP"); + Err(SyscallError::ENOSYS) + } + // TODO: priority-inheritance futex + _ => { + error!( + "[linux_syscall_api] futex: unsupported futex operation: {}", + futex_op + ); + Err(SyscallError::ENOSYS) + } + } + // success anyway and reach here +} + +/// 内核只发挥存储的作用 +/// 但要保证head对应的地址已经被分配 +/// # Arguments +/// * head: usize +/// * len: usize +pub fn syscall_set_robust_list(args: [usize; 6]) -> SyscallResult { + let head = args[0]; + let len = args[1]; + let process = current_process(); + if len != core::mem::size_of::() { + return Err(SyscallError::EINVAL); + } + let curr_id = current_task().id().as_u64(); + if process.manual_alloc_for_lazy(head.into()).is_ok() { + let mut robust_list = process.robust_list.lock(); + robust_list.insert(curr_id, FutexRobustList::new(head, len)); + Ok(0) + } else { + Err(SyscallError::EINVAL) + } +} + +/// 取出对应线程的robust list +/// # Arguments +/// * pid: i32 +/// * head: *mut usize +/// * len: *mut usize +pub fn syscall_get_robust_list(args: [usize; 6]) -> SyscallResult { + let pid = args[0] as i32; + let head = args[1] as *mut usize; + let len = args[2] as *mut usize; + + if pid == 0 { + let process = current_process(); + let curr_id = current_task().id().as_u64(); + if process + .manual_alloc_for_lazy((head as usize).into()) + .is_ok() + { + let robust_list = process.robust_list.lock(); + if robust_list.contains_key(&curr_id) { + let list = robust_list.get(&curr_id).unwrap(); + unsafe { + *head = list.head; + *len = list.len; + } + } else { + return Err(SyscallError::EPERM); + } + return Ok(0); + } + return Err(SyscallError::EPERM); + } + Err(SyscallError::EPERM) +} diff --git a/api/linux_syscall_api/src/syscall_task/imp/mod.rs b/api/linux_syscall_api/src/syscall_task/imp/mod.rs new file mode 100644 index 0000000..6466b71 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_task/imp/mod.rs @@ -0,0 +1,19 @@ +mod signal; + +mod futex; + +mod schedule; + +mod task; + +mod utils; + +pub use signal::*; + +pub use futex::*; + +pub use schedule::*; + +pub use task::*; + +pub use utils::*; diff --git a/api/linux_syscall_api/src/syscall_task/imp/schedule.rs b/api/linux_syscall_api/src/syscall_task/imp/schedule.rs new file mode 100644 index 0000000..5b5be87 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_task/imp/schedule.rs @@ -0,0 +1,247 @@ +//! 支持与任务调度相关的 syscall +extern crate alloc; +use alloc::sync::Arc; +use axconfig::SMP; +use axhal::mem::VirtAddr; +use axprocess::{current_process, current_task, PID2PC, TID2TASK}; +use axtask::{SchedPolicy, SchedStatus, MAX_RT_PRIO}; + +use crate::{SchedParam, SyscallError, SyscallResult}; +/// 获取对应任务的CPU适配集 +/// +/// 若pid是进程ID,则获取对应的进程的主线程的信息 +/// +/// 若pid是线程ID,则获取对应线程信息 +/// +/// 若pid为0,则获取当前运行任务的信息 +/// +/// mask为即将写入的cpu set的地址指针 +/// # Arguments +/// * `pid` - usize +/// * `cpu_set_size` - usize +/// * `mask` - *mut usize +pub fn syscall_sched_getaffinity(args: [usize; 6]) -> SyscallResult { + let pid = args[0]; + let cpu_set_size = args[1]; + let mask = args[2] as *mut usize; + // let task: LazyInit = LazyInit::new(); + let tid2task = TID2TASK.lock(); + let pid2task = PID2PC.lock(); + let pid = pid as u64; + let task = if tid2task.contains_key(&pid) { + Arc::clone(tid2task.get(&pid).unwrap()) + } else if pid2task.contains_key(&pid) { + let process = pid2task.get(&pid).unwrap(); + + process + .tasks + .lock() + .iter() + .find(|task| task.is_leader()) + .cloned() + .unwrap() + } else if pid == 0 { + Arc::clone(current_task().as_task_ref()) + } else { + // 找不到对应任务 + return Err(SyscallError::ESRCH); + }; + + drop(pid2task); + drop(tid2task); + + let process = current_process(); + if process + .manual_alloc_for_lazy(VirtAddr::from(mask as usize)) + .is_err() + { + return Err(SyscallError::EFAULT); + } + let cpu_set = task.get_cpu_set(); + let mut prev_mask = unsafe { *mask }; + let len = SMP.min(cpu_set_size * 4); + prev_mask &= !((1 << len) - 1); + prev_mask &= cpu_set & ((1 << len) - 1); + unsafe { + *mask = prev_mask; + } + // 返回成功填充的缓冲区的长度 + Ok(SMP as isize) +} + +/// # Arguments +/// * `pid` - usize +/// * `cpu_set_size` - usize +/// * `mask` - *const usize +#[allow(unused)] +pub fn syscall_sched_setaffinity(args: [usize; 6]) -> SyscallResult { + let pid = args[0]; + let cpu_set_size = args[1]; + let mask = args[2] as *const usize; + let tid2task = TID2TASK.lock(); + let pid2task = PID2PC.lock(); + let pid = pid as u64; + let task = if tid2task.contains_key(&pid) { + Arc::clone(tid2task.get(&pid).unwrap()) + } else if pid2task.contains_key(&pid) { + let process = pid2task.get(&pid).unwrap(); + + process + .tasks + .lock() + .iter() + .find(|task| task.is_leader()) + .cloned() + .unwrap() + } else if pid == 0 { + Arc::clone(current_task().as_task_ref()) + } else { + // 找不到对应任务 + return Err(SyscallError::ESRCH); + }; + + drop(pid2task); + drop(tid2task); + + let process = current_process(); + if process + .manual_alloc_for_lazy(VirtAddr::from(mask as usize)) + .is_err() + { + return Err(SyscallError::EFAULT); + } + + let mask = unsafe { *mask }; + + task.set_cpu_set(mask, cpu_set_size, axconfig::SMP); + + Ok(0) +} + +/// # Arguments +/// * `pid` - usize +/// * `policy` - usize +/// * `param` - *const SchedParam +pub fn syscall_sched_setscheduler(args: [usize; 6]) -> SyscallResult { + let pid = args[0]; + let policy = args[1]; + let param = args[2] as *const SchedParam; + if (pid as isize) < 0 || param.is_null() { + return Err(SyscallError::EINVAL); + } + + let tid2task = TID2TASK.lock(); + let pid2task = PID2PC.lock(); + let pid = pid as u64; + let task = if tid2task.contains_key(&pid) { + Arc::clone(tid2task.get(&pid).unwrap()) + } else if pid2task.contains_key(&pid) { + let process = pid2task.get(&pid).unwrap(); + + process + .tasks + .lock() + .iter() + .find(|task| task.is_leader()) + .cloned() + .unwrap() + } else if pid == 0 { + Arc::clone(current_task().as_task_ref()) + } else { + // 找不到对应任务 + return Err(SyscallError::ESRCH); + }; + + drop(pid2task); + drop(tid2task); + + let process = current_process(); + if process + .manual_alloc_for_lazy(VirtAddr::from(param as usize)) + .is_err() + { + return Err(SyscallError::EFAULT); + } + + let param = unsafe { *param }; + let policy = SchedPolicy::from(policy); + if policy == SchedPolicy::SCHED_UNKNOWN { + return Err(SyscallError::EINVAL); + } + if policy == SchedPolicy::SCHED_OTHER + || policy == SchedPolicy::SCHED_BATCH + || policy == SchedPolicy::SCHED_IDLE + { + if param.sched_priority != 0 { + return Err(SyscallError::EINVAL); + } + } else if param.sched_priority < 1 || param.sched_priority > 99 { + return Err(SyscallError::EINVAL); + } + + task.set_sched_status(SchedStatus { + policy, + priority: param.sched_priority, + }); + + Ok(0) +} + +/// # Arguments +/// * `pid` - usize +pub fn syscall_sched_getscheduler(args: [usize; 6]) -> SyscallResult { + let pid = args[0]; + if (pid as isize) < 0 { + return Err(SyscallError::EINVAL); + } + + let tid2task = TID2TASK.lock(); + let pid2task = PID2PC.lock(); + let pid = pid as u64; + let task = if tid2task.contains_key(&pid) { + Arc::clone(tid2task.get(&pid).unwrap()) + } else if pid2task.contains_key(&pid) { + let process = pid2task.get(&pid).unwrap(); + + process + .tasks + .lock() + .iter() + .find(|task| task.is_leader()) + .map(Arc::clone) + .unwrap() + } else if pid == 0 { + Arc::clone(current_task().as_task_ref()) + } else { + // 找不到对应任务 + return Err(SyscallError::ESRCH); + }; + + drop(pid2task); + drop(tid2task); + + let policy: isize = task.get_sched_status().policy.into(); + Ok(policy) +} + +/// # Arguments +/// * `policy` - usize +pub fn syscall_sched_getscheduler_max(args: [usize; 6]) -> SyscallResult { + let policy: usize = args[0]; + match SchedPolicy::from(policy) { + SchedPolicy::SCHED_FIFO | SchedPolicy::SCHED_RR => Ok(MAX_RT_PRIO as isize), + SchedPolicy::SCHED_OTHER | SchedPolicy::SCHED_DEADLINE | SchedPolicy::SCHED_BATCH | SchedPolicy::SCHED_IDLE => Ok(0), + _ => Err(SyscallError::EINVAL), + } +} + +/// # Arguments +/// * `policy` - usize +pub fn syscall_sched_getscheduler_min(args: [usize; 6]) -> SyscallResult { + let policy: usize = args[0]; + match SchedPolicy::from(policy) { + SchedPolicy::SCHED_FIFO | SchedPolicy::SCHED_RR => Ok(1), + SchedPolicy::SCHED_OTHER | SchedPolicy::SCHED_DEADLINE | SchedPolicy::SCHED_BATCH | SchedPolicy::SCHED_IDLE => Ok(0), + _ => Err(SyscallError::EINVAL), + } +} \ No newline at end of file diff --git a/api/linux_syscall_api/src/syscall_task/imp/signal.rs b/api/linux_syscall_api/src/syscall_task/imp/signal.rs new file mode 100644 index 0000000..8495ff1 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_task/imp/signal.rs @@ -0,0 +1,262 @@ +//! 支持信号相关的 syscall +//! 与信号处理相关的系统调用 + +use axhal::cpu::this_cpu_id; +use axlog::{debug, info}; +use axprocess::{current_process, current_task, yield_now_task}; +use axsignal::signal_no::SignalNo; +use axsignal::{action::SigAction, ucontext::SignalStack}; + +use crate::{SigMaskFlag, SyscallError, SyscallResult, SIGSET_SIZE_IN_BYTE}; + +/// # Arguments +/// * `signum` - usize +/// * `action` - *const SigAction +/// * `old_action` - *mut SigAction +pub fn syscall_sigaction(args: [usize; 6]) -> SyscallResult { + let signum = args[0]; + let action = args[1] as *const SigAction; + let old_action = args[2] as *mut SigAction; + info!( + "signum: {}, action: {:X}, old_action: {:X}", + signum, action as usize, old_action as usize + ); + if signum == SignalNo::SIGKILL as usize || signum == SignalNo::SIGSTOP as usize { + // 特殊参数不能被覆盖 + return Err(SyscallError::EPERM); + } + + let current_process = current_process(); + let mut signal_modules = current_process.signal_modules.lock(); + let signal_module = signal_modules + .get_mut(¤t_task().id().as_u64()) + .unwrap(); + let mut signal_handler = signal_module.signal_handler.lock(); + let old_address = old_action as usize; + + if old_address != 0 { + // old_address非零说明要求写入到这个地址 + // 此时要检查old_address是否在某一个段中 + if current_process + .manual_alloc_for_lazy(old_address.into()) + .is_err() + { + // 无法分配 + return Err(SyscallError::EPERM); + } + + // 将原有的action存储到old_address + unsafe { + *old_action = *signal_handler.get_action(signum); + } + } + + let new_address = action as usize; + if new_address != 0 { + if current_process + .manual_alloc_for_lazy(new_address.into()) + .is_err() + { + // 无法分配 + return Err(SyscallError::EPERM); + } + unsafe { signal_handler.set_action(signum, action) }; + } + Ok(0) +} + +/// 实现sigsuspend系统调用 +/// TODO: 这里实现的似乎和文档有出入,应该有 BUG +/// # Arguments +/// * `mask` - *const usize +pub fn syscall_sigsuspend(args: [usize; 6]) -> SyscallResult { + let mask = args[0] as *const usize; + let process = current_process(); + if process + .manual_alloc_for_lazy((mask as usize).into()) + .is_err() + { + return Err(SyscallError::EFAULT); + } + let mut signal_modules = process.signal_modules.lock(); + + let signal_module = signal_modules + .get_mut(¤t_task().id().as_u64()) + .unwrap(); + // 设置新的掩码 + if signal_module.last_trap_frame_for_signal.is_some() { + // 信号嵌套的情况下触发这个调用 + return Err(SyscallError::EINTR); + } + signal_module.signal_set.mask = unsafe { *mask }; + drop(signal_modules); + loop { + let mut signal_modules = process.signal_modules.lock(); + let signal_module = signal_modules + .get_mut(¤t_task().id().as_u64()) + .unwrap(); + + if signal_module.signal_set.find_signal().is_none() { + // 记得释放锁 + drop(signal_modules); + yield_now_task(); + if process.have_signals().is_some() { + return Err(SyscallError::EINTR); + } + } else { + // 说明来了一个信号 + break; + } + } + Err(SyscallError::EINTR) +} + +/// Note: It can only be called by the signal processing function during signal processing. +pub fn syscall_sigreturn() -> SyscallResult { + Ok(axprocess::signal::signal_return()) +} + +/// # Arguments +/// * `flag` - SigMaskFlag +/// * `new_mask` - *const usize +/// * `old_mask` - *mut usize +/// * `sigsetsize` - usize, specifies the size in bytes of the signal sets in set and oldset, which is equal to sizeof(kernel_sigset_t) +pub fn syscall_sigprocmask(args: [usize; 6]) -> SyscallResult { + let flag = SigMaskFlag::from(args[0]); + let new_mask = args[1] as *const usize; + let old_mask = args[2] as *mut usize; + let sigsetsize = args[3]; + if sigsetsize != SIGSET_SIZE_IN_BYTE { + // 若sigsetsize不是正确的大小,则返回错误 + return Err(SyscallError::EINVAL); + } + + let current_process = current_process(); + if old_mask as usize != 0 + && current_process + .manual_alloc_for_lazy((old_mask as usize).into()) + .is_err() + { + return Err(SyscallError::EFAULT); + } + if new_mask as usize != 0 + && current_process + .manual_alloc_for_lazy((new_mask as usize).into()) + .is_err() + { + return Err(SyscallError::EPERM); + } + + let mut signal_modules = current_process.signal_modules.lock(); + let signal_module = signal_modules + .get_mut(¤t_task().id().as_u64()) + .unwrap(); + if old_mask as usize != 0 { + unsafe { + *old_mask = signal_module.signal_set.mask; + } + } + + if new_mask as usize != 0 { + let now_mask = unsafe { *new_mask }; + match flag { + SigMaskFlag::Block => { + signal_module.signal_set.mask |= now_mask; + } + SigMaskFlag::Unblock => { + signal_module.signal_set.mask &= !now_mask; + } + SigMaskFlag::Setmask => { + signal_module.signal_set.mask = now_mask; + } + } + } + Ok(0) +} + +/// 向pid指定的进程发送信号 +/// +/// 由于处理信号的单位在线程上,所以若进程中有多个线程,则会发送给主线程 +/// # Arguments +/// * `pid` - isize +/// * `signum` - isize +pub fn syscall_kill(args: [usize; 6]) -> SyscallResult { + let pid = args[0] as isize; + let signum = args[1] as isize; + if pid > 0 && signum > 0 { + // 不关心是否成功 + let _ = axprocess::signal::send_signal_to_process(pid, signum, None); + Ok(0) + } else if pid == 0 { + Err(SyscallError::ESRCH) + } else { + Err(SyscallError::EINVAL) + } +} + +/// 向tid指定的线程发送信号 +/// # Arguments +/// * `tid` - isize +/// * `signum` - isize +pub fn syscall_tkill(args: [usize; 6]) -> SyscallResult { + let tid = args[0] as isize; + let signum = args[1] as isize; + debug!( + "cpu: {}, send singal: {} to: {}", + this_cpu_id(), + signum, + tid + ); + if tid > 0 && signum > 0 { + let _ = axprocess::signal::send_signal_to_thread(tid, signum); + Ok(0) + } else { + Err(SyscallError::EINVAL) + } +} + +/// 向tid指定的线程组发送信号 +pub fn syscall_tgkill(args: [usize; 6]) -> SyscallResult { + let tgid = args[0] as isize; + let tid = args[1] as isize; + let signum = args[2] as isize; + debug!( + "cpu: {}, send singal: {} to: {}", + this_cpu_id(), + signum, + tid + ); + if tgid > 0 && tid > 0 && signum > 0 { + let _ = axprocess::signal::send_signal_to_thread(tid, signum); + Ok(0) + } else { + Err(SyscallError::EINVAL) + } +} + +/// Set and get the alternate signal stack +pub fn syscall_sigaltstack(args: [usize; 6]) -> SyscallResult { + let current_process = current_process(); + let ss = args[0] as *const SignalStack; + let old_ss = args[1] as *mut SignalStack; + if !ss.is_null() && current_process.manual_alloc_type_for_lazy(ss).is_err() { + return Err(SyscallError::EFAULT); + } + let task_id = current_task().id().as_u64(); + let mut signal_modules = current_process.signal_modules.lock(); + + if !old_ss.is_null() { + if current_process.manual_alloc_type_for_lazy(old_ss).is_err() { + return Err(SyscallError::EFAULT); + } + unsafe { + *old_ss = signal_modules.get(&task_id).unwrap().alternate_stack; + } + } + + if !ss.is_null() { + signal_modules.get_mut(&task_id).unwrap().alternate_stack = unsafe { *ss }; + } + + Ok(0) +} diff --git a/api/linux_syscall_api/src/syscall_task/imp/task.rs b/api/linux_syscall_api/src/syscall_task/imp/task.rs new file mode 100644 index 0000000..321a8de --- /dev/null +++ b/api/linux_syscall_api/src/syscall_task/imp/task.rs @@ -0,0 +1,707 @@ +use axfs::api::OpenFlags; +use axhal::time::current_time; +use axprocess::{ + current_process, current_task, exit_current_task, + flags::{CloneFlags, WaitStatus}, + link::{raw_ptr_to_ref_str, AT_FDCWD}, + set_child_tid, + signal::send_signal_to_process, + sleep_now_task, wait_pid, yield_now_task, Process, PID2PC, +}; +use axsync::Mutex; +use core::sync::atomic::AtomicI32; +use core::time::Duration; +// use axtask::{ +// monolithic_task::task::{SchedPolicy, SchedStatus}, +// AxTaskRef, +// }; +use crate::{ + syscall_fs::{ + ctype::pidfd::{new_pidfd, PidFd}, + imp::solve_path, + }, + CloneArgs, RLimit, SyscallError, SyscallResult, TimeSecs, WaitFlags, RLIMIT_AS, RLIMIT_NOFILE, + RLIMIT_STACK, +}; +use axlog::info; +use axtask::TaskId; +extern crate alloc; + +use alloc::{ + string::{String, ToString}, + sync::Arc, + vec::Vec, +}; + +use axsignal::{info::SigInfo, signal_no::SignalNo}; + +use axprocess::signal::SignalModule; +// pub static TEST_FILTER: Mutex> = Mutex::new(BTreeMap::new()); + +/// # Arguments +/// * `exit_code` - i32 +pub fn syscall_exit(args: [usize; 6]) -> ! { + let exit_code = args[0] as i32; + info!("exit: exit_code = {}", exit_code); + // let cases = ["fcanf", "fgetwc_buffering", "lat_pipe"]; + // let mut test_filter = TEST_FILTER.lock(); + // for case in cases { + // let case = case.to_string(); + // if test_filter.contains_key(&case) { + // test_filter.remove(&case); + // } + // } + // drop(test_filter); + exit_current_task(exit_code) +} + +// /// 过滤掉不想测的测例,比赛时使用 +// /// +// /// 若不想测该测例,返回false +// pub fn filter(testcase: String) -> bool { +// let mut test_filter = TEST_FILTER.lock(); +// if testcase == "./fstime".to_string() +// || testcase == "fstime".to_string() +// || testcase == "looper".to_string() +// || testcase == "./looper".to_string() +// { +// if test_filter.contains_key(&testcase) { +// let count = test_filter.get_mut(&testcase).unwrap(); +// if (testcase == "./fstime".to_string() || testcase == "fstime".to_string()) +// && *count == 6 +// { +// return false; +// } +// *count += 1; +// } else { +// if testcase == "looper".to_string() || testcase == "./looper".to_string() { +// return false; +// } +// test_filter.insert(testcase, 1); +// } +// } else { +// // 记录有无即可 + +// test_filter.insert(testcase, 1); +// } +// true +// } + +/// # Arguments +/// * `path` - *const u8 +/// * `argv` - *const usize +/// * `envp` - *const usize +pub fn syscall_exec(args: [usize; 6]) -> SyscallResult { + let path = args[0] as *const u8; + let mut argv = args[1] as *const usize; + let mut envp = args[2] as *const usize; + let path = solve_path(AT_FDCWD, Some(path), false)?; + + if path.is_dir() { + return Err(SyscallError::EISDIR); + } + let path = path.path().to_string(); + + let mut args_vec = Vec::new(); + // args相当于argv,指向了参数所在的地址 + loop { + let args_str_ptr = unsafe { *argv }; + if args_str_ptr == 0 { + break; + } + args_vec.push(unsafe { raw_ptr_to_ref_str(args_str_ptr as *const u8) }.to_string()); + unsafe { + argv = argv.add(1); + } + } + let mut envs_vec = Vec::new(); + if envp as usize != 0 { + loop { + let envp_str_ptr = unsafe { *envp }; + if envp_str_ptr == 0 { + break; + } + envs_vec.push(unsafe { raw_ptr_to_ref_str(envp_str_ptr as *const u8) }.to_string()); + unsafe { + envp = envp.add(1); + } + } + } + info!("args: {:?}", args_vec); + info!("envs: {:?}", envs_vec); + let curr_process = current_process(); + + // 设置 file_path + curr_process.set_file_path(path.clone()); + + let argc = args_vec.len(); + if curr_process.exec(path, args_vec, &envs_vec).is_err() { + exit_current_task(0); + } + Ok(argc as isize) +} + +/// # Arguments for riscv +/// * `flags` - usize +/// * `user_stack` - usize +/// * `ptid` - usize +/// * `tls` - usize +/// * `ctid` - usize +/// +/// # Arguments for x86_64 +/// * `flags` - usize +/// * `user_stack` - usize +/// * `ptid` - usize +/// * `ctid` - usize +/// * `tls` - usize +pub fn syscall_clone(args: [usize; 6]) -> SyscallResult { + let flags = args[0]; + let user_stack = args[1]; + let ptid = args[2]; + let tls: usize; + let ctid: usize; + #[cfg(target_arch = "x86_64")] + { + ctid = args[3]; + tls = args[4]; + } + #[cfg(not(target_arch = "x86_64"))] + { + tls = args[3]; + ctid = args[4]; + } + + let stack = if user_stack == 0 { + None + } else { + Some(user_stack) + }; + let curr_process = current_process(); + let sig_child = if SignalNo::from(flags & 0x3f) == SignalNo::SIGCHLD { + Some(SignalNo::SIGCHLD) + } else { + None + }; + + let clone_flags = CloneFlags::from_bits((flags & !0x3f) as u32).unwrap(); + + if clone_flags.contains(CloneFlags::CLONE_SIGHAND) + && !clone_flags.contains(CloneFlags::CLONE_VM) + { + // Error when CLONE_SIGHAND was specified in the flags mask, but CLONE_VM was not. + return Err(SyscallError::EINVAL); + } + + if let Ok(new_task_id) = curr_process.clone_task(flags, stack, ptid, tls, ctid, sig_child) { + if clone_flags.contains(CloneFlags::CLONE_PIDFD) { + if clone_flags.contains(CloneFlags::CLONE_PARENT_SETTID) { + return Err(SyscallError::EINVAL); + } + if curr_process.manual_alloc_for_lazy(ptid.into()).is_ok() { + unsafe { + *(ptid as *mut i32) = new_pidfd(new_task_id, OpenFlags::empty())? as i32; + } + } + } + + Ok(new_task_id as isize) + } else { + Err(SyscallError::ENOMEM) + } +} + +/// 创建子进程的新函数,所有信息保存在 CloneArgs +/// # Arguments +/// * `clone_args` - *const CloneArgs +/// * `size` - usize +pub fn syscall_clone3(args: [usize; 6]) -> SyscallResult { + let size = args[1]; + if size < core::mem::size_of::() { + // The size argument that is supplied to clone3() should be initialized to the size of this structure + return Err(SyscallError::EINVAL); + } + let curr_process = current_process(); + let clone_args = match curr_process.manual_alloc_type_for_lazy(args[0] as *const CloneArgs) { + Ok(_) => unsafe { &mut *(args[0] as *mut CloneArgs) }, + Err(_) => return Err(SyscallError::EFAULT), + }; + let clone_flags = CloneFlags::from_bits(clone_args.flags as u32).unwrap(); + if (clone_flags.contains(CloneFlags::CLONE_THREAD) + || clone_flags.contains(CloneFlags::CLONE_PARENT)) + && clone_args.exit_signal != 0 + { + // Error when CLONE_THREAD or CLONE_PARENT was specified in the flags mask, but a signal was specified in exit_signal. + return Err(SyscallError::EINVAL); + } + if clone_flags.contains(CloneFlags::CLONE_SIGHAND) + && !clone_flags.contains(CloneFlags::CLONE_VM) + { + // Error when CLONE_SIGHAND was specified in the flags mask, but CLONE_VM was not. + return Err(SyscallError::EINVAL); + } + + let stack = if clone_args.stack == 0 { + None + } else { + Some((clone_args.stack + clone_args.stack_size) as usize) + }; + let sig_child = if clone_args.exit_signal != 0 { + Some(SignalNo::from(clone_args.exit_signal as usize)) + } else { + None + }; + + if clone_args.stack != 0 && (clone_args.stack % 16 != 0 || clone_args.stack_size == 0) { + return Err(SyscallError::EINVAL); + } + + if let Ok(new_task_id) = curr_process.clone_task( + clone_args.flags as usize, + stack, + clone_args.parent_tid as usize, + clone_args.tls as usize, + clone_args.child_tid as usize, + sig_child, + ) { + if clone_flags.contains(CloneFlags::CLONE_PIDFD) { + unsafe { + *(clone_args.pidfd as *mut u64) = + new_pidfd(new_task_id, OpenFlags::empty())? as u64; + } + } + Ok(new_task_id as isize) + } else { + Err(SyscallError::ENOMEM) + } +} + +/// 创建一个子进程,挂起父进程,直到子进程exec或者exit,父进程才继续执行 +#[cfg(target_arch = "x86_64")] +pub fn syscall_vfork() -> SyscallResult { + let args: [usize; 6] = [0x4011, 0, 0, 0, 0, 0]; + // TODO: check the correctness + syscall_clone(args).map(|new_task_id| { + let task_ref = axprocess::get_task_ref(new_task_id as u64).unwrap(); + axtask::vfork_suspend(&task_ref); + new_task_id + }) +} + +/// 等待子进程完成任务,若子进程没有完成,则自身yield +/// 当前仅支持WNOHANG选项,即若未完成时则不予等待,直接返回0 +/// # Arguments +/// * `pid` - i32 +/// * `exit_code_ptr` - *mut i32 +/// * `option` - WaitFlags +pub fn syscall_wait4(args: [usize; 6]) -> SyscallResult { + let pid: i32 = args[0] as i32; + let exit_code_ptr = args[1] as *mut i32; + let option = WaitFlags::from_bits(args[2] as u32).unwrap(); + loop { + let answer = unsafe { wait_pid(pid, exit_code_ptr) }; + match answer { + Ok(pid) => { + return Ok(pid as isize); + } + Err(status) => { + match status { + WaitStatus::NotExist => { + return Err(SyscallError::ECHILD); + } + WaitStatus::Running => { + if option.contains(WaitFlags::WNOHANG) { + // 不予等待,直接返回0 + return Ok(0); + } else { + // wait回来之后,如果还需要wait,先检查是否有信号未处理 + + match current_process().have_restart_signals() { + Some(true) => return Err(SyscallError::ERESTART), + Some(false) => return Err(SyscallError::EINTR), + None => {} + } + // 执行yield操作,切换任务 + yield_now_task(); + } + } + _ => { + panic!("Shouldn't reach here!"); + } + } + } + }; + } +} + +/// To yield the current task +pub fn syscall_yield() -> SyscallResult { + yield_now_task(); + Ok(0) +} + +/// 当前任务进入睡眠,req指定了睡眠的时间 +/// rem存储当睡眠完成时,真实睡眠时间和预期睡眠时间之间的差值 +/// # Arguments +/// * `req` - *const TimeSecs +/// * `rem` - *mut TimeSecs +pub fn syscall_sleep(args: [usize; 6]) -> SyscallResult { + let req = args[0] as *const TimeSecs; + let rem = args[1] as *mut TimeSecs; + let req_time = unsafe { *req }; + let start_to_sleep = current_time(); + // info!("sleep: req_time = {:?}", req_time); + let dur = Duration::new(req_time.tv_sec as u64, req_time.tv_nsec as u32); + sleep_now_task(dur); + // 若被唤醒时时间小于请求时间,则将剩余时间写入rem + let sleep_time = current_time() - start_to_sleep; + if rem as usize != 0 { + if sleep_time < dur { + let delta = (dur - sleep_time).as_nanos() as usize; + unsafe { + *rem = TimeSecs { + tv_sec: delta / 1_000_000_000, + tv_nsec: delta % 1_000_000_000, + } + }; + } else { + unsafe { + *rem = TimeSecs { + tv_sec: 0, + tv_nsec: 0, + } + }; + } + } + + if current_process().have_signals().is_some() { + return Err(SyscallError::EINTR); + } + Ok(0) +} + +/// 设置tid对应的指针 +/// 返回值为当前的tid +/// # Arguments +/// * `tid` - usize +pub fn syscall_set_tid_address(args: [usize; 6]) -> SyscallResult { + let tid = args[0]; + set_child_tid(tid); + Ok(current_task().id().as_u64() as isize) +} + +/// 设置任务资源限制 +/// +/// pid 设为0时,表示应用于自己 +/// +/// # Arguments +/// * `pid` - usize +/// * `resource` - i32 +/// * `new_limit` - *const RLimit +/// * `old_limit` - *mut RLimit +pub fn syscall_prlimit64(args: [usize; 6]) -> SyscallResult { + let pid = args[0]; + let resource = args[1] as i32; + let new_limit = args[2] as *const RLimit; + let old_limit = args[3] as *mut RLimit; + // 当pid不为0,其实没有权利去修改其他的进程的资源限制 + let curr_process = current_process(); + if pid == 0 || pid == curr_process.pid() as usize { + match resource { + // TODO: 改变了新创建的任务栈大小,但未实现当前任务的栈扩展 + RLIMIT_STACK => { + let mut stack_limit: u64 = curr_process.get_stack_limit(); + if old_limit as usize != 0 { + unsafe { + *old_limit = RLimit { + rlim_cur: stack_limit, + rlim_max: stack_limit, + }; + } + } + if new_limit as usize != 0 { + let new_size = unsafe { (*new_limit).rlim_cur }; + if new_size > axconfig::TASK_STACK_SIZE as u64 { + stack_limit = new_size; + curr_process.set_stack_limit(stack_limit); + } + } + } + RLIMIT_NOFILE => { + // 仅支持修改最大文件数 + if old_limit as usize != 0 { + let limit = curr_process.fd_manager.get_limit(); + unsafe { + *old_limit = RLimit { + rlim_cur: limit as u64, + rlim_max: limit as u64, + }; + } + } + if new_limit as usize != 0 { + let new_limit = unsafe { (*new_limit).rlim_cur }; + curr_process.fd_manager.set_limit(new_limit); + } + } + RLIMIT_AS => { + const USER_MEMORY_LIMIT: usize = 0xffff_ffff; + if old_limit as usize != 0 { + unsafe { + *old_limit = RLimit { + rlim_cur: USER_MEMORY_LIMIT as u64, + rlim_max: USER_MEMORY_LIMIT as u64, + }; + } + } + } + _ => {} + } + } + Ok(0) +} + +/// not support +pub fn syscall_getpgid() -> SyscallResult { + Ok(0) +} + +/// # Arguments +/// * `pgid`: usize +pub fn syscall_setpgid(args: [usize; 6]) -> SyscallResult { + let pgid = args[0]; + info!("not support setpgid, try to set {}", pgid); + Ok(0) +} + +/// 当前不涉及多核情况 +pub fn syscall_getpid() -> SyscallResult { + Ok(current_process().pid() as isize) +} + +/// To get the parent process id +pub fn syscall_getppid() -> SyscallResult { + Ok(current_process().get_parent() as isize) +} + +/// # Arguments +/// * `new_mask` - i32 +pub fn syscall_umask(args: [usize; 6]) -> SyscallResult { + let new_mask = args[0] as i32; + Ok(current_process().fd_manager.set_mask(new_mask) as isize) +} + +/// 获取用户 id。在实现多用户权限前默认为最高权限 +pub fn syscall_getuid() -> SyscallResult { + Ok(0) +} + +/// 获取有效用户 id,即相当于哪个用户的权限。在实现多用户权限前默认为最高权限 +pub fn syscall_geteuid() -> SyscallResult { + Ok(0) +} + +/// 获取用户组 id。在实现多用户权限前默认为最高权限 +pub fn syscall_getgid() -> SyscallResult { + Ok(0) +} + +/// 获取有效用户组 id,即相当于哪个用户的权限。在实现多用户权限前默认为最高权限 +pub fn syscall_getegid() -> SyscallResult { + Ok(0) +} + +/// 获取当前任务的线程 id +pub fn syscall_gettid() -> SyscallResult { + Ok(current_task().id().as_u64() as isize) +} + +/// Creates a session and sets the process group ID +/// +/// The calling process is the leader of the new session +pub fn syscall_setsid() -> SyscallResult { + let process = current_process(); + let task = current_task(); + + let task_id = task.id().as_u64(); + + // 当前 process 已经是 process group leader + if process.pid() == task_id { + return Err(SyscallError::EPERM); + } + + // 从当前 process 的 thread group 中移除 calling thread + process.tasks.lock().retain(|t| t.id().as_u64() != task_id); + + // 新建 process group 并加入 + let new_process = Process::new( + TaskId::new().as_u64(), + process.get_stack_limit(), + process.get_parent(), + Mutex::new(process.memory_set.lock().clone()), + process.get_heap_bottom(), + Arc::new(Mutex::new(String::from("/"))), + Arc::new(AtomicI32::new(0o022)), + Arc::new(Mutex::new(process.fd_manager.fd_table.lock().clone())), + ); + + new_process + .signal_modules + .lock() + .insert(task_id, SignalModule::init_signal(None)); + + new_process.tasks.lock().push(task.as_task_ref().clone()); + task.set_leader(true); + task.set_process_id(new_process.pid()); + + // 修改 PID2PC + PID2PC + .lock() + .insert(new_process.pid(), Arc::new(new_process)); + + Ok(task_id as isize) +} + +/// arch_prc +#[cfg(target_arch = "x86_64")] +/// # Arguments +/// * `code` - usize +/// * `addr` - *mut usize +pub fn syscall_arch_prctl(args: [usize; 6]) -> SyscallResult { + /* + #define ARCH_SET_GS 0x1001 + #define ARCH_SET_FS 0x1002 + #define ARCH_GET_FS 0x1003 + #define ARCH_GET_GS 0x1004 + */ + let code = args[0]; + let addr = args[1] as *mut usize; + match code { + 0x1002 => { + #[cfg(target_arch = "x86_64")] + unsafe { + axhal::arch::write_thread_pointer(*addr); + // *(read_thread_pointer() as *mut usize) = addr; + } + Ok(0) + } + 0x1003 => { + #[cfg(target_arch = "x86_64")] + unsafe { + *addr = *(axhal::arch::read_thread_pointer() as *mut usize); + } + Ok(0) + } + 0x1001 | 0x1004 => todo!(), + _ => Err(SyscallError::EINVAL), + } + //Ok(0) +} + +/// To implement the fork syscall for x86_64 +#[cfg(target_arch = "x86_64")] +pub fn syscall_fork() -> SyscallResult { + axlog::warn!("transfer syscall_fork to syscall_clone"); + let args = [1, 0, 0, 0, 0, 0]; + syscall_clone(args) +} + +/// prctl +/// # Arguments +/// * `option` - usize +/// * `arg2` - *mut u8 +pub fn syscall_prctl(args: [usize; 6]) -> SyscallResult { + use crate::{PrctlOption, PR_NAME_SIZE}; + + let option = args[0]; + let arg2 = args[1] as *mut u8; + match PrctlOption::try_from(option) { + Ok(PrctlOption::PR_GET_NAME) => { + // 获取进程名称。 + let mut process_name = current_task().name().to_string(); + process_name += "\0"; + // [syscall 定义](https://man7.org/linux/man-pages/man2/prctl.2.html)要求 NAME 应该不超过 16 Byte + process_name.truncate(PR_NAME_SIZE); + // 把 arg2 转换成可写的 buffer + if current_process() + .manual_alloc_for_lazy((arg2 as usize).into()) + .is_ok() + // 直接访问前需要确保地址已经被分配 + { + unsafe { + let name = &mut *core::ptr::slice_from_raw_parts_mut(arg2, PR_NAME_SIZE); + name[..process_name.len()].copy_from_slice(process_name.as_bytes()); + } + Ok(0) + } else { + Err(SyscallError::EINVAL) + } + } + Ok(PrctlOption::PR_SET_NAME) => { + if current_process() + .manual_alloc_for_lazy((arg2 as usize).into()) + .is_ok() + { + unsafe { + let name = &mut *core::ptr::slice_from_raw_parts_mut(arg2, PR_NAME_SIZE); + let new_name_bytes = name + .iter() + .take_while(|&&c| c != 0) + .cloned() + .collect::>(); + let new_name = String::from_utf8(new_name_bytes).unwrap_or_default(); + // Set the new process name + current_task().set_name(&new_name); + } + Ok(0) + } else { + Err(SyscallError::EINVAL) + } + } + _ => Ok(0), + } +} + +/// Sendthe signal sig to the target process referred to by pidfd +pub fn syscall_pidfd_send_signal(args: [usize; 6]) -> SyscallResult { + let fd = args[0]; + let signum = args[1] as i32; + axlog::warn!("Ignore the info arguments"); + + let curr_process = current_process(); + let fd_table = curr_process.fd_manager.fd_table.lock(); + + let pidfd_file = match fd_table.get(fd) { + Some(Some(f)) => f.clone(), + _ => return Err(SyscallError::EBADF), + }; + + let pidfd = pidfd_file + .as_any() + .downcast_ref::() + .ok_or(SyscallError::EBADF)?; + let sig_info_ptr = args[2] as *const SigInfo; + + let sig_info = if sig_info_ptr.is_null() { + SigInfo { + si_code: 0, + si_errno: 0, + si_signo: signum, + pid: curr_process.pid() as i32, + uid: 0, + ..Default::default() + } + } else { + if curr_process + .manual_alloc_type_for_lazy(sig_info_ptr) + .is_err() + { + return Err(SyscallError::EFAULT); + } + unsafe { *sig_info_ptr } + }; + + info!("Pid: {} Sig Info: {:?}", pidfd.pid(), sig_info.si_val_int); + + send_signal_to_process(pidfd.pid() as isize, signum as isize, Some(sig_info))?; + + Ok(0) +} diff --git a/api/linux_syscall_api/src/syscall_task/imp/utils.rs b/api/linux_syscall_api/src/syscall_task/imp/utils.rs new file mode 100644 index 0000000..95be303 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_task/imp/utils.rs @@ -0,0 +1,305 @@ +use core::{slice::from_raw_parts_mut, time::Duration}; + +use axhal::time::{current_time, current_time_nanos, nanos_to_ticks, NANOS_PER_SEC}; + +use axprocess::{current_process, current_task, time_stat_output}; +use rand::{rngs::SmallRng, Fill, SeedableRng}; + +use crate::{ + ClockId, ITimerVal, RusageFlags, SysInfo, SyscallError, SyscallResult, TimeSecs, TimeVal, Tms, + UtsName, +}; + +/// 返回值为当前经过的时钟中断数 +/// # Arguments +/// * `tms` - *mut Tms +pub fn syscall_time(args: [usize; 6]) -> SyscallResult { + let tms = args[0] as *mut Tms; + let (_, utime_us, _, stime_us) = time_stat_output(); + unsafe { + *tms = Tms { + tms_utime: utime_us, + tms_stime: stime_us, + tms_cutime: utime_us, + tms_cstime: stime_us, + } + } + Ok(nanos_to_ticks(current_time_nanos()) as isize) +} + +/// 获取当前系统时间并且存储在给定结构体中 +/// # Arguments +/// * `ts` - *mut TimeVal +pub fn syscall_get_time_of_day(args: [usize; 6]) -> SyscallResult { + let ts = args[0] as *mut TimeVal; + let current_us = current_time_nanos() as usize / 1000; + unsafe { + *ts = TimeVal { + sec: current_us / 1_000_000, + usec: current_us % 1_000_000, + } + } + Ok(0) +} + +/// 用于获取当前系统时间并且存储在对应的结构体中 +/// # Arguments +/// * `clock_id` - usize +/// * `ts` - *mut TimeSecs +pub fn syscall_clock_get_time(args: [usize; 6]) -> SyscallResult { + let _clock_id = args[0]; + let ts = args[1] as *mut TimeSecs; + unsafe { + (*ts) = TimeSecs::now(); + } + Ok(0) +} + +/// 获取系统信息 +/// # Arguments +/// * `uts` - *mut UtsName +pub fn syscall_uname(args: [usize; 6]) -> SyscallResult { + let uts = args[0] as *mut UtsName; + unsafe { + *uts = UtsName::default(); + } + Ok(0) +} + +/// 获取系统的启动时间和内存信息,当前仅支持启动时间 +/// # Arguments +/// * `info` - *mut SysInfo +pub fn syscall_sysinfo(args: [usize; 6]) -> SyscallResult { + let info = args[0] as *mut SysInfo; + let process = current_process(); + if process + .manual_alloc_type_for_lazy(info as *const SysInfo) + .is_err() + { + return Err(SyscallError::EFAULT); + } + + unsafe { + // 获取以秒为单位的时间 + (*info).uptime = (current_time_nanos() / NANOS_PER_SEC) as isize; + } + Ok(0) +} + +/// # Arguments +/// * `which` - usize +/// * `new_value` - *const ITimerVal +/// * `old_value` - *mut ITimerVal +pub fn syscall_settimer(args: [usize; 6]) -> SyscallResult { + let which = args[0]; + let new_value = args[1] as *const ITimerVal; + let old_value = args[2] as *mut ITimerVal; + let process = current_process(); + + if new_value.is_null() { + return Err(SyscallError::EFAULT); + } + + let new_value = match process.manual_alloc_type_for_lazy(new_value) { + Ok(_) => unsafe { &*new_value }, + Err(_) => return Err(SyscallError::EFAULT), + }; + + if !old_value.is_null() { + if process.manual_alloc_type_for_lazy(old_value).is_err() { + return Err(SyscallError::EFAULT); + } + + let (time_interval_us, time_remained_us) = current_task().timer_output(); + unsafe { + (*old_value).it_interval = TimeVal::from_micro(time_interval_us); + (*old_value).it_value = TimeVal::from_micro(time_remained_us); + } + } + let (time_interval_ns, time_remained_ns) = ( + new_value.it_interval.turn_to_nanos(), + new_value.it_value.turn_to_nanos(), + ); + if current_task().set_timer(time_interval_ns, time_remained_ns, which) { + Ok(0) + } else { + // 说明which参数错误 + Err(SyscallError::EFAULT) + } +} + +/// # Arguments +/// * `which` - usize +/// * `value` - *mut ITimerVal +pub fn syscall_gettimer(args: [usize; 6]) -> SyscallResult { + let _which = args[0]; + let value = args[1] as *mut ITimerVal; + let process = current_process(); + if process + .manual_alloc_type_for_lazy(value as *const ITimerVal) + .is_err() + { + return Err(SyscallError::EFAULT); + } + let (time_interval_us, time_remained_us) = current_task().timer_output(); + unsafe { + (*value).it_interval = TimeVal::from_micro(time_interval_us); + (*value).it_value = TimeVal::from_micro(time_remained_us); + } + Ok(0) +} + +/// # Arguments +/// * `who` - i32 +/// * `utime` - *mut TimeVal +pub fn syscall_getrusage(args: [usize; 6]) -> SyscallResult { + let who = args[0] as i32; + let utime = args[1] as *mut TimeVal; + let stime: *mut TimeVal = unsafe { utime.add(1) }; + let process = current_process(); + if process.manual_alloc_type_for_lazy(utime).is_err() + || process.manual_alloc_type_for_lazy(stime).is_err() + { + return Err(SyscallError::EFAULT); + } + if RusageFlags::from(who).is_some() { + let (_, utime_us, _, stime_us) = time_stat_output(); + unsafe { + *utime = TimeVal::from_micro(utime_us); + *stime = TimeVal::from_micro(stime_us); + } + Ok(0) + } else { + Err(SyscallError::EINVAL) + } +} + +/// # Arguments +/// * `buf` - *mut u8 +/// * `len` - usize +/// * `flags` - usize +pub fn syscall_getrandom(args: [usize; 6]) -> SyscallResult { + let buf = args[0] as *mut u8; + let len = args[1]; + let _flags = args[2]; + let process = current_process(); + + if process + .manual_alloc_range_for_lazy( + (buf as usize).into(), + unsafe { buf.add(len) as usize }.into(), + ) + .is_err() + { + return Err(SyscallError::EFAULT); + } + + let buf = unsafe { from_raw_parts_mut(buf, len) }; + + // TODO: flags + // - GRND_RANDOM: use /dev/random or /dev/urandom + // - GRND_NONBLOCK: EAGAIN when block + let mut rng = SmallRng::from_seed([0; 32]); + buf.try_fill(&mut rng).unwrap(); + + Ok(buf.len() as isize) +} + +/// # 获取时钟精度 +/// +/// # Arguments +/// * `id` - usize, 时钟种类,当前仅支持CLOCK_MONOTONIC +/// * `res` - *mut TimeSecs, 存储时钟精度的结构体的地址 +pub fn syscall_clock_getres(args: [usize; 6]) -> SyscallResult { + let id = args[0]; + let res = args[1] as *mut TimeSecs; + let id = if let Ok(opt) = ClockId::try_from(id) { + opt + } else { + return Err(SyscallError::EINVAL); + }; + + if id != ClockId::CLOCK_MONOTONIC { + // 暂时不支持其他类型 + return Err(SyscallError::EINVAL); + } + + let process = current_process(); + if process.manual_alloc_type_for_lazy(res).is_err() { + return Err(SyscallError::EFAULT); + } + + unsafe { + (*res) = TimeSecs { + tv_nsec: 1, + tv_sec: 0, + }; + } + + Ok(0) +} + +/// # 指定任务进行睡眠 +/// +/// # Arguments +/// * id: usize,指定使用的时钟ID,对应结构体为ClockId +/// +/// * flags: usize,指定是使用相对时间还是绝对时间 +/// +/// * request: *const TimeSecs指定睡眠的时间,根据flags划分为相对时间或者绝对时间 +/// +/// * remain: *mut TimeSecs存储剩余睡眠时间。当任务提前醒来时,如果flags不为绝对时间,且remain不为空,则将剩余存储时间存进remain所指向地址。 +/// +/// 若睡眠被信号处理打断或者遇到未知错误,则返回对应错误码 +pub fn syscall_clock_nanosleep(args: [usize; 6]) -> SyscallResult { + let id = args[0]; + let flags = args[1]; + let request = args[2] as *const TimeSecs; + let remain = args[3] as *mut TimeSecs; + const TIMER_ABSTIME: usize = 1; + let id = if let Ok(opt) = ClockId::try_from(id) { + opt + } else { + return Err(SyscallError::EINVAL); + }; + + if id != ClockId::CLOCK_MONOTONIC { + // 暂时不支持其他类型 + axlog::warn!("Unsupported clock id: {:?}", id); + } + + let process = current_process(); + + if process.manual_alloc_type_for_lazy(request).is_err() { + return Err(SyscallError::EFAULT); + } + let request_time = unsafe { *request }; + let request_time = Duration::new(request_time.tv_sec as u64, request_time.tv_nsec as u32); + let deadline = if flags != TIMER_ABSTIME { + current_time() + request_time + } else { + if request_time < current_time() { + return Ok(0); + } + request_time + }; + + axtask::sleep_until(deadline); + + let current_time = current_time(); + if current_time < deadline && !remain.is_null() { + if process.manual_alloc_type_for_lazy(remain).is_err() { + return Err(SyscallError::EFAULT); + } else { + let delta = (deadline - current_time).as_nanos() as usize; + unsafe { + *remain = TimeSecs { + tv_sec: delta / 1_000_000_000, + tv_nsec: delta % 1_000_000_000, + } + }; + return Err(SyscallError::EINTR); + } + } + Ok(0) +} diff --git a/api/linux_syscall_api/src/syscall_task/mod.rs b/api/linux_syscall_api/src/syscall_task/mod.rs new file mode 100644 index 0000000..04c9600 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_task/mod.rs @@ -0,0 +1,99 @@ +//! 提供和 task 模块相关的 syscall + +mod task_syscall_id; + +use crate::SyscallResult; +pub use task_syscall_id::TaskSyscallId::{self, *}; + +mod imp; + +pub use imp::*; + +/// 进行 syscall 的分发 +pub fn task_syscall(syscall_id: task_syscall_id::TaskSyscallId, args: [usize; 6]) -> SyscallResult { + match syscall_id { + EXIT => syscall_exit(args), + EXECVE => syscall_exec(args), + CLONE => syscall_clone(args), + CLONE3 => syscall_clone3(args), + NANO_SLEEP => syscall_sleep(args), + SCHED_YIELD => syscall_yield(), + TIMES => syscall_time(args), + UNAME => syscall_uname(args), + GETTIMEOFDAY => syscall_get_time_of_day(args), + GETPGID => syscall_getpgid(), + SETPGID => syscall_setpgid(args), + GETPID => syscall_getpid(), + + GETPPID => syscall_getppid(), + WAIT4 => syscall_wait4(args), + GETRANDOM => syscall_getrandom(args), + + SIGSUSPEND => syscall_sigsuspend(args), + + SIGACTION => syscall_sigaction(args), + + KILL => syscall_kill(args), + + TKILL => syscall_tkill(args), + + TGKILL => syscall_tgkill(args), + + SIGPROCMASK => syscall_sigprocmask(args), + SIGALTSTACK => syscall_sigaltstack(args), + SIGRETURN => syscall_sigreturn(), + EXIT_GROUP => syscall_exit(args), + SET_TID_ADDRESS => syscall_set_tid_address(args), + PRLIMIT64 => syscall_prlimit64(args), + CLOCK_GET_TIME => syscall_clock_get_time(args), + GETUID => syscall_getuid(), + GETEUID => syscall_geteuid(), + GETGID => syscall_getgid(), + SETGID => Ok(0), + GETEGID => syscall_getegid(), + GETTID => syscall_gettid(), + FUTEX => syscall_futex(args), + SET_ROBUST_LIST => syscall_set_robust_list(args), + GET_ROBUST_LIST => syscall_get_robust_list(args), + SYSINFO => syscall_sysinfo(args), + SETITIMER => syscall_settimer(args), + GETTIMER => syscall_gettimer(args), + SETSID => syscall_setsid(), + GETRUSAGE => syscall_getrusage(args), + UMASK => syscall_umask(args), + // 不做处理即可 + SIGTIMEDWAIT => Ok(0), + SYSLOG => Ok(0), + MADVICE => Ok(0), + SCHED_SETAFFINITY => Ok(0), + SCHED_GETAFFINITY => syscall_sched_getaffinity(args), + SCHED_SETSCHEDULER => syscall_sched_setscheduler(args), + SCHED_GETSCHEDULER => syscall_sched_getscheduler(args), + SCHED_GET_PRORITY_MAX => syscall_sched_getscheduler_max(args), + SCHED_GET_PRORITY_MIN => syscall_sched_getscheduler_min(args), + GET_MEMPOLICY => Ok(0), + CLOCK_GETRES => syscall_clock_getres(args), + CLOCK_NANOSLEEP => syscall_clock_nanosleep(args), + PRCTL => syscall_prctl(args), + PIDFD_SEND_SIGNAL => syscall_pidfd_send_signal(args), + // syscall below just for x86_64 + #[cfg(target_arch = "x86_64")] + VFORK => syscall_vfork(), + #[cfg(target_arch = "x86_64")] + ARCH_PRCTL => syscall_arch_prctl(args), + #[cfg(target_arch = "x86_64")] + FORK => syscall_fork(), + #[cfg(target_arch = "x86_64")] + ALARM => Ok(0), + #[cfg(target_arch = "x86_64")] + RSEQ => Ok(0), + #[cfg(target_arch = "x86_64")] + TIME => Ok(0), + #[allow(unused)] + _ => { + panic!("Invalid Syscall Id: {:?}!", syscall_id); + // return -1; + // exit(-1) + } + } +} diff --git a/api/linux_syscall_api/src/syscall_task/task_syscall_id.rs b/api/linux_syscall_api/src/syscall_task/task_syscall_id.rs new file mode 100644 index 0000000..49131c7 --- /dev/null +++ b/api/linux_syscall_api/src/syscall_task/task_syscall_id.rs @@ -0,0 +1,144 @@ +//! 记录该模块使用到的系统调用 id +//! +//! +#[cfg(any( + target_arch = "riscv32", + target_arch = "riscv64", + target_arch = "aarch64" +))] +numeric_enum_macro::numeric_enum! { +#[repr(usize)] +#[allow(non_camel_case_types)] +#[allow(missing_docs)] +#[derive(Eq, PartialEq, Debug, Copy, Clone)] +pub enum TaskSyscallId { + EXIT = 93, + EXIT_GROUP = 94, + SET_TID_ADDRESS = 96, + FUTEX = 98, + SET_ROBUST_LIST = 99, + GET_ROBUST_LIST = 100, + NANO_SLEEP = 101, + GETTIMER = 102, + SETITIMER = 103, + CLOCK_GETRES = 114, + CLOCK_NANOSLEEP = 115, + SYSLOG = 116, + SCHED_SETSCHEDULER = 119, + SCHED_GETSCHEDULER = 120, + SCHED_SETAFFINITY = 122, + SCHED_GETAFFINITY = 123, + SIGALTSTACK = 132, + GET_MEMPOLICY = 236, + SETPGID = 154, + GETPGID = 155, + SETSID = 157, + GETRUSAGE = 165, + UMASK = 166, + PRCTL = 167, + GETPID = 172, + GETPPID = 173, + GETUID = 174, + GETEUID = 175, + GETGID = 176, + SETGID = 144, + GETEGID = 177, + GETTID = 178, + SYSINFO = 179, + CLONE = 220, + CLONE3 = 435, + EXECVE = 221, + MADVICE = 233, + WAIT4 = 260, + GETRANDOM = 278, + SCHED_YIELD = 124, + CLOCK_GET_TIME = 113, + SIGTIMEDWAIT = 137, + TIMES = 153, + UNAME = 160, + GETTIMEOFDAY = 169, + PRLIMIT64 = 261, + // 信号模块 + KILL = 129, + TKILL = 130, + TGKILL = 131, + SIGSUSPEND = 133, + SIGACTION = 134, + SIGPROCMASK = 135, + SIGRETURN = 139, + PIDFD_SEND_SIGNAL = 424, +} +} + +#[cfg(target_arch = "x86_64")] +numeric_enum_macro::numeric_enum! { + #[repr(usize)] + #[allow(non_camel_case_types)] + #[allow(missing_docs)] + #[derive(Eq, PartialEq, Debug, Copy, Clone)] + pub enum TaskSyscallId { + ARCH_PRCTL = 158, + EXIT = 60, + EXIT_GROUP = 231, + SET_TID_ADDRESS = 218, + FUTEX = 202, + SET_ROBUST_LIST = 273, + GET_ROBUST_LIST = 274, + NANO_SLEEP = 35, + GETTIMER = 36, + SETITIMER = 38, + CLOCK_GETRES = 229, + CLOCK_NANOSLEEP = 230, + TIME = 201, + SYSLOG = 103, + SCHED_SETSCHEDULER = 144, + SCHED_GETSCHEDULER = 145, + SCHED_GET_PRORITY_MAX = 146, + SCHED_GET_PRORITY_MIN = 147, + SCHED_SETAFFINITY = 203, + SCHED_GETAFFINITY = 204, + GET_MEMPOLICY = 239, + SETSID = 112, + GETRUSAGE = 98, + UMASK = 95, + PRCTL = 157, + GETPID = 39, + GETPPID = 110, + GETUID = 102, + GETEUID = 107, + GETGID = 104, + SETGID = 106, + GETPGID = 121, + SETPGID = 109, + GETEGID = 108, + GETTID = 186, + SYSINFO = 99, + CLONE = 56, + CLONE3 = 435, + EXECVE = 59, + MADVICE = 28, + WAIT4 = 61, + GETRANDOM = 318, + SCHED_YIELD = 24, + CLOCK_GET_TIME = 228, + SIGTIMEDWAIT = 128, + TIMES = 100, + UNAME = 63, + GETTIMEOFDAY = 96, + PRLIMIT64 = 302, + RSEQ = 334, + // 信号模块 + KILL = 62, + TKILL = 200, + TGKILL = 234, + SIGSUSPEND = 130, + SIGACTION = 13, + SIGPROCMASK = 14, + SIGRETURN = 15, + FORK = 57, + VFORK = 58, + ALARM = 37, + SIGALTSTACK = 131, + PIDFD_SEND_SIGNAL = 424, + } +} diff --git a/api/linux_syscall_api/src/trap.rs b/api/linux_syscall_api/src/trap.rs new file mode 100644 index 0000000..580cffd --- /dev/null +++ b/api/linux_syscall_api/src/trap.rs @@ -0,0 +1,70 @@ +//! Define the trap handler for the whole kernel +pub use axhal::{mem::VirtAddr, paging::MappingFlags}; + +use crate::syscall::syscall; + +fn time_stat_from_kernel_to_user() { + axprocess::time_stat_from_kernel_to_user(); +} + +fn time_stat_from_user_to_kernel() { + axprocess::time_stat_from_user_to_kernel(); +} + +/// Handle the interrupt +/// +/// # Arguments +/// +/// * `irq_num` - The number of the interrupt +/// +/// * `from_user` - Whether the interrupt is from user space +pub fn handle_irq(irq_num: usize, from_user: bool) { + // trap进来,统计时间信息 + // 只有当trap是来自用户态才进行统计 + if from_user { + time_stat_from_user_to_kernel(); + } + axhal::irq::dispatch_irq(irq_num); + if from_user { + time_stat_from_kernel_to_user(); + } +} + +/// Handle the syscall +/// +/// # Arguments +/// +/// * `syscall_id` - The id of the syscall +/// +/// * `args` - The arguments of the syscall +pub fn handle_syscall(syscall_id: usize, args: [usize; 6]) -> isize { + time_stat_from_user_to_kernel(); + let ans = syscall(syscall_id, args); + time_stat_from_kernel_to_user(); + ans +} + +/// Handle the page fault exception +/// +/// # Arguments +/// +/// * `addr` - The address where the page fault occurs +/// +/// * `flags` - The permission which the page fault needs +pub fn handle_page_fault(addr: VirtAddr, flags: MappingFlags) { + time_stat_from_user_to_kernel(); + axprocess::handle_page_fault(addr, flags); + time_stat_from_kernel_to_user(); +} + +/// To handle the pending signals for current process +pub fn handle_signals() { + time_stat_from_user_to_kernel(); + axprocess::signal::handle_signals(); + time_stat_from_kernel_to_user(); +} + +/// Record the occurrence of a syscall +pub fn record_trap(syscall_code: usize) { + axfs::axfs_ramfs::INTERRUPT.lock().record(syscall_code); +} diff --git a/apps/display/Cargo.toml b/apps/display/Cargo.toml index c24e197..7c68b01 100644 --- a/apps/display/Cargo.toml +++ b/apps/display/Cargo.toml @@ -7,5 +7,5 @@ authors = ["Shiping Yuan "] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -axstd = { git = "https://github.com/Starry-OS/axstd.git", features = ["display"], optional = true } +axstd = { workspace = true, features = ["display"], optional = true } embedded-graphics = "0.8" diff --git a/apps/exception/Cargo.toml b/apps/exception/Cargo.toml index d973de2..7211cd1 100644 --- a/apps/exception/Cargo.toml +++ b/apps/exception/Cargo.toml @@ -7,4 +7,4 @@ authors = ["Yuekai Jia "] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -axstd = { git = "https://github.com/Starry-OS/axstd.git", optional = true } +axstd = { workspace = true, optional = true } diff --git a/apps/fs/shell/Cargo.toml b/apps/fs/shell/Cargo.toml index 56ce651..9d6bc05 100644 --- a/apps/fs/shell/Cargo.toml +++ b/apps/fs/shell/Cargo.toml @@ -14,4 +14,4 @@ default = [] axfs_vfs = { git = "https://github.com/Starry-OS/axfs_vfs.git", optional = true } axfs_ramfs = { git = "https://github.com/Starry-OS/axfs_ramfs.git", optional = true } crate_interface = { git = "https://github.com/Starry-OS/crate_interface.git", optional = true } -axstd = { git = "https://github.com/Starry-OS/axstd.git", features = ["alloc", "fs"], optional = true } +axstd = { workspace = true, features = ["alloc", "fs"], optional = true } diff --git a/apps/helloworld/Cargo.toml b/apps/helloworld/Cargo.toml index d758f2d..67f5f35 100644 --- a/apps/helloworld/Cargo.toml +++ b/apps/helloworld/Cargo.toml @@ -7,4 +7,4 @@ authors = ["Yuekai Jia "] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -axstd = { git = "https://github.com/Starry-OS/axstd.git", optional = true } +axstd = { workspace = true, optional = true } diff --git a/apps/memtest/Cargo.toml b/apps/memtest/Cargo.toml index 2e3f93e..f782517 100644 --- a/apps/memtest/Cargo.toml +++ b/apps/memtest/Cargo.toml @@ -8,4 +8,4 @@ authors = ["Yuekai Jia "] [dependencies] rand = { version = "0.8", default-features = false, features = ["small_rng"] } -axstd = { git = "https://github.com/Starry-OS/axstd.git", features = ["alloc"], optional = true } +axstd = { workspace = true, features = ["alloc"], optional = true } diff --git a/apps/monolithic_userboot/Cargo.toml b/apps/monolithic_userboot/Cargo.toml index 057ad78..6fec898 100644 --- a/apps/monolithic_userboot/Cargo.toml +++ b/apps/monolithic_userboot/Cargo.toml @@ -11,4 +11,4 @@ authors = ["Youjie Zheng "] batch = [] [dependencies] -axstarry = { git = "https://github.com/Starry-OS/axstarry.git" } +axstarry = { workspace = true } diff --git a/apps/net/bwbench/Cargo.toml b/apps/net/bwbench/Cargo.toml index 1914c6f..95e3c2e 100644 --- a/apps/net/bwbench/Cargo.toml +++ b/apps/net/bwbench/Cargo.toml @@ -7,5 +7,5 @@ authors = ["ChengXiang Qi "] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -axstd = { git = "https://github.com/Starry-OS/axstd.git", features = ["net"] } -axnet = { git = "https://github.com/Starry-OS/axnet.git" } \ No newline at end of file +axstd = { workspace = true, features = ["net"] } +axnet = { workspace = true } \ No newline at end of file diff --git a/apps/net/echoserver/Cargo.toml b/apps/net/echoserver/Cargo.toml index 62d12a3..c346ced 100644 --- a/apps/net/echoserver/Cargo.toml +++ b/apps/net/echoserver/Cargo.toml @@ -7,4 +7,4 @@ authors = ["Yuekai Jia "] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -axstd = { git = "https://github.com/Starry-OS/axstd.git", features = ["alloc", "multitask", "net"], optional = true } +axstd = { workspace = true, features = ["alloc", "multitask", "net"], optional = true } diff --git a/apps/net/httpclient/Cargo.toml b/apps/net/httpclient/Cargo.toml index 8027f01..bce4bc0 100644 --- a/apps/net/httpclient/Cargo.toml +++ b/apps/net/httpclient/Cargo.toml @@ -7,7 +7,7 @@ authors = ["Yuekai Jia ", "Dashuai Wu "] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -axstd = { git = "https://github.com/Starry-OS/axstd.git", features = ["alloc", "multitask", "net"], optional = true } +axstd = { workspace = true, features = ["alloc", "multitask", "net"], optional = true } diff --git a/apps/net/udpserver/Cargo.toml b/apps/net/udpserver/Cargo.toml index 9068162..1d34111 100644 --- a/apps/net/udpserver/Cargo.toml +++ b/apps/net/udpserver/Cargo.toml @@ -5,4 +5,4 @@ edition = "2021" authors = ["Dashuai Wu "] [dependencies] -axstd = { git = "https://github.com/Starry-OS/axstd.git", features = ["net"], optional = true } +axstd = { workspace = true, features = ["net"], optional = true } diff --git a/apps/task/parallel/Cargo.toml b/apps/task/parallel/Cargo.toml index 668f462..637315c 100644 --- a/apps/task/parallel/Cargo.toml +++ b/apps/task/parallel/Cargo.toml @@ -8,4 +8,4 @@ authors = ["Yuekai Jia "] [dependencies] rand = { version = "0.8", default-features = false, features = ["small_rng"] } -axstd = { git = "https://github.com/Starry-OS/axstd.git", features = ["alloc", "multitask", "irq"], optional = true } +axstd = { workspace = true, features = ["alloc", "multitask", "irq"], optional = true } diff --git a/apps/task/priority/Cargo.toml b/apps/task/priority/Cargo.toml index d5c166f..5d66ef6 100644 --- a/apps/task/priority/Cargo.toml +++ b/apps/task/priority/Cargo.toml @@ -11,4 +11,4 @@ sched_rr = ["axstd?/sched_rr"] sched_cfs = ["axstd?/sched_cfs"] [dependencies] -axstd = { git = "https://github.com/Starry-OS/axstd.git", features = ["alloc", "multitask"], optional = true } +axstd = { workspace = true, features = ["alloc", "multitask"], optional = true } diff --git a/apps/task/sleep/Cargo.toml b/apps/task/sleep/Cargo.toml index 2512aca..24111e5 100644 --- a/apps/task/sleep/Cargo.toml +++ b/apps/task/sleep/Cargo.toml @@ -7,4 +7,4 @@ authors = ["Yuekai Jia "] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -axstd = { git = "https://github.com/Starry-OS/axstd.git", features = ["multitask", "irq"], optional = true } +axstd = { workspace = true, features = ["multitask", "irq"], optional = true } diff --git a/apps/task/tls/Cargo.toml b/apps/task/tls/Cargo.toml index b2638e0..6f4a68e 100644 --- a/apps/task/tls/Cargo.toml +++ b/apps/task/tls/Cargo.toml @@ -7,5 +7,5 @@ authors = ["Yuekai Jia "] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -axstd = { git = "https://github.com/Starry-OS/axstd.git", features = ["tls", "alloc", "multitask"], optional = true } -axhal = { git = "https://github.com/Starry-OS/axhal.git", features = ["tls"] } \ No newline at end of file +axstd = { workspace = true, features = ["tls", "alloc", "multitask"], optional = true } +axhal = { workspace = true, features = ["tls"] } \ No newline at end of file diff --git a/apps/task/yield/Cargo.toml b/apps/task/yield/Cargo.toml index cd3e8c2..3b27528 100644 --- a/apps/task/yield/Cargo.toml +++ b/apps/task/yield/Cargo.toml @@ -11,4 +11,4 @@ sched_rr = ["axstd?/sched_rr"] sched_cfs = ["axstd?/sched_cfs"] [dependencies] -axstd = { git = "https://github.com/Starry-OS/axstd.git", features = ["multitask"], optional = true } +axstd = { workspace = true, features = ["multitask"], optional = true } diff --git a/log b/log new file mode 100644 index 0000000..e69de29 diff --git a/modules/arch_boot b/modules/arch_boot new file mode 160000 index 0000000..dc98dfc --- /dev/null +++ b/modules/arch_boot @@ -0,0 +1 @@ +Subproject commit dc98dfc8e6374dead21a78bc982e898595da6284 diff --git a/modules/axalloc/.gitignore b/modules/axalloc/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/modules/axalloc/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/modules/axalloc/Cargo.toml b/modules/axalloc/Cargo.toml new file mode 100644 index 0000000..ecdc597 --- /dev/null +++ b/modules/axalloc/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "axalloc" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "ArceOS global memory allocator" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axalloc" +documentation = "https://rcore-os.github.io/arceos/axalloc/index.html" +keywords = ["Starry"] + +[features] +default = ["tlsf"] +tlsf = ["allocator/tlsf"] +slab = ["allocator/slab"] +buddy = ["allocator/buddy"] + +[dependencies] +log = "0.4" +cfg-if = "1.0" +spinlock = { git = "https://github.com/Starry-OS/spinlock.git" } +memory_addr = { git = "https://github.com/Starry-OS/memory_addr.git" } +allocator = { git = "https://github.com/Starry-OS/allocator.git", features = ["bitmap"] } +axerrno = { git = "https://github.com/Starry-OS/axerrno.git" } diff --git a/modules/axalloc/src/lib.rs b/modules/axalloc/src/lib.rs new file mode 100644 index 0000000..5150b60 --- /dev/null +++ b/modules/axalloc/src/lib.rs @@ -0,0 +1,233 @@ +//! [ArceOS](https://github.com/rcore-os/arceos) global memory allocator. +//! +//! It provides [`GlobalAllocator`], which implements the trait +//! [`core::alloc::GlobalAlloc`]. A static global variable of type +//! [`GlobalAllocator`] is defined with the `#[global_allocator]` attribute, to +//! be registered as the standard library’s default allocator. + +#![no_std] + +#[macro_use] +extern crate log; +extern crate alloc; + +mod page; +use allocator::{AllocResult, BaseAllocator, BitmapPageAllocator, ByteAllocator, PageAllocator}; +use core::alloc::{GlobalAlloc, Layout}; +use core::ptr::NonNull; +pub use page::PhysPage; +use spinlock::SpinNoIrq; + +const PAGE_SIZE: usize = 0x1000; +const MIN_HEAP_SIZE: usize = 0x8000; // 32 K + +pub use page::GlobalPage; + +cfg_if::cfg_if! { + if #[cfg(feature = "slab")] { + use allocator::SlabByteAllocator as DefaultByteAllocator; + } else if #[cfg(feature = "buddy")] { + use allocator::BuddyByteAllocator as DefaultByteAllocator; + } else if #[cfg(feature = "tlsf")] { + use allocator::TlsfByteAllocator as DefaultByteAllocator; + } +} + +/// The global allocator used by ArceOS. +/// +/// It combines a [`ByteAllocator`] and a [`PageAllocator`] into a simple +/// two-level allocator: firstly tries allocate from the byte allocator, if +/// there is no memory, asks the page allocator for more memory and adds it to +/// the byte allocator. +/// +/// Currently, [`TlsfByteAllocator`] is used as the byte allocator, while +/// [`BitmapPageAllocator`] is used as the page allocator. +/// +/// [`TlsfByteAllocator`]: allocator::TlsfByteAllocator +pub struct GlobalAllocator { + balloc: SpinNoIrq, + palloc: SpinNoIrq>, +} + +impl GlobalAllocator { + /// Creates an empty [`GlobalAllocator`]. + pub const fn new() -> Self { + Self { + balloc: SpinNoIrq::new(DefaultByteAllocator::new()), + palloc: SpinNoIrq::new(BitmapPageAllocator::new()), + } + } + + /// Returns the name of the allocator. + pub const fn name(&self) -> &'static str { + cfg_if::cfg_if! { + if #[cfg(feature = "slab")] { + "slab" + } else if #[cfg(feature = "buddy")] { + "buddy" + } else if #[cfg(feature = "tlsf")] { + "TLSF" + } + } + } + + /// Initializes the allocator with the given region. + /// + /// It firstly adds the whole region to the page allocator, then allocates + /// a small region (32 KB) to initialize the byte allocator. Therefore, + /// the given region must be larger than 32 KB. + pub fn init(&self, start_vaddr: usize, size: usize) { + assert!(size > MIN_HEAP_SIZE); + let init_heap_size = MIN_HEAP_SIZE; + self.palloc.lock().init(start_vaddr, size); + let heap_ptr = self + .alloc_pages(init_heap_size / PAGE_SIZE, PAGE_SIZE) + .unwrap(); + self.balloc.lock().init(heap_ptr, init_heap_size); + } + + /// Add the given region to the allocator. + /// + /// It will add the whole region to the byte allocator. + pub fn add_memory(&self, start_vaddr: usize, size: usize) -> AllocResult { + self.balloc.lock().add_memory(start_vaddr, size) + } + + /// Allocate arbitrary number of bytes. Returns the left bound of the + /// allocated region. + /// + /// It firstly tries to allocate from the byte allocator. If there is no + /// memory, it asks the page allocator for more memory and adds it to the + /// byte allocator. + /// + /// `align_pow2` must be a power of 2, and the returned region bound will be + /// aligned to it. + pub fn alloc(&self, layout: Layout) -> AllocResult> { + // simple two-level allocator: if no heap memory, allocate from the page allocator. + let mut balloc = self.balloc.lock(); + loop { + if let Ok(ptr) = balloc.alloc(layout) { + return Ok(ptr); + } else { + let old_size = balloc.total_bytes(); + let expand_size = old_size + .max(layout.size()) + .next_power_of_two() + .max(PAGE_SIZE); + let heap_ptr = self.alloc_pages(expand_size / PAGE_SIZE, PAGE_SIZE)?; + debug!( + "expand heap memory: [{:#x}, {:#x})", + heap_ptr, + heap_ptr + expand_size + ); + balloc.add_memory(heap_ptr, expand_size)?; + } + } + } + + /// Gives back the allocated region to the byte allocator. + /// + /// The region should be allocated by [`alloc`], and `align_pow2` should be + /// the same as the one used in [`alloc`]. Otherwise, the behavior is + /// undefined. + /// + /// [`alloc`]: GlobalAllocator::alloc + pub fn dealloc(&self, pos: NonNull, layout: Layout) { + self.balloc.lock().dealloc(pos, layout) + } + + /// Allocates contiguous pages. + /// + /// It allocates `num_pages` pages from the page allocator. + /// + /// `align_pow2` must be a power of 2, and the returned region bound will be + /// aligned to it. + pub fn alloc_pages(&self, num_pages: usize, align_pow2: usize) -> AllocResult { + self.palloc.lock().alloc_pages(num_pages, align_pow2) + } + + /// Gives back the allocated pages starts from `pos` to the page allocator. + /// + /// The pages should be allocated by [`alloc_pages`], and `align_pow2` + /// should be the same as the one used in [`alloc_pages`]. Otherwise, the + /// behavior is undefined. + /// + /// [`alloc_pages`]: GlobalAllocator::alloc_pages + pub fn dealloc_pages(&self, pos: usize, num_pages: usize) { + self.palloc.lock().dealloc_pages(pos, num_pages) + } + + /// Returns the number of allocated bytes in the byte allocator. + pub fn used_bytes(&self) -> usize { + self.balloc.lock().used_bytes() + } + + /// Returns the number of available bytes in the byte allocator. + pub fn available_bytes(&self) -> usize { + self.balloc.lock().available_bytes() + } + + /// Returns the number of allocated pages in the page allocator. + pub fn used_pages(&self) -> usize { + self.palloc.lock().used_pages() + } + + /// Returns the number of available pages in the page allocator. + pub fn available_pages(&self) -> usize { + self.palloc.lock().available_pages() + } +} + +unsafe impl GlobalAlloc for GlobalAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + if let Ok(ptr) = GlobalAllocator::alloc(self, layout) { + ptr.as_ptr() + } else { + alloc::alloc::handle_alloc_error(layout) + } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + GlobalAllocator::dealloc(self, NonNull::new(ptr).expect("dealloc null ptr"), layout) + } +} + +#[cfg_attr(all(target_os = "none", not(test)), global_allocator)] +static GLOBAL_ALLOCATOR: GlobalAllocator = GlobalAllocator::new(); + +/// Returns the reference to the global allocator. +pub fn global_allocator() -> &'static GlobalAllocator { + &GLOBAL_ALLOCATOR +} + +/// Initializes the global allocator with the given memory region. +/// +/// Note that the memory region bounds are just numbers, and the allocator +/// does not actually access the region. Users should ensure that the region +/// is valid and not being used by others, so that the allocated memory is also +/// valid. +/// +/// This function should be called only once, and before any allocation. +pub fn global_init(start_vaddr: usize, size: usize) { + debug!( + "initialize global allocator at: [{:#x}, {:#x})", + start_vaddr, + start_vaddr + size + ); + GLOBAL_ALLOCATOR.init(start_vaddr, size); +} + +/// Add the given memory region to the global allocator. +/// +/// Users should ensure that the region is valid and not being used by others, +/// so that the allocated memory is also valid. +/// +/// It's similar to [`global_init`], but can be called multiple times. +pub fn global_add_memory(start_vaddr: usize, size: usize) -> AllocResult { + debug!( + "add a memory region to global allocator: [{:#x}, {:#x})", + start_vaddr, + start_vaddr + size + ); + GLOBAL_ALLOCATOR.add_memory(start_vaddr, size) +} diff --git a/modules/axalloc/src/page.rs b/modules/axalloc/src/page.rs new file mode 100644 index 0000000..e1abaae --- /dev/null +++ b/modules/axalloc/src/page.rs @@ -0,0 +1,190 @@ +use allocator::AllocError; +use axerrno::{AxError, AxResult}; +use memory_addr::{PhysAddr, VirtAddr}; + +use crate::{global_allocator, PAGE_SIZE}; + +extern crate alloc; +use alloc::vec::Vec; + +/// A RAII wrapper of contiguous 4K-sized pages. +/// +/// It will automatically deallocate the pages when dropped. +#[derive(Debug)] +pub struct GlobalPage { + start_vaddr: VirtAddr, + num_pages: usize, +} + +impl GlobalPage { + /// Allocate one 4K-sized page. + pub fn alloc() -> AxResult { + global_allocator() + .alloc_pages(1, PAGE_SIZE) + .map(|vaddr| Self { + start_vaddr: vaddr.into(), + num_pages: 1, + }) + .map_err(alloc_err_to_ax_err) + } + + /// Allocate one 4K-sized page and fill with zero. + pub fn alloc_zero() -> AxResult { + let mut p = Self::alloc()?; + p.zero(); + Ok(p) + } + + /// Allocate contiguous 4K-sized pages. + pub fn alloc_contiguous(num_pages: usize, align_pow2: usize) -> AxResult { + global_allocator() + .alloc_pages(num_pages, align_pow2) + .map(|vaddr| Self { + start_vaddr: vaddr.into(), + num_pages, + }) + .map_err(alloc_err_to_ax_err) + } + + /// Get the start virtual address of this page. + pub fn start_vaddr(&self) -> VirtAddr { + self.start_vaddr + } + + /// Get the start physical address of this page. + pub fn start_paddr(&self, virt_to_phys: F) -> PhysAddr + where + F: FnOnce(VirtAddr) -> PhysAddr, + { + virt_to_phys(self.start_vaddr) + } + + /// Get the total size (in bytes) of these page(s). + pub fn size(&self) -> usize { + self.num_pages * PAGE_SIZE + } + + /// Convert to a raw pointer. + pub fn as_ptr(&self) -> *const u8 { + self.start_vaddr.as_ptr() + } + + /// Convert to a mutable raw pointer. + pub fn as_mut_ptr(&mut self) -> *mut u8 { + self.start_vaddr.as_mut_ptr() + } + + /// Fill `self` with `byte`. + pub fn fill(&mut self, byte: u8) { + unsafe { core::ptr::write_bytes(self.as_mut_ptr(), byte, self.size()) } + } + + /// Fill `self` with zero. + pub fn zero(&mut self) { + self.fill(0) + } + + /// Forms a slice that can read data. + pub fn as_slice(&self) -> &[u8] { + unsafe { core::slice::from_raw_parts(self.as_ptr(), self.size()) } + } + + /// Forms a mutable slice that can write data. + pub fn as_slice_mut(&mut self) -> &mut [u8] { + unsafe { core::slice::from_raw_parts_mut(self.as_mut_ptr(), self.size()) } + } +} + +impl Drop for GlobalPage { + fn drop(&mut self) { + global_allocator().dealloc_pages(self.start_vaddr.into(), self.num_pages); + } +} + +const fn alloc_err_to_ax_err(e: AllocError) -> AxError { + match e { + AllocError::InvalidParam | AllocError::MemoryOverlap | AllocError::NotAllocated => { + AxError::InvalidInput + } + AllocError::NoMemory => AxError::NoMemory, + } +} + +/// A safe wrapper of a single 4K page. +/// It holds the page's VirtAddr (PhysAddr + offset) +#[derive(Debug)] +pub struct PhysPage { + /// The start virtual address of this page. + pub start_vaddr: VirtAddr, +} + +impl PhysPage { + /// Allocate one 4K-sized page. + pub fn alloc() -> AxResult { + global_allocator() + .alloc_pages(1, PAGE_SIZE) + .map(|vaddr| Self { + start_vaddr: vaddr.into(), + }) + .map_err(alloc_err_to_ax_err) + } + + /// Allocate some 4K-sized pages and fill with zero. + pub fn alloc_contiguous( + num_pages: usize, + align_pow2: usize, + data: Option<&[u8]>, + ) -> AxResult>> { + global_allocator() + .alloc_pages(num_pages, align_pow2) + .map(|vaddr| { + let pages = unsafe { + core::slice::from_raw_parts_mut(vaddr as *mut u8, num_pages * PAGE_SIZE) + }; + pages.fill(0); + if let Some(data) = data { + pages[..data.len()].copy_from_slice(data); + } + + (0..num_pages) + .map(|page_idx| { + Some(PhysPage { + start_vaddr: (vaddr + page_idx * PAGE_SIZE).into(), + }) + }) + .collect() + }) + .map_err(alloc_err_to_ax_err) + } + + /// 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() + } + + /// Forms a slice that can read data. + pub fn as_slice(&self) -> &[u8] { + unsafe { core::slice::from_raw_parts(self.as_ptr(), PAGE_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(), PAGE_SIZE) } + } + + /// Fill `self` with `byte`. + pub fn fill(&mut self, byte: u8) { + unsafe { core::ptr::write_bytes(self.as_mut_ptr(), byte, PAGE_SIZE) } + } +} + +impl Drop for PhysPage { + fn drop(&mut self) { + global_allocator().dealloc_pages(self.start_vaddr.into(), 1); + } +} diff --git a/modules/axconfig/.gitignore b/modules/axconfig/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/modules/axconfig/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/modules/axconfig/Cargo.toml b/modules/axconfig/Cargo.toml new file mode 100644 index 0000000..9c231f9 --- /dev/null +++ b/modules/axconfig/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "axconfig" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Platform-specific constants and parameters for ArceOS" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axconfig" +documentation = "https://rcore-os.github.io/arceos/axconfig/index.html" +keywords = ["Starry"] + +[build-dependencies] +toml_edit = "0.22" +serde = "1.0" diff --git a/modules/axconfig/build.rs b/modules/axconfig/build.rs new file mode 100644 index 0000000..091ff08 --- /dev/null +++ b/modules/axconfig/build.rs @@ -0,0 +1,174 @@ +use std::io::{Result, Write}; +use std::path::{Path, PathBuf}; +use toml_edit::{Decor, DocumentMut, Item, Table, Value}; + +fn resolve_config_path(platform: Option<&str>) -> Result { + if platform.is_none() || platform == Some("") { + return Ok(Path::new("defconfig.toml").into()); + } + let root_dir = PathBuf::from(option_env!("AX_WORK_DIR").unwrap_or(".")); + let config_dir = root_dir.join("platforms"); + + let builtin_platforms = std::fs::read_dir(&config_dir)? + .filter_map(|e| { + e.unwrap() + .file_name() + .to_str()? + .strip_suffix(".toml") + .map(String::from) + }) + .collect::>(); + + let path = match platform { + None | Some("") => "defconfig.toml".into(), + Some(plat) if builtin_platforms.contains(&plat.to_string()) => { + config_dir.join(format!("{plat}.toml")) + } + Some(plat) => { + let path = PathBuf::from(&plat); + if path.is_absolute() { + path + } else { + root_dir.join(plat) + } + } + }; + + Ok(path) +} + +fn get_comments<'a>(config: &'a Table, key: &str) -> Option<&'a str> { + config + .key(key) + .and_then(|k| k.leaf_decor().prefix()) + .and_then(|s| s.as_str()) + .map(|s| s.trim()) +} + +fn add_config(config: &mut Table, key: &str, item: Item, comments: Option<&str>) { + config.insert(key, item); + if let Some(comm) = comments { + if let Some(mut dst) = config.key_mut(key) { + *dst.leaf_decor_mut() = Decor::new(comm, ""); + } + } +} + +fn load_config_toml(config_path: &Path) -> Result { + let config_content = std::fs::read_to_string(config_path)?; + let toml = config_content + .parse::() + .expect("failed to parse config file") + .as_table() + .clone(); + Ok(toml) +} + +fn gen_config_rs(config_path: &Path) -> Result> { + fn is_num(s: &str) -> bool { + let s = s.replace('_', ""); + if s.parse::().is_ok() { + true + } else if let Some(s) = s.strip_prefix("0x") { + usize::from_str_radix(s, 16).is_ok() + } else { + false + } + } + + // Load TOML config file + let mut config = if config_path == Path::new("defconfig.toml") { + load_config_toml(config_path)? + } else { + // Set default values for missing items + let defconfig = load_config_toml(Path::new("defconfig.toml"))?; + let mut config = load_config_toml(config_path)?; + + for (key, item) in defconfig.iter() { + if !config.contains_key(key) { + add_config( + &mut config, + key, + item.clone(), + get_comments(&defconfig, key), + ); + } + } + config + }; + + add_config( + &mut config, + "smp", + toml_edit::value(std::env::var("AX_SMP").unwrap_or("1".into())), + Some("# Number of CPUs"), + ); + + // Generate config.rs + let mut output = Vec::new(); + writeln!( + output, + "// Platform constants and parameters for {}.", + config["platform"].as_str().unwrap(), + )?; + writeln!(output, "// Generated by build.rs, DO NOT edit!\n")?; + + for (key, item) in config.iter() { + let var_name = key.to_uppercase().replace('-', "_"); + if let Item::Value(value) = item { + let comments = get_comments(&config, key) + .unwrap_or_default() + .replace('#', "///"); + match value { + Value::String(s) => { + writeln!(output, "{comments}")?; + let s = s.value(); + if is_num(s) { + writeln!(output, "pub const {var_name}: usize = {s};")?; + } else { + writeln!(output, "pub const {var_name}: &str = \"{s}\";")?; + } + } + Value::Array(regions) => { + if key != "mmio-regions" && key != "virtio-mmio-regions" && key != "pci-ranges" + { + continue; + } + writeln!(output, "{comments}")?; + writeln!(output, "pub const {var_name}: &[(usize, usize)] = &[")?; + for r in regions.iter() { + let r = r.as_array().unwrap(); + writeln!( + output, + " ({}, {}),", + r.get(0).unwrap().as_str().unwrap(), + r.get(1).unwrap().as_str().unwrap() + )?; + } + writeln!(output, "];")?; + } + _ => {} + } + } + } + + Ok(output) +} + +fn main() -> Result<()> { + let platform = option_env!("AX_PLATFORM"); + let config_path = resolve_config_path(platform)?; + + println!("Reading config file: {:?}", config_path); + let config_rs = gen_config_rs(&config_path)?; + + let out_dir = std::env::var("OUT_DIR").unwrap(); + let out_path = Path::new(&out_dir).join("config.rs"); + println!("Generating config file: {}", out_path.display()); + std::fs::write(out_path, config_rs)?; + + println!("cargo:rerun-if-changed={}", config_path.display()); + println!("cargo:rerun-if-env-changed=AX_PLATFORM"); + println!("cargo:rerun-if-env-changed=AX_SMP"); + Ok(()) +} diff --git a/modules/axconfig/defconfig.toml b/modules/axconfig/defconfig.toml new file mode 100644 index 0000000..cf98d89 --- /dev/null +++ b/modules/axconfig/defconfig.toml @@ -0,0 +1,57 @@ +# Architecture identifier. +arch = "unknown" +# Platform identifier. +platform = "dummy" +# Platform family. +family = "dummy" + +# Base address of the whole physical memory. +phys-memory-base = "0" +# Size of the whole physical memory. +phys-memory-size = "0" +# Base physical address of the kernel image. +kernel-base-paddr = "0" +# Base virtual address of the kernel image. +kernel-base-vaddr = "0" + +# Linear mapping offset, for quick conversions between physical and virtual +# addresses. +phys-virt-offset = "0" +# MMIO regions with format (`base_paddr`, `size`). +mmio-regions = [] +# VirtIO MMIO regions with format (`base_paddr`, `size`). +virtio-mmio-regions = [] +# Base physical address of the PCIe ECAM space. +pci-ecam-base = "0" +# End PCI bus number. +pci-bus-end = "0" +# PCI device memory ranges. +pci-ranges = [] + +# Timer interrupt frequency in Hz. +timer-frequency = "0" + +# Stack size of each task. +task-stack-size = "0x40000" # 256 K + +# Number of timer ticks per second (Hz). A timer tick may contain several timer +# interrupts. +ticks-per-sec = "100" + +# Number of CPUs +smp = "1" + +# Testcase memory start address. +testcase-memory-start = "0" +# Testcase memory size. +testcase-memory-size = "0" +# The base address of the user heap. +user-heap-base = "0" +# The base address of the user stack. And the stack bottom is `user-stack-top + max-user-stack-size`. +user-stack-top = "0" +# The size of the user heap. +max-user-heap-size = "0" +# The size of the user stack. +max-user-stack-size = "0" +# The base address of the signal trampoline. +signal-trampoline = "0" \ No newline at end of file diff --git a/modules/axconfig/src/config_dummy.rs b/modules/axconfig/src/config_dummy.rs new file mode 100644 index 0000000..9938254 --- /dev/null +++ b/modules/axconfig/src/config_dummy.rs @@ -0,0 +1,40 @@ +//! Platform constants and parameters for dummy. +//! Generated by build.rs, DO NOT edit! + +/// Stack size of each task. +pub const TASK_STACK_SIZE: usize = 0x40000; +/// Number of timer ticks per second (Hz). A timer tick may contain several timer +/// interrupts. +pub const TICKS_PER_SEC: usize = 100; +/// Architecture identifier. +pub const ARCH: &str = "unknown"; +/// Platform identifier. +pub const PLATFORM: &str = "dummy"; +/// Base address of the whole physical memory. +pub const PHYS_MEMORY_BASE: usize = 0; +/// Size of the whole physical memory. +pub const PHYS_MEMORY_SIZE: usize = 0; +/// Base physical address of the kernel image. +pub const KERNEL_BASE_PADDR: usize = 0; +/// Base virtual address of the kernel image. +pub const KERNEL_BASE_VADDR: usize = 0; +/// Linear mapping offset, for quick conversions between physical and virtual +/// addresses. +pub const PHYS_VIRT_OFFSET: usize = 0; +/// MMIO regions with format (`base_paddr`, `size`). +pub const MMIO_REGIONS: &[(usize, usize)] = &[ +]; +/// VirtIO MMIO regions with format (`base_paddr`, `size`). +pub const VIRTIO_MMIO_REGIONS: &[(usize, usize)] = &[ +]; +/// Base physical address of the PCIe ECAM space. +pub const PCI_ECAM_BASE: usize = 0; +/// End PCI bus number. +pub const PCI_BUS_END: usize = 0; +/// PCI device memory ranges. +pub const PCI_RANGES: &[(usize, usize)] = &[ +]; +/// Timer interrupt frequency in Hz. +pub const TIMER_FREQUENCY: usize = 0; +/// Number of CPUs +pub const SMP: usize = 1; diff --git a/modules/axconfig/src/config_pc_x86.rs b/modules/axconfig/src/config_pc_x86.rs new file mode 100644 index 0000000..1798641 --- /dev/null +++ b/modules/axconfig/src/config_pc_x86.rs @@ -0,0 +1,45 @@ +//! Platform constants and parameters for pc-x86. +//! Generated by build.rs, DO NOT edit! + +/// Stack size of each task. +pub const TASK_STACK_SIZE: usize = 0x40000; +/// Number of timer ticks per second (Hz). A timer tick may contain several timer +/// interrupts. +pub const TICKS_PER_SEC: usize = 100; +/// Architecture identifier. +pub const ARCH: &str = "x86_64"; +/// Platform identifier. +pub const PLATFORM: &str = "pc-x86"; +/// Base address of the whole physical memory. +pub const PHYS_MEMORY_BASE: usize = 0; +/// Size of the whole physical memory. +pub const PHYS_MEMORY_SIZE: usize = 0x800_0000; +/// Base physical address of the kernel image. +pub const KERNEL_BASE_PADDR: usize = 0x20_0000; +/// Base virtual address of the kernel image. +pub const KERNEL_BASE_VADDR: usize = 0xffff_ff80_0020_0000; +/// Linear mapping offset, for quick conversions between physical and virtual +/// addresses. +pub const PHYS_VIRT_OFFSET: usize = 0xffff_ff80_0000_0000; +/// MMIO regions with format (`base_paddr`, `size`). +pub const MMIO_REGIONS: &[(usize, usize)] = &[ + (0xb000_0000, 0x1000_0000), + (0xfe00_0000, 0xc0_0000), + (0xfec0_0000, 0x1000), + (0xfed0_0000, 0x1000), + (0xfee0_0000, 0x1000), +]; +/// VirtIO MMIO regions with format (`base_paddr`, `size`). +pub const VIRTIO_MMIO_REGIONS: &[(usize, usize)] = &[ +]; +/// Base physical address of the PCIe ECAM space (should read from ACPI 'MCFG' table). +pub const PCI_ECAM_BASE: usize = 0xb000_0000; +/// End PCI bus number. +pub const PCI_BUS_END: usize = 0xff; +/// PCI device memory ranges (not used on x86). +pub const PCI_RANGES: &[(usize, usize)] = &[ +]; +/// Timer interrupt frequencyin Hz. +pub const TIMER_FREQUENCY: usize = 4_000_000_000; +/// Number of CPUs +pub const SMP: usize = 1; diff --git a/modules/axconfig/src/config_phytiumpi_aarch64.rs b/modules/axconfig/src/config_phytiumpi_aarch64.rs new file mode 100644 index 0000000..c76ed84 --- /dev/null +++ b/modules/axconfig/src/config_phytiumpi_aarch64.rs @@ -0,0 +1,48 @@ +//! Platform constants and parameters for raspi4-aarch64. +//! Generated by build.rs, DO NOT edit! + +/// Stack size of each task. +pub const TASK_STACK_SIZE: usize = 0x40000; +/// Number of timer ticks per second (Hz). A timer tick may contain several timer +/// interrupts. +pub const TICKS_PER_SEC: usize = 100; +/// Architecture identifier. +pub const ARCH: &str = "aarch64"; +/// Platform identifier. +pub const PLATFORM: &str = "phytiumpi-aarch64"; +/// Base address of the whole physical memory. +pub const PHYS_MEMORY_BASE: usize = 0x8000_0000; +/// Size of the whole physical memory. +pub const PHYS_MEMORY_SIZE: usize = 0x8000_0000; +/// Base physical address of the kernel image. +pub const KERNEL_BASE_PADDR: usize = 0x9010_0000; +/// Base virtual address of the kernel image. +pub const KERNEL_BASE_VADDR: usize = 0xffff_0000_9010_0000; +/// Linear mapping offset, for quick conversions between physical and virtual +/// addresses. +pub const PHYS_VIRT_OFFSET: usize = 0xffff_0000_0000_0000; +/// MMIO regions with format (`base_paddr`, `size`). +pub const MMIO_REGIONS: &[(usize, usize)] = &[ + (0x2800_C000, 0x1000), + (0x2800_D000, 0x1000), + (0x2800_E000, 0x1000), + (0x2800_F000, 0x1000), + (0xFE20_1000, 0x1000), + (0xFF84_1000, 0x8000), + (0x3000_0000,0x800_0000), + (0x4000_0000,0x4000_0000), + (0x10_0000_0000, 0x20_0000_0000), +]; + +pub const VIRTIO_MMIO_REGIONS: &[(usize, usize)] = &[ +]; +/// UART Address +pub const UART_PADDR: usize = 0x2800_D000; + +pub const UART_IRQ_NUM: usize = 24; +/// GIC Address +pub const GICC_PADDR: usize = 0xFF84_2000; + +pub const GICD_PADDR: usize = 0xFF84_1000; +/// Number of CPUs +pub const SMP: usize = 1; diff --git a/modules/axconfig/src/config_qemu_virt_aarch64.rs b/modules/axconfig/src/config_qemu_virt_aarch64.rs new file mode 100644 index 0000000..aabcee0 --- /dev/null +++ b/modules/axconfig/src/config_qemu_virt_aarch64.rs @@ -0,0 +1,86 @@ +//! Platform constants and parameters for qemu-virt-aarch64. +//! Generated by build.rs, DO NOT edit! + +/// Stack size of each task. +pub const TASK_STACK_SIZE: usize = 0x40000; +/// Number of timer ticks per second (Hz). A timer tick may contain several timer +/// interrupts. +pub const TICKS_PER_SEC: usize = 100; +/// Architecture identifier. +pub const ARCH: &str = "aarch64"; +/// Platform identifier. +pub const PLATFORM: &str = "qemu-virt-aarch64"; +/// Base address of the whole physical memory. +pub const PHYS_MEMORY_BASE: usize = 0x4000_0000; +/// Size of the whole physical memory. +pub const PHYS_MEMORY_SIZE: usize = 0x800_0000; +/// Base physical address of the kernel image. +pub const KERNEL_BASE_PADDR: usize = 0x4008_0000; +/// Base virtual address of the kernel image. +pub const KERNEL_BASE_VADDR: usize = 0xffff_0000_4008_0000; +/// Linear mapping offset, for quick conversions between physical and virtual +/// addresses. +pub const PHYS_VIRT_OFFSET: usize = 0xffff_0000_0000_0000; +/// MMIO regions with format (`base_paddr`, `size`). +pub const MMIO_REGIONS: &[(usize, usize)] = &[ + (0x0900_0000, 0x1000), + (0x0800_0000, 0x2_0000), + (0x0a00_0000, 0x4000), + (0x1000_0000, 0x2eff_0000), + (0x40_1000_0000, 0x1000_0000), +]; +/// VirtIO MMIO regions with format (`base_paddr`, `size`). +pub const VIRTIO_MMIO_REGIONS: &[(usize, usize)] = &[ + (0x0a00_0000, 0x200), + (0x0a00_0200, 0x200), + (0x0a00_0400, 0x200), + (0x0a00_0600, 0x200), + (0x0a00_0800, 0x200), + (0x0a00_0a00, 0x200), + (0x0a00_0c00, 0x200), + (0x0a00_0e00, 0x200), + (0x0a00_1000, 0x200), + (0x0a00_1200, 0x200), + (0x0a00_1400, 0x200), + (0x0a00_1600, 0x200), + (0x0a00_1800, 0x200), + (0x0a00_1a00, 0x200), + (0x0a00_1c00, 0x200), + (0x0a00_1e00, 0x200), + (0x0a00_3000, 0x200), + (0x0a00_2200, 0x200), + (0x0a00_2400, 0x200), + (0x0a00_2600, 0x200), + (0x0a00_2800, 0x200), + (0x0a00_2a00, 0x200), + (0x0a00_2c00, 0x200), + (0x0a00_2e00, 0x200), + (0x0a00_3000, 0x200), + (0x0a00_3200, 0x200), + (0x0a00_3400, 0x200), + (0x0a00_3600, 0x200), + (0x0a00_3800, 0x200), + (0x0a00_3a00, 0x200), + (0x0a00_3c00, 0x200), + (0x0a00_3e00, 0x200), +]; +/// Base physical address of the PCIe ECAM space. +pub const PCI_ECAM_BASE: usize = 0x40_1000_0000; +/// End PCI bus number (`bus-range` property in device tree). +pub const PCI_BUS_END: usize = 0xff; +/// PCI device memory ranges (`ranges` property in device tree). +pub const PCI_RANGES: &[(usize, usize)] = &[ + (0x3ef_f0000, 0x1_0000), + (0x1000_0000, 0x2eff_0000), + (0x80_0000_0000, 0x80_0000_0000), +]; +/// UART Address +pub const UART_PADDR: usize = 0x0900_0000; + +pub const UART_IRQ_NUM: usize = 33; +/// GICC Address +pub const GICC_PADDR: usize = 0x0801_0000; + +pub const GICD_PADDR: usize = 0x0800_0000; +/// Number of CPUs +pub const SMP: usize = 1; diff --git a/modules/axconfig/src/config_qemu_virt_riscv.rs b/modules/axconfig/src/config_qemu_virt_riscv.rs new file mode 100644 index 0000000..15fcd37 --- /dev/null +++ b/modules/axconfig/src/config_qemu_virt_riscv.rs @@ -0,0 +1,67 @@ +//! Platform constants and parameters for qemu-virt-riscv. +//! Generated by build.rs, DO NOT edit! + +/// Stack size of each task. +pub const TASK_STACK_SIZE: usize = 0x40000; +/// Number of timer ticks per second (Hz). A timer tick may contain several timer +/// interrupts. +pub const TICKS_PER_SEC: usize = 100; +/// Architecture identifier. +pub const ARCH: &str = "riscv64"; +/// Platform identifier. +pub const PLATFORM: &str = "qemu-virt-riscv"; +/// Base address of the whole physical memory. +pub const PHYS_MEMORY_BASE: usize = 0x8000_0000; +/// Size of the pre physical memory. +/// If now it is in patch, then it will have the other physical memory. +pub const PHYS_MEMORY_SIZE: usize = 0x800_0000; +/// Base physical address of the kernel image. +pub const KERNEL_BASE_PADDR: usize = 0x8020_0000; +/// Base virtual address of the kernel image. +pub const KERNEL_BASE_VADDR: usize = 0xffff_ffc0_8020_0000; +/// Linear mapping offset, for quick conversions between physical and virtual +/// addresses. +pub const PHYS_VIRT_OFFSET: usize = 0xffff_ffc0_0000_0000; +/// user memory +pub const USER_MEMORY_START: usize = 0x1000; + +pub const USER_MEMORY_LIMIT: usize = 0xffff_ffff; +/// Testcase memory +pub const TESTCASE_MEMORY_START: usize = 0x9000_0000; + +pub const TESTCASE_MEMORY_SIZE: usize = 0x400_0000; + +pub const QEMU_MEMORY_SIZE: usize = 0x4000_0000; +/// MMIO regions with format (`base_paddr`, `size`). +pub const MMIO_REGIONS: &[(usize, usize)] = &[ + (0x0c00_0000, 0x21_0000), + (0x1000_0000, 0x1000), + (0x1000_1000, 0x8000), + (0x3000_0000, 0x1000_0000), + (0x4000_0000, 0x4000_0000), +]; +/// VirtIO MMIO regions with format (`base_paddr`, `size`). +pub const VIRTIO_MMIO_REGIONS: &[(usize, usize)] = &[ + (0x1000_1000, 0x1000), + (0x1000_2000, 0x1000), + (0x1000_3000, 0x1000), + (0x1000_4000, 0x1000), + (0x1000_5000, 0x1000), + (0x1000_6000, 0x1000), + (0x1000_7000, 0x1000), + (0x1000_8000, 0x1000), +]; +/// Base physical address of the PCIe ECAM space. +pub const PCI_ECAM_BASE: usize = 0x3000_0000; +/// End PCI bus number (`bus-range` property in device tree). +pub const PCI_BUS_END: usize = 0xff; +/// PCI device memory ranges (`ranges` property in device tree). +pub const PCI_RANGES: &[(usize, usize)] = &[ + (0x0300_0000, 0x1_0000), + (0x4000_0000, 0x4000_0000), + (0x4_0000_0000, 0x4_0000_0000), +]; +/// Timer interrupt frequency in Hz. +pub const TIMER_FREQUENCY: usize = 1000_000; +/// Number of CPUs +pub const SMP: usize = 1; diff --git a/modules/axconfig/src/config_raspi4_aarch64.rs b/modules/axconfig/src/config_raspi4_aarch64.rs new file mode 100644 index 0000000..e8e3a2e --- /dev/null +++ b/modules/axconfig/src/config_raspi4_aarch64.rs @@ -0,0 +1,41 @@ +//! Platform constants and parameters for raspi4-aarch64. +//! Generated by build.rs, DO NOT edit! + +/// Stack size of each task. +pub const TASK_STACK_SIZE: usize = 0x40000; +/// Number of timer ticks per second (Hz). A timer tick may contain several timer +/// interrupts. +pub const TICKS_PER_SEC: usize = 100; +/// Architecture identifier. +pub const ARCH: &str = "aarch64"; +/// Platform identifier. +pub const PLATFORM: &str = "raspi4-aarch64"; +/// Base address of the whole physical memory. +pub const PHYS_MEMORY_BASE: usize = 0x0; +/// Size of the whole physical memory. +pub const PHYS_MEMORY_SIZE: usize = 0xFC00_0000; +/// Base physical address of the kernel image. +pub const KERNEL_BASE_PADDR: usize = 0x8_0000; +/// Base virtual address of the kernel image. +pub const KERNEL_BASE_VADDR: usize = 0xffff_0000_0008_0000; +/// Linear mapping offset, for quick conversions between physical and virtual +/// addresses. +pub const PHYS_VIRT_OFFSET: usize = 0xffff_0000_0000_0000; +/// MMIO regions with format (`base_paddr`, `size`). +pub const MMIO_REGIONS: &[(usize, usize)] = &[ + (0xFE20_1000, 0x1000), + (0xFF84_1000, 0x8000), +]; + +pub const VIRTIO_MMIO_REGIONS: &[(usize, usize)] = &[ +]; +/// UART Address +pub const UART_PADDR: usize = 0xFE20_1000; + +pub const UART_IRQ_NUM: usize = 153; +/// GIC Address +pub const GICC_PADDR: usize = 0xFF84_2000; + +pub const GICD_PADDR: usize = 0xFF84_1000; +/// Number of CPUs +pub const SMP: usize = 1; diff --git a/modules/axconfig/src/lib.rs b/modules/axconfig/src/lib.rs new file mode 100644 index 0000000..b40ac4f --- /dev/null +++ b/modules/axconfig/src/lib.rs @@ -0,0 +1,22 @@ +//! Platform-specific constants and parameters for [ArceOS]. +//! +//! Currently supported platforms can be found in the [platforms] directory of +//! the [ArceOS] root. +//! +//! [ArceOS]: https://github.com/rcore-os/arceos +//! [platforms]: https://github.com/rcore-os/arceos/tree/main/platforms + +#![no_std] + +#[rustfmt::skip] +mod config { + include!(concat!(env!("OUT_DIR"), "/config.rs")); +} + +pub use config::*; + +/// End address of the whole physical memory. +pub const PHYS_MEMORY_END: usize = PHYS_MEMORY_BASE + PHYS_MEMORY_SIZE; + +/// user memory +pub const USER_MEMORY_START: usize = 0x1000; diff --git a/modules/axdisplay/.gitignore b/modules/axdisplay/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/modules/axdisplay/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/modules/axdisplay/Cargo.toml b/modules/axdisplay/Cargo.toml new file mode 100644 index 0000000..23a88ef --- /dev/null +++ b/modules/axdisplay/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "axdisplay" +version = "0.1.0" +edition = "2021" +authors = ["Shiping Yuan "] +description = "ArceOS graphics module" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axdisplay" +documentation = "https://rcore-os.github.io/arceos/axdisplay/index.html" +keywords = ["Starry"] + +[dependencies] +log = "0.4" +axdriver = { workspace = true, features = ["display"] } +lazy_init = { git = "https://github.com/Starry-OS/lazy_init.git" } +axsync = { workspace = true } +driver_display = { git = "https://github.com/Starry-OS/driver_display.git" } diff --git a/modules/axdisplay/src/lib.rs b/modules/axdisplay/src/lib.rs new file mode 100644 index 0000000..26dc422 --- /dev/null +++ b/modules/axdisplay/src/lib.rs @@ -0,0 +1,36 @@ +//! [ArceOS](https://github.com/rcore-os/arceos) graphics module. +//! +//! Currently only supports direct writing to the framebuffer. + +#![no_std] + +#[macro_use] +extern crate log; + +#[doc(no_inline)] +pub use driver_display::DisplayInfo; + +use axdriver::{prelude::*, AxDeviceContainer}; +use axsync::Mutex; +use lazy_init::LazyInit; + +static MAIN_DISPLAY: LazyInit> = LazyInit::new(); + +/// Initializes the graphics subsystem by underlayer devices. +pub fn init_display(mut display_devs: AxDeviceContainer) { + info!("Initialize graphics subsystem..."); + + let dev = display_devs.take_one().expect("No graphics device found!"); + info!(" use graphics device 0: {:?}", dev.device_name()); + MAIN_DISPLAY.init_by(Mutex::new(dev)); +} + +/// Gets the framebuffer information. +pub fn framebuffer_info() -> DisplayInfo { + MAIN_DISPLAY.lock().info() +} + +/// Flushes the framebuffer, i.e. show on the screen. +pub fn framebuffer_flush() { + MAIN_DISPLAY.lock().flush().unwrap(); +} diff --git a/modules/axdriver/.gitignore b/modules/axdriver/.gitignore new file mode 100644 index 0000000..1904343 --- /dev/null +++ b/modules/axdriver/.gitignore @@ -0,0 +1,16 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +image.S \ No newline at end of file diff --git a/modules/axdriver/Cargo.toml b/modules/axdriver/Cargo.toml new file mode 100644 index 0000000..77beabb --- /dev/null +++ b/modules/axdriver/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "axdriver" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia ", "ChengXiang Qi "] +description = "ArceOS device drivers" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axdriver" +documentation = "https://rcore-os.github.io/arceos/axdriver/index.html" +keywords = ["Starry"] + +[features] +dyn = [] +bus-mmio = [] +bus-pci = ["dep:driver_pci", "dep:axhal", "dep:axconfig"] +net = ["driver_net"] +block = ["driver_block"] +display = ["driver_display"] + +# Enabled by features `virtio-*` +virtio = ["driver_virtio", "dep:axalloc", "dep:axhal", "dep:axconfig"] + +# various types of drivers +virtio-blk = ["block", "virtio", "driver_virtio/block"] +virtio-net = ["net", "virtio", "driver_virtio/net"] +virtio-gpu = ["display", "virtio", "driver_virtio/gpu"] +ramdisk = ["block", "driver_block/ramdisk"] +bcm2835-sdhci = ["block", "driver_block/bcm2835-sdhci"] +ixgbe = ["net", "driver_net/ixgbe", "dep:axalloc", "dep:axhal"] +e1000 = ["net", "driver_net/e1000", "dep:axalloc", "dep:axhal"] + +img = ["ramdisk", "dep:axconfig"] + +default = ["bus-pci"] + +[dependencies] +log = "0.4" +cfg-if = "1.0" +driver_common = { git = "https://github.com/Starry-OS/driver_common.git" } +driver_block = { git = "https://github.com/Starry-OS/driver_block.git", optional = true } +driver_net = { git = "https://github.com/Starry-OS/driver_net.git", optional = true } +driver_display = { git = "https://github.com/Starry-OS/driver_display.git", optional = true } +driver_pci = { git = "https://github.com/Starry-OS/driver_pci.git", optional = true } +driver_virtio = { git = "https://github.com/Starry-OS/driver_virtio.git", optional = true } +axalloc = { workspace = true, optional = true } +axhal = { workspace = true, optional = true } +axconfig = { workspace = true, optional = true } diff --git a/modules/axdriver/build.rs b/modules/axdriver/build.rs new file mode 100644 index 0000000..829248f --- /dev/null +++ b/modules/axdriver/build.rs @@ -0,0 +1,104 @@ +#[cfg(feature = "img")] +use std::{ + fs::File, + io::{Result, Write}, +}; + +const NET_DEV_FEATURES: &[&str] = &["e1000", "ixgbe", "virtio-net"]; +const BLOCK_DEV_FEATURES: &[&str] = &["ramdisk", "bcm2835-sdhci", "virtio-blk"]; +const DISPLAY_DEV_FEATURES: &[&str] = &["virtio-gpu"]; + +fn make_cfg_values(str_list: &[&str]) -> String { + str_list + .iter() + .map(|s| format!("{:?}", s)) + .collect::>() + .join(", ") +} + +fn has_feature(feature: &str) -> bool { + std::env::var(format!( + "CARGO_FEATURE_{}", + feature.to_uppercase().replace('-', "_") + )) + .is_ok() +} + +fn enable_cfg(key: &str, value: &str) { + println!("cargo:rustc-cfg={key}=\"{value}\""); +} + +#[cfg(feature = "img")] +fn new_fs_img() -> Result<()> { + let mut f = File::create("./image.S").unwrap(); + let img_path = std::env::var("AX_WORK_DIR").unwrap() + "/disk.img"; + writeln!( + f, + r#" + .section .data + .global img_start + .global img_end + .align 16 +img_start: + .incbin "{}" +img_end:"#, + img_path, + )?; + Ok(()) +} + +fn main() { + if has_feature("bus-mmio") { + enable_cfg("bus", "mmio"); + } else { + enable_cfg("bus", "pci"); + } + + #[cfg(feature = "img")] + // 将测例镜像放置在ram-disk中 + new_fs_img().unwrap(); + + // Generate cfgs like `net_dev="virtio-net"`. if `dyn` is not enabled, only one device is + // selected for each device category. If no device is selected, `dummy` is selected. + let is_dyn = has_feature("dyn"); + for (dev_kind, feat_list) in [ + ("net", NET_DEV_FEATURES), + ("block", BLOCK_DEV_FEATURES), + ("display", DISPLAY_DEV_FEATURES), + ] { + if !has_feature(dev_kind) { + continue; + } + + let mut selected = false; + for feat in feat_list { + if has_feature(feat) { + enable_cfg(&format!("{dev_kind}_dev"), feat); + selected = true; + if !is_dyn { + break; + } + } + } + if !is_dyn && !selected { + enable_cfg(&format!("{dev_kind}_dev"), "dummy"); + } + } + + println!( + "cargo::rustc-check-cfg=cfg(bus, values({}))", + make_cfg_values(&["pci", "mmio"]) + ); + println!( + "cargo::rustc-check-cfg=cfg(net_dev, values({}, \"dummy\"))", + make_cfg_values(NET_DEV_FEATURES) + ); + println!( + "cargo::rustc-check-cfg=cfg(block_dev, values({}, \"dummy\"))", + make_cfg_values(BLOCK_DEV_FEATURES) + ); + println!( + "cargo::rustc-check-cfg=cfg(display_dev, values({}, \"dummy\"))", + make_cfg_values(DISPLAY_DEV_FEATURES) + ); +} diff --git a/modules/axdriver/src/bus/mmio.rs b/modules/axdriver/src/bus/mmio.rs new file mode 100644 index 0000000..5a3573a --- /dev/null +++ b/modules/axdriver/src/bus/mmio.rs @@ -0,0 +1,23 @@ +#[allow(unused_imports)] +use crate::{prelude::*, AllDevices}; + +impl AllDevices { + pub(crate) fn probe_bus_devices(&mut self) { + // TODO: parse device tree + #[cfg(feature = "virtio")] + for reg in axconfig::VIRTIO_MMIO_REGIONS { + for_each_drivers!(type Driver, { + if let Some(dev) = Driver::probe_mmio(reg.0, reg.1) { + info!( + "registered a new {:?} device at [PA:{:#x}, PA:{:#x}): {:?}", + dev.device_type(), + reg.0, reg.0 + reg.1, + dev.device_name(), + ); + self.add_device(dev); + continue; // skip to the next device + } + }); + } + } +} diff --git a/modules/axdriver/src/bus/mod.rs b/modules/axdriver/src/bus/mod.rs new file mode 100644 index 0000000..bc7f80f --- /dev/null +++ b/modules/axdriver/src/bus/mod.rs @@ -0,0 +1,4 @@ +#[cfg(bus = "mmio")] +mod mmio; +#[cfg(bus = "pci")] +mod pci; diff --git a/modules/axdriver/src/bus/pci.rs b/modules/axdriver/src/bus/pci.rs new file mode 100644 index 0000000..f47b275 --- /dev/null +++ b/modules/axdriver/src/bus/pci.rs @@ -0,0 +1,122 @@ +use crate::{prelude::*, AllDevices}; +use axhal::mem::phys_to_virt; +use driver_pci::{ + BarInfo, Cam, Command, DeviceFunction, HeaderType, MemoryBarType, PciRangeAllocator, PciRoot, +}; + +const PCI_BAR_NUM: u8 = 6; + +fn config_pci_device( + root: &mut PciRoot, + bdf: DeviceFunction, + allocator: &mut Option, +) -> DevResult { + let mut bar = 0; + while bar < PCI_BAR_NUM { + let info = root.bar_info(bdf, bar).unwrap(); + if let BarInfo::Memory { + address_type, + address, + size, + .. + } = info + { + // if the BAR address is not assigned, call the allocator and assign it. + if size > 0 && address == 0 { + let new_addr = allocator + .as_mut() + .expect("No memory ranges available for PCI BARs!") + .alloc(size as _) + .ok_or(DevError::NoMemory)?; + if address_type == MemoryBarType::Width32 { + root.set_bar_32(bdf, bar, new_addr as _); + } else if address_type == MemoryBarType::Width64 { + root.set_bar_64(bdf, bar, new_addr); + } + } + } + + // read the BAR info again after assignment. + let info = root.bar_info(bdf, bar).unwrap(); + match info { + BarInfo::IO { address, size } => { + if address > 0 && size > 0 { + debug!(" BAR {}: IO [{:#x}, {:#x})", bar, address, address + size); + } + } + BarInfo::Memory { + address_type, + prefetchable, + address, + size, + } => { + if address > 0 && size > 0 { + debug!( + " BAR {}: MEM [{:#x}, {:#x}){}{}", + bar, + address, + address + size as u64, + if address_type == MemoryBarType::Width64 { + " 64bit" + } else { + "" + }, + if prefetchable { " pref" } else { "" }, + ); + } + } + } + + bar += 1; + if info.takes_two_entries() { + bar += 1; + } + } + + // Enable the device. + let (_status, cmd) = root.get_status_command(bdf); + root.set_command( + bdf, + cmd | Command::IO_SPACE | Command::MEMORY_SPACE | Command::BUS_MASTER, + ); + Ok(()) +} + +impl AllDevices { + pub(crate) fn probe_bus_devices(&mut self) { + let base_vaddr = phys_to_virt(axconfig::PCI_ECAM_BASE.into()); + let mut root = unsafe { PciRoot::new(base_vaddr.as_mut_ptr(), Cam::Ecam) }; + + // PCI 32-bit MMIO space + let mut allocator = axconfig::PCI_RANGES + .get(1) + .map(|range| PciRangeAllocator::new(range.0 as u64, range.1 as u64)); + + for bus in 0..=axconfig::PCI_BUS_END as u8 { + for (bdf, dev_info) in root.enumerate_bus(bus) { + debug!("PCI {}: {}", bdf, dev_info); + if dev_info.header_type != HeaderType::Standard { + continue; + } + match config_pci_device(&mut root, bdf, &mut allocator) { + Ok(_) => for_each_drivers!(type Driver, { + if let Some(dev) = Driver::probe_pci(&mut root, bdf, &dev_info) { + info!( + "registered a new {:?} device at {}: {:?}", + dev.device_type(), + bdf, + dev.device_name(), + ); + self.add_device(dev); + continue; // skip to the next device + } + }), + Err(e) => warn!( + "failed to enable PCI device at {}({}): {:?}", + bdf, dev_info, e + ), + } + } + } + } +} diff --git a/modules/axdriver/src/drivers.rs b/modules/axdriver/src/drivers.rs new file mode 100644 index 0000000..a8463a4 --- /dev/null +++ b/modules/axdriver/src/drivers.rs @@ -0,0 +1,204 @@ +//! Defines types and probe methods of all supported devices. + +#![allow(unused_imports, dead_code)] + +use core::ptr::NonNull; + +use crate::AxDeviceEnum; +use driver_common::DeviceType; + +#[cfg(feature = "virtio")] +use crate::virtio::{self, VirtIoDevMeta}; + +#[cfg(feature = "bus-pci")] +use driver_pci::{DeviceFunction, DeviceFunctionInfo, PciRoot}; + +pub use super::dummy::*; + +pub trait DriverProbe { + fn probe_global() -> Option { + None + } + + #[cfg(bus = "mmio")] + fn probe_mmio(_mmio_base: usize, _mmio_size: usize) -> Option { + None + } + + #[cfg(bus = "pci")] + fn probe_pci( + _root: &mut PciRoot, + _bdf: DeviceFunction, + _dev_info: &DeviceFunctionInfo, + ) -> Option { + None + } +} + +#[cfg(net_dev = "virtio-net")] +register_net_driver!( + ::Driver, + ::Device +); + +#[cfg(block_dev = "virtio-blk")] +register_block_driver!( + ::Driver, + ::Device +); + +#[cfg(display_dev = "virtio-gpu")] +register_display_driver!( + ::Driver, + ::Device +); + +cfg_if::cfg_if! { + if #[cfg(block_dev = "ramdisk")] { + pub struct RamDiskDriver; + register_block_driver!(RamDiskDriver, driver_block::ramdisk::RamDisk); + + impl DriverProbe for RamDiskDriver { + fn probe_global() -> Option { + // TODO: format RAM disk + Some(AxDeviceEnum::from_block( + driver_block::ramdisk::RamDisk::new(0x100_0000), // 16 MiB + )) + } + } + } +} + +cfg_if::cfg_if! { + if #[cfg(block_dev = "bcm2835-sdhci")]{ + pub struct BcmSdhciDriver; + register_block_driver!(MmckDriver, driver_block::bcm2835sdhci::SDHCIDriver); + + impl DriverProbe for BcmSdhciDriver { + fn probe_global() -> Option { + debug!("mmc probe"); + driver_block::bcm2835sdhci::SDHCIDriver::try_new().ok().map(AxDeviceEnum::from_block) + } + } + } +} + +cfg_if::cfg_if! { + if #[cfg(net_dev = "ixgbe")] { + use crate::ixgbe::IxgbeHalImpl; + use axhal::mem::phys_to_virt; + pub struct IxgbeDriver; + register_net_driver!(IxgbeDriver, driver_net::ixgbe::IxgbeNic); + impl DriverProbe for IxgbeDriver { + #[cfg(bus = "pci")] + fn probe_pci( + root: &mut driver_pci::PciRoot, + bdf: driver_pci::DeviceFunction, + dev_info: &driver_pci::DeviceFunctionInfo, + ) -> Option { + use driver_net::ixgbe::{INTEL_82599, INTEL_VEND, IxgbeNic}; + if dev_info.vendor_id == INTEL_VEND && dev_info.device_id == INTEL_82599 { + // Intel 10Gb Network + info!("ixgbe PCI device found at {:?}", bdf); + + // Initialize the device + // These can be changed according to the requirments specified in the ixgbe init function. + const QN: u16 = 1; + const QS: usize = 1024; + let bar_info = root.bar_info(bdf, 0).unwrap(); + match bar_info { + driver_pci::BarInfo::Memory { + address, + size, + .. + } => { + let ixgbe_nic = IxgbeNic::::init( + phys_to_virt((address as usize).into()).into(), + size as usize + ) + .expect("failed to initialize ixgbe device"); + return Some(AxDeviceEnum::from_net(ixgbe_nic)); + } + driver_pci::BarInfo::IO { .. } => { + error!("ixgbe: BAR0 is of I/O type"); + return None; + } + } + } + None + } + } + } +} + +cfg_if::cfg_if! { + if #[cfg(net_dev = "e1000")] { + use axalloc::global_allocator; + use driver_net::e1000::{E1000Nic, KernelFunc}; + use core::alloc::Layout; + + pub struct KernelFuncObj; + impl KernelFunc for KernelFuncObj { + /// Allocate consequent physical memory for DMA; + /// Return (cpu virtual address, dma physical address) which is page aligned. + //fn dma_alloc_coherent(pages: usize) -> usize; + fn dma_alloc_coherent(&mut self, pages: usize) -> (usize, usize) { + let vaddr = if let Ok(start_vaddr) = global_allocator().alloc_pages(pages, Self::PAGE_SIZE) { + start_vaddr + } else { + error!("failed to alloc pages"); + return (0, 0); + }; + let paddr = axhal::mem::virt_to_phys((vaddr).into()); + info!("dma_alloc_coherent pages @ vaddr={:#x}, paddr={:#x}", vaddr, paddr); + (vaddr, paddr.as_usize()) + } + + /// Deallocate DMA memory by virtual address + fn dma_free_coherent(&mut self, vaddr: usize, pages: usize) { + global_allocator().dealloc_pages(vaddr, pages); + } + } + + pub struct E1000Driver; + register_net_driver!(E1000Driver, driver_net::e1000::E1000Nic<'static, KernelFuncObj>); + + + impl DriverProbe for E1000Driver { + #[cfg(bus = "pci")] + fn probe_pci( + root: &mut driver_pci::PciRoot, + bdf: driver_pci::DeviceFunction, + dev_info: &driver_pci::DeviceFunctionInfo, + ) -> Option { + const E1000_VENDOR_ID: u16 = 0x8086; + const E1000_DEVICE_ID: u16 = 0x100e; + info!("PCI vendor:device = {:#x}:{:#x}", dev_info.vendor_id, dev_info.device_id); + if dev_info.vendor_id == E1000_VENDOR_ID && dev_info.device_id == E1000_DEVICE_ID { + info!("E1000 PCI device found at {:?}", bdf); + + // Initialize the device + match root.bar_info(bdf, 0).unwrap() { + driver_pci::BarInfo::Memory { + address, + .. + } => { + let kfn = KernelFuncObj; + let nic = E1000Nic::::init( + kfn, + axhal::mem::phys_to_virt((address as usize).into()).into() + ) + .expect("failed to initialize e1000 device"); + return Some(AxDeviceEnum::from_net(nic)); + } + driver_pci::BarInfo::IO { .. } => { + error!("e1000: BAR0 is of I/O type"); + return None; + } + } + } + None + } + } + } +} diff --git a/modules/axdriver/src/dummy.rs b/modules/axdriver/src/dummy.rs new file mode 100644 index 0000000..7a02fb0 --- /dev/null +++ b/modules/axdriver/src/dummy.rs @@ -0,0 +1,102 @@ +//! Dummy types used if no device of a certain category is selected. + +#![allow(unused_imports)] +#![allow(dead_code)] + +use super::prelude::*; +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(net_dev = "dummy")] { + use driver_net::{EthernetAddress, NetBuf, NetBufBox, NetBufPool, NetBufPtr}; + + pub struct DummyNetDev; + pub struct DummyNetDrvier; + register_net_driver!(DummyNetDriver, DummyNetDev); + + impl BaseDriverOps for DummyNetDev { + fn device_type(&self) -> DeviceType { DeviceType::Net } + fn device_name(&self) -> &str { "dummy-net" } + } + + impl NetDriverOps for DummyNetDev { + fn mac_address(&self) -> EthernetAddress { unreachable!() } + fn can_transmit(&self) -> bool { false } + fn can_receive(&self) -> bool { false } + fn rx_queue_size(&self) -> usize { 0 } + fn tx_queue_size(&self) -> usize { 0 } + fn recycle_rx_buffer(&mut self, _: NetBufPtr) -> DevResult { Err(DevError::Unsupported) } + fn recycle_tx_buffers(&mut self) -> DevResult { Err(DevError::Unsupported) } + fn transmit(&mut self, _: NetBufPtr) -> DevResult { Err(DevError::Unsupported) } + fn receive(&mut self) -> DevResult { Err(DevError::Unsupported) } + fn alloc_tx_buffer(&mut self, _: usize) -> DevResult { Err(DevError::Unsupported) } + } + } +} + +cfg_if! { + if #[cfg(block_dev = "dummy")] { + pub struct DummyBlockDev; + pub struct DummyBlockDriver; + register_block_driver!(DummyBlockDriver, DummyBlockDev); + + impl BaseDriverOps for DummyBlockDev { + fn device_type(&self) -> DeviceType { + DeviceType::Block + } + fn device_name(&self) -> &str { + "dummy-block" + } + } + + impl BlockDriverOps for DummyBlockDev { + fn num_blocks(&self) -> u64 { + 0 + } + fn block_size(&self) -> usize { + 0 + } + fn read_block(&mut self, _: u64, _: &mut [u8]) -> DevResult { + Err(DevError::Unsupported) + } + fn write_block(&mut self, _: u64, _: &[u8]) -> DevResult { + Err(DevError::Unsupported) + } + fn flush(&mut self) -> DevResult { + Err(DevError::Unsupported) + } + } + } +} + +cfg_if! { + if #[cfg(display_dev = "dummy")] { + pub struct DummyDisplayDev; + pub struct DummyDisplayDriver; + register_display_driver!(DummyDisplayDriver, DummyDisplayDev); + + impl BaseDriverOps for DummyDisplayDev { + fn device_type(&self) -> DeviceType { + DeviceType::Display + } + fn device_name(&self) -> &str { + "dummy-display" + } + } + + impl DisplayDriverOps for DummyDisplayDev { + fn info(&self) -> driver_display::DisplayInfo { + unreachable!() + } + fn fb(&self) -> driver_display::FrameBuffer { + unreachable!() + } + fn need_flush(&self) -> bool { + false + } + fn flush(&mut self) -> DevResult { + Err(DevError::Unsupported) + } + } + } +} diff --git a/modules/axdriver/src/ixgbe.rs b/modules/axdriver/src/ixgbe.rs new file mode 100644 index 0000000..34d9267 --- /dev/null +++ b/modules/axdriver/src/ixgbe.rs @@ -0,0 +1,38 @@ +use axalloc::global_allocator; +use axhal::mem::{phys_to_virt, virt_to_phys}; +use core::{alloc::Layout, ptr::NonNull}; +use driver_net::ixgbe::{IxgbeHal, PhysAddr as IxgbePhysAddr}; + +pub struct IxgbeHalImpl; + +unsafe impl IxgbeHal for IxgbeHalImpl { + fn dma_alloc(size: usize) -> (IxgbePhysAddr, NonNull) { + let layout = Layout::from_size_align(size, 8).unwrap(); + let vaddr = if let Ok(vaddr) = global_allocator().alloc(layout) { + vaddr + } else { + return (0, NonNull::dangling()); + }; + let paddr = virt_to_phys((vaddr.as_ptr() as usize).into()); + (paddr.as_usize(), vaddr) + } + + unsafe fn dma_dealloc(_paddr: IxgbePhysAddr, vaddr: NonNull, size: usize) -> i32 { + let layout = Layout::from_size_align(size, 8).unwrap(); + global_allocator().dealloc(vaddr, layout); + 0 + } + + unsafe fn mmio_phys_to_virt(paddr: IxgbePhysAddr, _size: usize) -> NonNull { + NonNull::new(phys_to_virt(paddr.into()).as_mut_ptr()).unwrap() + } + + unsafe fn mmio_virt_to_phys(vaddr: NonNull, _size: usize) -> IxgbePhysAddr { + virt_to_phys((vaddr.as_ptr() as usize).into()).into() + } + + fn wait_until(duration: core::time::Duration) -> Result<(), &'static str> { + axhal::time::busy_wait_until(duration); + Ok(()) + } +} diff --git a/modules/axdriver/src/lib.rs b/modules/axdriver/src/lib.rs new file mode 100644 index 0000000..2f4091e --- /dev/null +++ b/modules/axdriver/src/lib.rs @@ -0,0 +1,212 @@ +//! [ArceOS](https://github.com/rcore-os/arceos) device drivers. +//! +//! # Usage +//! +//! All detected devices are composed into a large struct [`AllDevices`] +//! and returned by the [`init_drivers`] function. The upperlayer subsystems +//! (e.g., the network stack) may unpack the struct to get the specified device +//! driver they want. +//! +//! For each device category (i.e., net, block, display, etc.), an unified type +//! is used to represent all devices in that category. Currently, there are 3 +//! categories: [`AxNetDevice`], [`AxBlockDevice`], and [`AxDisplayDevice`]. +//! +//! # Concepts +//! +//! This crate supports two device models depending on the `dyn` feature: +//! +//! - **Static**: The type of all devices is static, it is determined at compile +//! time by corresponding cargo features. For example, [`AxNetDevice`] will be +//! an alias of [`VirtioNetDev`] if the `virtio-net` feature is enabled. This +//! model provides the best performance as it avoids dynamic dispatch. But on +//! limitation, only one device instance is supported for each device category. +//! - **Dynamic**: All device instance is using [trait objects] and wrapped in a +//! `Box`. For example, [`AxNetDevice`] will be [`Box`]. +//! When call a method provided by the device, it uses [dynamic dispatch][dyn] +//! that may introduce a little overhead. But on the other hand, it is more +//! flexible, multiple instances of each device category are supported. +//! +//! # Supported Devices +//! +//! | Device Category | Cargo Feature | Description | +//! |-|-|-| +//! | Block | `ramdisk` | A RAM disk that stores data in a vector | +//! | Block | `virtio-blk` | VirtIO block device | +//! | Network | `virtio-net` | VirtIO network device | +//! | Display | `virtio-gpu` | VirtIO graphics device | +//! +//! # Other Cargo Features +//! +//! - `dyn`: use the dynamic device model (see above). +//! - `bus-mmio`: use device tree to probe all MMIO devices. +//! - `bus-pci`: use PCI bus to probe all PCI devices. This feature is +//! enabeld by default. +//! - `virtio`: use VirtIO devices. This is enabled if any of `virtio-blk`, +//! `virtio-net` or `virtio-gpu` is enabled. +//! - `net`: use network devices. This is enabled if any feature of network +//! devices is selected. If this feature is enabled without any network device +//! features, a dummy struct is used for [`AxNetDevice`]. +//! - `block`: use block storage devices. Similar to the `net` feature. +//! - `display`: use graphics display devices. Similar to the `net` feature. +//! +//! [`VirtioNetDev`]: driver_virtio::VirtIoNetDev +//! [`Box`]: driver_net::NetDriverOps +//! [trait objects]: https://doc.rust-lang.org/book/ch17-02-trait-objects.html +//! [dyn]: https://doc.rust-lang.org/std/keyword.dyn.html + +#![no_std] +#![feature(doc_auto_cfg)] +#![feature(associated_type_defaults)] + +#[macro_use] +extern crate log; + +#[cfg(feature = "dyn")] +extern crate alloc; + +#[macro_use] +mod macros; + +mod bus; +mod drivers; +mod dummy; +mod structs; + +#[cfg(feature = "virtio")] +mod virtio; + +#[cfg(feature = "ixgbe")] +mod ixgbe; + +pub mod prelude; + +#[allow(unused_imports)] +use self::prelude::*; +pub use self::structs::{AxDeviceContainer, AxDeviceEnum}; + +#[cfg(feature = "block")] +pub use self::structs::AxBlockDevice; +#[cfg(feature = "display")] +pub use self::structs::AxDisplayDevice; +#[cfg(feature = "net")] +pub use self::structs::AxNetDevice; + +/// A structure that contains all device drivers, organized by their category. +#[derive(Default)] +pub struct AllDevices { + /// All network device drivers. + #[cfg(feature = "net")] + pub net: AxDeviceContainer, + /// All block device drivers. + #[cfg(feature = "block")] + pub block: AxDeviceContainer, + /// All graphics device drivers. + #[cfg(feature = "display")] + pub display: AxDeviceContainer, +} + +#[cfg(feature = "img")] +core::arch::global_asm!(include_str!("../image.S")); + +impl AllDevices { + /// Returns the device model used, either `dyn` or `static`. + /// + /// See the [crate-level documentation](crate) for more details. + pub const fn device_model() -> &'static str { + if cfg!(feature = "dyn") { + "dyn" + } else { + "static" + } + } + + /// Probes all supported devices. + fn probe(&mut self) { + for_each_drivers!(type Driver, { + if let Some(dev) = Driver::probe_global() { + info!( + "registered a new {:?} device: {:?}", + dev.device_type(), + dev.device_name(), + ); + self.add_device(dev); + } + }); + + self.probe_bus_devices(); + } + + /// Adds one device into the corresponding container, according to its device category. + #[allow(dead_code)] + fn add_device(&mut self, dev: AxDeviceEnum) { + match dev { + #[cfg(feature = "net")] + AxDeviceEnum::Net(dev) => self.net.push(dev), + #[cfg(feature = "block")] + AxDeviceEnum::Block(dev) => self.block.push(dev), + #[cfg(feature = "display")] + AxDeviceEnum::Display(dev) => self.display.push(dev), + } + } +} + +/// Probes and initializes all device drivers, returns the [`AllDevices`] struct. +pub fn init_drivers() -> AllDevices { + info!("Initialize device drivers..."); + info!(" device model: {}", AllDevices::device_model()); + + let mut all_devs = AllDevices::default(); + + #[cfg(feature = "img")] + { + use axconfig::{PHYS_VIRT_OFFSET, TESTCASE_MEMORY_SIZE, TESTCASE_MEMORY_START}; + use driver_block::ramdisk::BLOCK_SIZE; + let mut ram_disk = driver_block::ramdisk::RamDisk::new(TESTCASE_MEMORY_SIZE); + let mut buf = [0u8; BLOCK_SIZE]; + let block_num = (TESTCASE_MEMORY_SIZE / BLOCK_SIZE) as u64; + for i in 0..block_num { + unsafe { + core::ptr::copy_nonoverlapping( + (TESTCASE_MEMORY_START + PHYS_VIRT_OFFSET + i as usize * BLOCK_SIZE) + as *const u8, + buf.as_mut_ptr(), + BLOCK_SIZE, + ); + } + ram_disk.write_block(i, &buf).unwrap(); + } + // unsafe { + // ram_disk.copy_from_slice((TESTCASE_MEMORY_START + PHYS_VIRT_OFFSET) as *const u8) + // }; + all_devs.add_device(AxDeviceEnum::from_block(ram_disk)); + } + + all_devs.probe(); + + #[cfg(feature = "net")] + { + debug!("number of NICs: {}", all_devs.net.len()); + for (i, dev) in all_devs.net.iter().enumerate() { + assert_eq!(dev.device_type(), DeviceType::Net); + debug!(" NIC {}: {:?}", i, dev.device_name()); + } + } + #[cfg(feature = "block")] + { + debug!("number of block devices: {}", all_devs.block.len()); + for (i, dev) in all_devs.block.iter().enumerate() { + assert_eq!(dev.device_type(), DeviceType::Block); + debug!(" block device {}: {:?}", i, dev.device_name()); + } + } + #[cfg(feature = "display")] + { + debug!("number of graphics devices: {}", all_devs.display.len()); + for (i, dev) in all_devs.display.iter().enumerate() { + assert_eq!(dev.device_type(), DeviceType::Display); + debug!(" graphics device {}: {:?}", i, dev.device_name()); + } + } + + all_devs +} diff --git a/modules/axdriver/src/macros.rs b/modules/axdriver/src/macros.rs new file mode 100644 index 0000000..e326aff --- /dev/null +++ b/modules/axdriver/src/macros.rs @@ -0,0 +1,73 @@ +//! TODO: generate registered drivers in `for_each_drivers!` automatically. + +#![allow(unused_macros)] + +macro_rules! register_net_driver { + ($driver_type:ty, $device_type:ty) => { + /// The unified type of the NIC devices. + #[cfg(not(feature = "dyn"))] + pub type AxNetDevice = $device_type; + }; +} + +macro_rules! register_block_driver { + ($driver_type:ty, $device_type:ty) => { + /// The unified type of the NIC devices. + #[cfg(not(feature = "dyn"))] + pub type AxBlockDevice = $device_type; + }; +} + +macro_rules! register_display_driver { + ($driver_type:ty, $device_type:ty) => { + /// The unified type of the NIC devices. + #[cfg(not(feature = "dyn"))] + pub type AxDisplayDevice = $device_type; + }; +} + +macro_rules! for_each_drivers { + (type $drv_type:ident, $code:block) => {{ + #[allow(unused_imports)] + use crate::drivers::DriverProbe; + #[cfg(feature = "virtio")] + #[allow(unused_imports)] + use crate::virtio::{self, VirtIoDevMeta}; + + #[cfg(net_dev = "virtio-net")] + { + type $drv_type = ::Driver; + $code + } + #[cfg(block_dev = "virtio-blk")] + { + type $drv_type = ::Driver; + $code + } + #[cfg(display_dev = "virtio-gpu")] + { + type $drv_type = ::Driver; + $code + } + #[cfg(block_dev = "ramdisk")] + { + type $drv_type = crate::drivers::RamDiskDriver; + $code + } + #[cfg(block_dev = "bcm2835-sdhci")] + { + type $drv_type = crate::drivers::BcmSdhciDriver; + $code + } + #[cfg(net_dev = "ixgbe")] + { + type $drv_type = crate::drivers::IxgbeDriver; + $code + } + #[cfg(net_dev = "e1000")] + { + type $drv_type = crate::drivers::E1000Driver; + $code + } + }}; +} diff --git a/modules/axdriver/src/prelude.rs b/modules/axdriver/src/prelude.rs new file mode 100644 index 0000000..d41f7fb --- /dev/null +++ b/modules/axdriver/src/prelude.rs @@ -0,0 +1,10 @@ +//! Device driver prelude that includes some traits and types. + +pub use driver_common::{BaseDriverOps, DevError, DevResult, DeviceType}; + +#[cfg(feature = "block")] +pub use {crate::structs::AxBlockDevice, driver_block::BlockDriverOps}; +#[cfg(feature = "display")] +pub use {crate::structs::AxDisplayDevice, driver_display::DisplayDriverOps}; +#[cfg(feature = "net")] +pub use {crate::structs::AxNetDevice, driver_net::NetDriverOps}; diff --git a/modules/axdriver/src/structs/dyn.rs b/modules/axdriver/src/structs/dyn.rs new file mode 100644 index 0000000..c4d04cb --- /dev/null +++ b/modules/axdriver/src/structs/dyn.rs @@ -0,0 +1,85 @@ +#![allow(unused_imports)] + +use crate::prelude::*; +use alloc::{boxed::Box, vec, vec::Vec}; + +/// The unified type of the NIC devices. +#[cfg(feature = "net")] +pub type AxNetDevice = Box; +/// The unified type of the block storage devices. +#[cfg(feature = "block")] +pub type AxBlockDevice = Box; +/// The unified type of the graphics display devices. +#[cfg(feature = "display")] +pub type AxDisplayDevice = Box; + +impl super::AxDeviceEnum { + /// Constructs a network device. + #[cfg(feature = "net")] + pub fn from_net(dev: impl NetDriverOps + 'static) -> Self { + Self::Net(Box::new(dev)) + } + + /// Constructs a block device. + #[cfg(feature = "block")] + pub fn from_block(dev: impl BlockDriverOps + 'static) -> Self { + Self::Block(Box::new(dev)) + } + + /// Constructs a display device. + #[cfg(feature = "display")] + pub fn from_display(dev: impl DisplayDriverOps + 'static) -> Self { + Self::Display(Box::new(dev)) + } +} + +/// A structure that contains all device drivers of a certain category. +/// +/// If the feature `dyn` is enabled, the inner type is [`Vec`]. Otherwise, +/// the inner type is [`Option`] and at most one device can be contained. +pub struct AxDeviceContainer(Vec); + +impl AxDeviceContainer { + /// Returns number of devices in this container. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns whether the container is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Takes one device out of the container (will remove it from the container). + pub fn take_one(&mut self) -> Option { + if self.is_empty() { + None + } else { + Some(self.0.remove(0)) + } + } + + /// Constructs the container from one device. + pub fn from_one(dev: D) -> Self { + Self(vec![dev]) + } + + /// Adds one device into the container. + #[allow(dead_code)] + pub(crate) fn push(&mut self, dev: D) { + self.0.push(dev); + } +} + +impl core::ops::Deref for AxDeviceContainer { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Default for AxDeviceContainer { + fn default() -> Self { + Self(Default::default()) + } +} diff --git a/modules/axdriver/src/structs/mod.rs b/modules/axdriver/src/structs/mod.rs new file mode 100644 index 0000000..d373100 --- /dev/null +++ b/modules/axdriver/src/structs/mod.rs @@ -0,0 +1,51 @@ +#[cfg_attr(feature = "dyn", path = "dyn.rs")] +#[cfg_attr(not(feature = "dyn"), path = "static.rs")] +mod imp; + +use driver_common::{BaseDriverOps, DeviceType}; + +pub use imp::*; + +/// A unified enum that represents different categories of devices. +#[allow(clippy::large_enum_variant)] +pub enum AxDeviceEnum { + /// Network card device. + #[cfg(feature = "net")] + Net(AxNetDevice), + /// Block storage device. + #[cfg(feature = "block")] + Block(AxBlockDevice), + /// Graphic display device. + #[cfg(feature = "display")] + Display(AxDisplayDevice), +} + +impl BaseDriverOps for AxDeviceEnum { + #[inline] + #[allow(unreachable_patterns)] + fn device_type(&self) -> DeviceType { + match self { + #[cfg(feature = "net")] + Self::Net(_) => DeviceType::Net, + #[cfg(feature = "block")] + Self::Block(_) => DeviceType::Block, + #[cfg(feature = "display")] + Self::Display(_) => DeviceType::Display, + _ => unreachable!(), + } + } + + #[inline] + #[allow(unreachable_patterns)] + fn device_name(&self) -> &str { + match self { + #[cfg(feature = "net")] + Self::Net(dev) => dev.device_name(), + #[cfg(feature = "block")] + Self::Block(dev) => dev.device_name(), + #[cfg(feature = "display")] + Self::Display(dev) => dev.device_name(), + _ => unreachable!(), + } + } +} diff --git a/modules/axdriver/src/structs/static.rs b/modules/axdriver/src/structs/static.rs new file mode 100644 index 0000000..9adff2c --- /dev/null +++ b/modules/axdriver/src/structs/static.rs @@ -0,0 +1,79 @@ +#[cfg(feature = "block")] +pub use crate::drivers::AxBlockDevice; +#[cfg(feature = "display")] +pub use crate::drivers::AxDisplayDevice; +#[cfg(feature = "net")] +pub use crate::drivers::AxNetDevice; + +impl super::AxDeviceEnum { + /// Constructs a network device. + #[cfg(feature = "net")] + pub const fn from_net(dev: AxNetDevice) -> Self { + Self::Net(dev) + } + + /// Constructs a block device. + #[cfg(feature = "block")] + pub const fn from_block(dev: AxBlockDevice) -> Self { + Self::Block(dev) + } + + /// Constructs a display device. + #[cfg(feature = "display")] + pub const fn from_display(dev: AxDisplayDevice) -> Self { + Self::Display(dev) + } +} + +/// A structure that contains all device drivers of a certain category. +/// +/// If the feature `dyn` is enabled, the inner type is [`Vec`]. Otherwise, +/// the inner type is [`Option`] and at most one device can be contained. +pub struct AxDeviceContainer(Option); + +impl AxDeviceContainer { + /// Returns number of devices in this container. + pub const fn len(&self) -> usize { + if self.0.is_some() { + 1 + } else { + 0 + } + } + + /// Returns whether the container is empty. + pub const fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Takes one device out of the container (will remove it from the container). + pub fn take_one(&mut self) -> Option { + self.0.take() + } + + /// Constructs the container from one device. + pub const fn from_one(dev: D) -> Self { + Self(Some(dev)) + } + + /// Adds one device into the container. + #[allow(dead_code)] + pub(crate) fn push(&mut self, dev: D) { + if self.0.is_none() { + self.0 = Some(dev); + } + } +} + +impl core::ops::Deref for AxDeviceContainer { + type Target = Option; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Default for AxDeviceContainer { + fn default() -> Self { + Self(Default::default()) + } +} diff --git a/modules/axdriver/src/virtio.rs b/modules/axdriver/src/virtio.rs new file mode 100644 index 0000000..42999b0 --- /dev/null +++ b/modules/axdriver/src/virtio.rs @@ -0,0 +1,172 @@ +use core::marker::PhantomData; +use core::ptr::NonNull; + +use axalloc::global_allocator; +use axhal::mem::{phys_to_virt, virt_to_phys}; +use cfg_if::cfg_if; +use driver_common::{BaseDriverOps, DevResult, DeviceType}; +use driver_virtio::{BufferDirection, PhysAddr, VirtIoHal}; + +use crate::{drivers::DriverProbe, AxDeviceEnum}; + +cfg_if! { + if #[cfg(bus = "pci")] { + use driver_pci::{PciRoot, DeviceFunction, DeviceFunctionInfo}; + type VirtIoTransport = driver_virtio::PciTransport; + } else if #[cfg(bus = "mmio")] { + type VirtIoTransport = driver_virtio::MmioTransport; + } +} + +/// A trait for VirtIO device meta information. +pub trait VirtIoDevMeta { + const DEVICE_TYPE: DeviceType; + + type Device: BaseDriverOps; + type Driver = VirtIoDriver; + + fn try_new(transport: VirtIoTransport) -> DevResult; +} + +cfg_if! { + if #[cfg(net_dev = "virtio-net")] { + pub struct VirtIoNet; + + impl VirtIoDevMeta for VirtIoNet { + const DEVICE_TYPE: DeviceType = DeviceType::Net; + type Device = driver_virtio::VirtIoNetDev; + + fn try_new(transport: VirtIoTransport) -> DevResult { + Ok(AxDeviceEnum::from_net(Self::Device::try_new(transport)?)) + } + } + } +} + +cfg_if! { + if #[cfg(block_dev = "virtio-blk")] { + pub struct VirtIoBlk; + + impl VirtIoDevMeta for VirtIoBlk { + const DEVICE_TYPE: DeviceType = DeviceType::Block; + type Device = driver_virtio::VirtIoBlkDev; + + fn try_new(transport: VirtIoTransport) -> DevResult { + Ok(AxDeviceEnum::from_block(Self::Device::try_new(transport)?)) + } + } + } +} + +cfg_if! { + if #[cfg(display_dev = "virtio-gpu")] { + pub struct VirtIoGpu; + + impl VirtIoDevMeta for VirtIoGpu { + const DEVICE_TYPE: DeviceType = DeviceType::Display; + type Device = driver_virtio::VirtIoGpuDev; + + fn try_new(transport: VirtIoTransport) -> DevResult { + Ok(AxDeviceEnum::from_display(Self::Device::try_new(transport)?)) + } + } + } +} + +/// A common driver for all VirtIO devices that implements [`DriverProbe`]. +pub struct VirtIoDriver(PhantomData); + +impl DriverProbe for VirtIoDriver { + #[cfg(bus = "mmio")] + fn probe_mmio(mmio_base: usize, mmio_size: usize) -> Option { + let base_vaddr = phys_to_virt(mmio_base.into()); + if let Some((ty, transport)) = + driver_virtio::probe_mmio_device(base_vaddr.as_mut_ptr(), mmio_size) + { + if ty == D::DEVICE_TYPE { + match D::try_new(transport) { + Ok(dev) => return Some(dev), + Err(e) => { + warn!( + "failed to initialize MMIO device at [PA:{:#x}, PA:{:#x}): {:?}", + mmio_base, + mmio_base + mmio_size, + e + ); + return None; + } + } + } + } + None + } + + #[cfg(bus = "pci")] + fn probe_pci( + root: &mut PciRoot, + bdf: DeviceFunction, + dev_info: &DeviceFunctionInfo, + ) -> Option { + if dev_info.vendor_id != 0x1af4 { + return None; + } + match (D::DEVICE_TYPE, dev_info.device_id) { + (DeviceType::Net, 0x1000) | (DeviceType::Net, 0x1040) => {} + (DeviceType::Block, 0x1001) | (DeviceType::Block, 0x1041) => {} + (DeviceType::Display, 0x1050) => {} + _ => return None, + } + + if let Some((ty, transport)) = + driver_virtio::probe_pci_device::(root, bdf, dev_info) + { + if ty == D::DEVICE_TYPE { + match D::try_new(transport) { + Ok(dev) => return Some(dev), + Err(e) => { + warn!( + "failed to initialize PCI device at {}({}): {:?}", + bdf, dev_info, e + ); + return None; + } + } + } + } + None + } +} + +pub struct VirtIoHalImpl; + +unsafe impl VirtIoHal for VirtIoHalImpl { + fn dma_alloc(pages: usize, _direction: BufferDirection) -> (PhysAddr, NonNull) { + let vaddr = if let Ok(vaddr) = global_allocator().alloc_pages(pages, 0x1000) { + vaddr + } else { + return (0, NonNull::dangling()); + }; + let paddr = virt_to_phys(vaddr.into()); + let ptr = NonNull::new(vaddr as _).unwrap(); + (paddr.as_usize(), ptr) + } + + unsafe fn dma_dealloc(_paddr: PhysAddr, vaddr: NonNull, pages: usize) -> i32 { + global_allocator().dealloc_pages(vaddr.as_ptr() as usize, pages); + 0 + } + + #[inline] + unsafe fn mmio_phys_to_virt(paddr: PhysAddr, _size: usize) -> NonNull { + NonNull::new(phys_to_virt(paddr.into()).as_mut_ptr()).unwrap() + } + + #[inline] + unsafe fn share(buffer: NonNull<[u8]>, _direction: BufferDirection) -> PhysAddr { + let vaddr = buffer.as_ptr() as *mut u8 as usize; + virt_to_phys(vaddr.into()).into() + } + + #[inline] + unsafe fn unshare(_paddr: PhysAddr, _buffer: NonNull<[u8]>, _direction: BufferDirection) {} +} diff --git a/modules/axfs/.gitignore b/modules/axfs/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/modules/axfs/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/modules/axfs/Cargo.toml b/modules/axfs/Cargo.toml new file mode 100644 index 0000000..26548f1 --- /dev/null +++ b/modules/axfs/Cargo.toml @@ -0,0 +1,66 @@ +[package] +name = "axfs" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "ArceOS filesystem module" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axfs" +documentation = "https://rcore-os.github.io/arceos/axfs/index.html" +keywords = ["Starry"] + +[features] +devfs = ["dep:axfs_devfs"] +ramfs = ["dep:axfs_ramfs"] +procfs = ["dep:axfs_ramfs"] +sysfs = ["dep:axfs_ramfs", "dep:axconfig"] +myfs = ["dep:crate_interface"] +use-ramdisk = [] +monolithic = [] +fatfs = ["dep:fatfs"] +# Use lwext4fs as the default filesystem +lwext4_rust = ["dep:lwext4_rust", "devfs", "ramfs", "procfs", "sysfs"] +# Use ext4rs as the default filesystem +ext4_rs = ["dep:ext4_rs", "devfs", "ramfs", "procfs", "sysfs"] +# Use another_ext4 as the default filesystem +another_ext4 = ["dep:another_ext4", "devfs", "ramfs", "procfs", "sysfs"] +default = ["devfs", "ramfs", "fatfs", "procfs", "sysfs"] + +[dependencies] +log = "0.4" +cfg-if = "1.0" +lazy_init = { git = "https://github.com/Starry-OS/lazy_init.git" } +capability = { git = "https://github.com/Starry-OS/capability.git" } +driver_block = { git = "https://github.com/Starry-OS/driver_block.git" } +axio = { git = "https://github.com/Starry-OS/axio.git", features = ["alloc"] } +axerrno = { git = "https://github.com/Starry-OS/axerrno.git" } +axconfig = { workspace = true, optional = true } +axfs_vfs = { git = "https://github.com/Starry-OS/axfs_vfs.git" } +axfs_devfs = { git = "https://github.com/Starry-OS/axfs_devfs.git", optional = true } +axfs_ramfs = { git = "https://github.com/Starry-OS/axfs_ramfs.git", optional = true } +ext4_rs = { git = "https://github.com/yuoo655/ext4_rs.git", rev= "6bcc7f5", optional = true } +lwext4_rust = { git = "https://github.com/elliott10/lwext4_rust.git", optional = true } +another_ext4 = { git = "https://github.com/Starry-OS/ljx_ext4.git", branch = "main", features = ["block_cache"], optional = true } +axdriver = { workspace = true, features = ["block"] } +axsync = { workspace = true } +crate_interface = { git = "https://github.com/Starry-OS/crate_interface.git", optional = true } +bitflags = "2.6" + +[dependencies.fatfs] +git = "https://github.com/rafalh/rust-fatfs" +rev = "85f06e0" +optional = true +default-features = false +features = [ # no std + "alloc", + "lfn", + "log_level_trace", + "unicode", +] + +[dev-dependencies] +axdriver = { workspace = true, features = ["block", "ramdisk"] } +driver_block = { git = "https://github.com/Starry-OS/driver_block.git", features = ["ramdisk"] } +axsync = { workspace = true, features = ["multitask"] } +axtask = { workspace = true, features = ["test"] } diff --git a/modules/axfs/resources/create_test_img.sh b/modules/axfs/resources/create_test_img.sh new file mode 100644 index 0000000..d51cef8 --- /dev/null +++ b/modules/axfs/resources/create_test_img.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# From https://github.com/rafalh/rust-fatfs/blob/master/scripts/create-test-img.sh + +CUR_DIR=`dirname $0` + +echo $OUT_DIR + +create_test_img() { + local name=$1 + local blkcount=$2 + local fatSize=$3 + dd if=/dev/zero of="$name" bs=1024 count=$blkcount + mkfs.vfat -s 1 -F $fatSize -n "Test!" -i 12345678 "$name" + mkdir -p mnt + sudo mount -o loop "$name" mnt -o rw,uid=$USER,gid=$USER + for i in $(seq 1 1000); do + echo "Rust is cool!" >>"mnt/long.txt" + done + echo "Rust is cool!" >>"mnt/short.txt" + mkdir -p "mnt/very/long/path" + echo "Rust is cool!" >>"mnt/very/long/path/test.txt" + mkdir -p "mnt/very-long-dir-name" + echo "Rust is cool!" >>"mnt/very-long-dir-name/very-long-file-name.txt" + + sudo umount mnt +} + +create_test_img "$CUR_DIR/fat16.img" 2500 16 +create_test_img "$CUR_DIR/fat32.img" 34000 32 diff --git a/modules/axfs/src/api/dir.rs b/modules/axfs/src/api/dir.rs new file mode 100644 index 0000000..55ad5ab --- /dev/null +++ b/modules/axfs/src/api/dir.rs @@ -0,0 +1,150 @@ +use alloc::string::String; +use axio::Result; +use core::fmt; + +use super::FileType; +use crate::fops; + +/// Iterator over the entries in a directory. +pub struct ReadDir<'a> { + path: &'a str, + inner: fops::Directory, + buf_pos: usize, + buf_end: usize, + end_of_stream: bool, + dirent_buf: [fops::DirEntry; 31], +} + +/// Entries returned by the [`ReadDir`] iterator. +pub struct DirEntry<'a> { + dir_path: &'a str, + entry_name: String, + entry_type: FileType, +} + +/// A builder used to create directories in various manners. +#[derive(Default, Debug)] +pub struct DirBuilder { + recursive: bool, +} + +impl<'a> ReadDir<'a> { + pub(super) fn new(path: &'a str) -> Result { + let mut opts = fops::OpenOptions::new(); + opts.read(true); + let inner = fops::Directory::open_dir(path, &opts)?; + const EMPTY: fops::DirEntry = fops::DirEntry::default(); + let dirent_buf = [EMPTY; 31]; + Ok(ReadDir { + path, + inner, + end_of_stream: false, + buf_pos: 0, + buf_end: 0, + dirent_buf, + }) + } +} + +impl<'a> Iterator for ReadDir<'a> { + type Item = Result>; + + fn next(&mut self) -> Option>> { + if self.end_of_stream { + return None; + } + + loop { + if self.buf_pos >= self.buf_end { + match self.inner.read_dir(&mut self.dirent_buf) { + Ok(n) => { + if n == 0 { + self.end_of_stream = true; + return None; + } + self.buf_pos = 0; + self.buf_end = n; + } + Err(e) => { + self.end_of_stream = true; + return Some(Err(e)); + } + } + } + let entry = &self.dirent_buf[self.buf_pos]; + self.buf_pos += 1; + let name_bytes = entry.name_as_bytes(); + if name_bytes == b"." || name_bytes == b".." { + continue; + } + let entry_name = unsafe { core::str::from_utf8_unchecked(name_bytes).into() }; + let entry_type = entry.entry_type(); + + return Some(Ok(DirEntry { + dir_path: self.path, + entry_name, + entry_type, + })); + } + } +} + +impl<'a> DirEntry<'a> { + /// Returns the full path to the file that this entry represents. + /// + /// The full path is created by joining the original path to `read_dir` + /// with the filename of this entry. + pub fn path(&self) -> String { + String::from(self.dir_path.trim_end_matches('/')) + "/" + &self.entry_name + } + + /// Returns the bare file name of this directory entry without any other + /// leading path component. + pub fn file_name(&self) -> String { + self.entry_name.clone() + } + + /// Returns the file type for the file that this entry points at. + pub fn file_type(&self) -> FileType { + self.entry_type + } +} + +impl fmt::Debug for DirEntry<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("DirEntry").field(&self.path()).finish() + } +} + +impl DirBuilder { + /// Creates a new set of options with default mode/security settings for all + /// platforms and also non-recursive. + pub fn new() -> Self { + Self { recursive: false } + } + + /// Indicates that directories should be created recursively, creating all + /// parent directories. Parents that do not exist are created with the same + /// security and permissions settings. + pub fn recursive(&mut self, recursive: bool) -> &mut Self { + self.recursive = recursive; + self + } + + /// Creates the specified directory with the options configured in this + /// builder. + pub fn create(&self, path: &str) -> Result<()> { + if self.recursive { + self.create_dir_all(path) + } else { + crate::root::create_dir(None, path) + } + } + + fn create_dir_all(&self, _path: &str) -> Result<()> { + axerrno::ax_err!( + Unsupported, + "Recursive directory creation is not supported yet" + ) + } +} diff --git a/modules/axfs/src/api/file.rs b/modules/axfs/src/api/file.rs new file mode 100644 index 0000000..a8abd57 --- /dev/null +++ b/modules/axfs/src/api/file.rs @@ -0,0 +1,223 @@ +use axio::{prelude::*, Result, SeekFrom}; +use core::fmt; + +use super::FileExt; +use crate::fops; + +/// A structure representing a type of file with accessors for each file type. +/// It is returned by [`Metadata::file_type`] method. +pub type FileType = fops::FileType; + +/// Representation of the various permissions on a file. +pub type Permissions = fops::FilePerm; + +/// An object providing access to an open file on the filesystem. +#[derive(Clone)] +pub struct File { + inner: fops::File, +} + +/// Metadata information about a file. +pub struct Metadata(fops::FileAttr); + +/// Options and flags which can be used to configure how a file is opened. +#[derive(Clone, Debug)] +pub struct OpenOptions(fops::OpenOptions); + +impl OpenOptions { + /// Creates a blank new set of options ready for configuration. + pub const fn new() -> Self { + OpenOptions(fops::OpenOptions::new()) + } + + /// Sets the option for read access. + pub fn read(&mut self, read: bool) -> &mut Self { + self.0.read(read); + self + } + + /// Sets the option for write access. + pub fn write(&mut self, write: bool) -> &mut Self { + self.0.write(write); + self + } + + /// Sets the option for the append mode. + pub fn append(&mut self, append: bool) -> &mut Self { + self.0.append(append); + self + } + + /// Sets the option for truncating a previous file. + pub fn truncate(&mut self, truncate: bool) -> &mut Self { + self.0.truncate(truncate); + self + } + + /// Sets the option to create a new file, or open it if it already exists. + pub fn create(&mut self, create: bool) -> &mut Self { + self.0.create(create); + self + } + + /// Sets the option to create a new file, failing if it already exists. + pub fn create_new(&mut self, create_new: bool) -> &mut Self { + self.0.create_new(create_new); + self + } + + /// Opens a file at `path` with the options specified by `self`. + pub fn open(&self, path: &str) -> Result { + fops::File::open(path, &self.0).map(|inner| File { inner }) + } +} + +impl Metadata { + /// Returns the file type for this metadata. + pub const fn file_type(&self) -> FileType { + self.0.file_type() + } + + /// Returns `true` if this metadata is for a directory. The + /// result is mutually exclusive to the result of + /// [`Metadata::is_file`]. + pub const fn is_dir(&self) -> bool { + self.0.is_dir() + } + + /// Returns `true` if this metadata is for a regular file. The + /// result is mutually exclusive to the result of + /// [`Metadata::is_dir`]. + pub const fn is_file(&self) -> bool { + self.0.is_file() + } + + /// Returns the size of the file, in bytes, this metadata is for. + #[allow(clippy::len_without_is_empty)] + pub const fn len(&self) -> u64 { + self.0.size() + } + + /// Returns the permissions of the file this metadata is for. + pub const fn permissions(&self) -> Permissions { + self.0.perm() + } + + /// Sets the permissions of the file this metadata is for. + pub fn set_permissions(&mut self, perm: Permissions) { + self.0.set_perm(perm) + } + + /// Returns the total size of this file in bytes. + pub const fn size(&self) -> u64 { + self.0.size() + } + + /// Returns the number of blocks allocated to the file, in 512-byte units. + pub const fn blocks(&self) -> u64 { + self.0.blocks() + } +} + +impl fmt::Debug for Metadata { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Metadata") + .field("file_type", &self.file_type()) + .field("is_dir", &self.is_dir()) + .field("is_file", &self.is_file()) + .field("permissions", &self.permissions()) + .finish_non_exhaustive() + } +} + +impl File { + /// Attempts to open a file in read-only mode. + pub fn open(path: &str) -> Result { + OpenOptions::new().read(true).open(path) + } + + /// Opens a file in write-only mode. + pub fn create(path: &str) -> Result { + OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(path) + } + + /// Creates a new file in read-write mode; error if the file exists. + pub fn create_new(path: &str) -> Result { + OpenOptions::new() + .read(true) + .write(true) + .create_new(true) + .open(path) + } + + /// Returns a new OpenOptions object. + pub fn options() -> OpenOptions { + OpenOptions::new() + } + + /// Truncates or extends the underlying file, updating the size of + /// this file to become `size`. + pub fn set_len(&self, size: u64) -> Result<()> { + self.inner.truncate(size) + } + + /// Queries metadata about the underlying file. + pub fn metadata(&self) -> Result { + self.inner.get_attr().map(Metadata) + } + + /// Whether the file is executable. + pub fn executable(&self) -> bool { + self.inner.executable() + } + + /// Get the file attributes. + pub fn get_attr(&self) -> Result { + self.inner.get_attr() + } + + /// To truncate the file to a specified length. + pub fn truncate(&mut self, len: usize) -> Result<()> { + self.inner.truncate(len as u64) + } +} + +impl Read for File { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner.read(buf) + } +} + +impl Write for File { + fn write(&mut self, buf: &[u8]) -> Result { + self.inner.write(buf) + } + + fn flush(&mut self) -> Result<()> { + self.inner.flush() + } +} + +impl Seek for File { + fn seek(&mut self, pos: SeekFrom) -> Result { + self.inner.seek(pos) + } +} + +impl FileExt for File { + fn readable(&self) -> bool { + self.inner.readable() + } + + fn writable(&self) -> bool { + self.inner.writable() + } + + fn executable(&self) -> bool { + self.inner.executable() + } +} diff --git a/modules/axfs/src/api/mod.rs b/modules/axfs/src/api/mod.rs new file mode 100644 index 0000000..fec6480 --- /dev/null +++ b/modules/axfs/src/api/mod.rs @@ -0,0 +1,106 @@ +//! [`std::fs`]-like high-level filesystem manipulation operations. + +mod dir; +mod file; + +pub mod port; + +pub use self::dir::{DirBuilder, DirEntry, ReadDir}; +pub use self::file::{File, FileType, Metadata, OpenOptions, Permissions}; +use axerrno::AxResult; +use axfs_vfs::VfsNodeRef; +pub use axio::{Read, Seek, SeekFrom, Write}; +pub use port::*; + +use alloc::{string::String, vec::Vec}; +#[allow(unused_imports)] +use axio::{self as io, prelude::*}; + +/// Returns an iterator over the entries within a directory. +pub fn read_dir(path: &str) -> io::Result { + ReadDir::new(path) +} + +/// Returns the canonical, absolute form of a path with all intermediate +/// components normalized. +pub fn canonicalize(path: &str) -> io::Result { + crate::root::absolute_path(path) +} + +/// Returns the current working directory as a [`String`]. +pub fn current_dir() -> io::Result { + crate::root::current_dir() +} + +/// Changes the current working directory to the specified path. +pub fn set_current_dir(path: &str) -> io::Result<()> { + crate::root::set_current_dir(path) +} + +/// Read the entire contents of a file into a bytes vector. +pub fn read(path: &str) -> io::Result> { + let mut file = File::open(path)?; + let size = file.metadata().map(|m| m.len()).unwrap_or(0); + let mut bytes = Vec::with_capacity(size as usize); + file.read_to_end(&mut bytes)?; + Ok(bytes) +} + +/// Read the entire contents of a file into a string. +pub fn read_to_string(path: &str) -> io::Result { + let mut file = File::open(path)?; + let size = file.metadata().map(|m| m.len()).unwrap_or(0); + let mut string = String::with_capacity(size as usize); + file.read_to_string(&mut string)?; + Ok(string) +} + +/// Write a slice as the entire contents of a file. +pub fn write>(path: &str, contents: C) -> io::Result<()> { + File::create(path)?.write_all(contents.as_ref()) +} + +/// Given a path, query the file system to get information about a file, +/// directory, etc. +pub fn metadata(path: &str) -> io::Result { + File::open(path)?.metadata() +} + +/// Creates a new, empty directory at the provided path. +pub fn create_dir(path: &str) -> io::Result<()> { + DirBuilder::new().create(path) +} + +/// Recursively create a directory and all of its parent components if they +/// are missing. +pub fn create_dir_all(path: &str) -> io::Result<()> { + DirBuilder::new().recursive(true).create(path) +} + +/// Removes an empty directory. +pub fn remove_dir(path: &str) -> io::Result<()> { + crate::root::remove_dir(None, path) +} + +/// Removes a file from the filesystem. +pub fn remove_file(path: &str) -> io::Result<()> { + crate::root::remove_file(None, path) +} + +/// Rename a file or directory to a new name. +/// Delete the original file if `old` already exists. +/// +/// This only works then the new path is in the same mounted fs. +pub fn rename(old: &str, new: &str) -> io::Result<()> { + crate::root::rename(old, new) +} + +/// Check if a path exists. +pub fn path_exists(path: &str) -> bool { + crate::root::lookup(None, path).is_ok() +} + +/// Look up a file by a given path. +pub fn lookup(path: &str) -> AxResult { + crate::root::lookup(None, path) +} diff --git a/modules/axfs/src/api/port.rs b/modules/axfs/src/api/port.rs new file mode 100644 index 0000000..7965ce9 --- /dev/null +++ b/modules/axfs/src/api/port.rs @@ -0,0 +1,420 @@ +//! 定义与文件I/O操作相关的trait泛型 +extern crate alloc; +use alloc::string::String; +use axerrno::{AxError, AxResult}; +use axio::{Read, Seek, SeekFrom, Write}; +use core::any::Any; +use log::debug; + +/// 文件系统信息 +#[derive(Debug, Clone, Copy, Default)] +#[repr(C)] +#[cfg(target_arch = "x86_64")] +pub struct Kstat { + /// 设备 + pub st_dev: u64, + /// inode 编号 + pub st_ino: u64, + /// 硬链接数 + pub st_nlink: u64, + /// 文件类型 + pub st_mode: u32, + /// 用户id + pub st_uid: u32, + /// 用户组id + pub st_gid: u32, + /// padding + pub _pad0: u32, + /// 设备号 + pub st_rdev: u64, + /// 文件大小 + pub st_size: u64, + /// 块大小 + pub st_blksize: u32, + /// padding + pub _pad1: u32, + /// 块个数 + pub st_blocks: u64, + /// 最后一次访问时间(秒) + pub st_atime_sec: isize, + /// 最后一次访问时间(纳秒) + pub st_atime_nsec: isize, + /// 最后一次修改时间(秒) + pub st_mtime_sec: isize, + /// 最后一次修改时间(纳秒) + pub st_mtime_nsec: isize, + /// 最后一次改变状态时间(秒) + pub st_ctime_sec: isize, + /// 最后一次改变状态时间(纳秒) + pub st_ctime_nsec: isize, +} +#[derive(Debug, Clone, Copy, Default)] +#[repr(C)] +#[cfg(not(target_arch = "x86_64"))] +pub struct Kstat { + /// 设备 + pub st_dev: u64, + /// inode 编号 + pub st_ino: u64, + /// 文件类型 + pub st_mode: u32, + /// 硬链接数 + pub st_nlink: u32, + /// 用户id + pub st_uid: u32, + /// 用户组id + pub st_gid: u32, + /// 设备号 + pub st_rdev: u64, + /// padding + pub _pad0: u64, + /// 文件大小 + pub st_size: u64, + /// 块大小 + pub st_blksize: u32, + /// padding + pub _pad1: u32, + /// 块个数 + pub st_blocks: u64, + /// 最后一次访问时间(秒) + pub st_atime_sec: isize, + /// 最后一次访问时间(纳秒) + pub st_atime_nsec: isize, + /// 最后一次修改时间(秒) + pub st_mtime_sec: isize, + /// 最后一次修改时间(纳秒) + pub st_mtime_nsec: isize, + /// 最后一次改变状态时间(秒) + pub st_ctime_sec: isize, + /// 最后一次改变状态时间(纳秒) + pub st_ctime_nsec: isize, +} + +use bitflags::*; + +bitflags! { + /// 指定文件打开时的权限 + #[derive(Clone, Copy, Default, Debug)] + pub struct OpenFlags: u32 { + /// 只读 + const RDONLY = 0; + /// 只能写入 + const WRONLY = 1 << 0; + /// 读写 + const RDWR = 1 << 1; + /// 如文件不存在,可创建它 + const CREATE = 1 << 6; + /// 确认一定是创建文件。如文件已存在,返回 EEXIST。 + const EXCLUSIVE = 1 << 7; + /// 使打开的文件不会成为该进程的控制终端。目前没有终端设置,不处理 + const NOCTTY = 1 << 8; + /// 同上,在不同的库中可能会用到这个或者上一个 + const TRUNC = 1 << 9; + /// 非阻塞读写?(虽然不知道为什么但 date.lua 也要) + /// 在 socket 中使用得较多 + const NON_BLOCK = 1 << 11; + /// 要求把 CR-LF 都换成 LF + const TEXT = 1 << 14; + /// 和上面不同,要求输入输出都不进行这个翻译 + const BINARY = 1 << 15; + /// 对这个文件的输出需符合 IO 同步一致性。可以理解为随时 fsync + const DSYNC = 1 << 16; + /// 如果是符号链接,不跟随符号链接去寻找文件,而是针对连接本身 + const NOFOLLOW = 1 << 17; + /// 在 exec 时需关闭 + const CLOEXEC = 1 << 19; + /// 是否是目录 + const DIR = 1 << 21; + } +} + +impl OpenFlags { + /// 获得文件的读/写权限 + pub fn read_write(&self) -> (bool, bool) { + if self.is_empty() { + (true, false) + } else if self.contains(Self::WRONLY) { + (false, true) + } else { + (true, true) + } + } + /// 获取读权限 + pub fn readable(&self) -> bool { + !self.contains(Self::WRONLY) + } + /// 获取写权限 + pub fn writable(&self) -> bool { + self.contains(Self::WRONLY) || self.contains(Self::RDWR) + } + + /// 获取创建权限 + pub fn creatable(&self) -> bool { + self.contains(Self::CREATE) + } + /// 获取创建新文件权限 + /// 与上面的区别是,如果文件已存在,返回 EEXIST + pub fn new_creatable(&self) -> bool { + self.contains(Self::EXCLUSIVE) + } + + /// 获取是否是目录 + pub fn is_dir(&self) -> bool { + self.contains(Self::DIR) + } + + /// 获取是否需要在 `exec()` 时关闭 + pub fn is_close_on_exec(&self) -> bool { + self.contains(Self::CLOEXEC) + } +} + +impl From for OpenFlags { + fn from(val: usize) -> Self { + Self::from_bits_truncate(val as u32) + } +} + +/// 文件类型 +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum FileIOType { + /// 文件 + FileDesc, + /// 目录 + DirDesc, + /// 标准输入 + Stdin, + /// 标准输出 + Stdout, + /// 标准错误 + Stderr, + /// 管道 + Pipe, + /// 链接 + Link, + /// Socket + Socket, + /// 其他 + Other, +} + +/// 用于给虚存空间进行懒分配 +pub trait FileExt: Read + Write + Seek + AsAny + Send + Sync { + /// whether the file is readable + fn readable(&self) -> bool; + + /// whether the file is writable + fn writable(&self) -> bool; + + /// whether the file is executable + fn executable(&self) -> bool; + /// Read from position without changing cursor. + fn read_from_seek(&mut self, pos: SeekFrom, buf: &mut [u8]) -> AxResult { + // get old position + let old_pos = self + .seek(SeekFrom::Current(0)) + .expect("Error get current pos in file"); + + // seek to read position + let _ = self.seek(pos).unwrap(); + + // read + let read_len = self.read_full(buf); + // seek back to old_pos + let new_pos = self.seek(SeekFrom::Start(old_pos)).unwrap(); + + assert_eq!(old_pos, new_pos); + + read_len + } + + /// Write to position without changing cursor. + fn write_to_seek(&mut self, pos: SeekFrom, buf: &[u8]) -> AxResult { + // get old position + let old_pos = self + .seek(SeekFrom::Current(0)) + .expect("Error get current pos in file"); + // seek to write position + let _ = self.seek(pos).unwrap(); + + let write_len = self.write(buf); + + // seek back to old_pos + let _ = self.seek(SeekFrom::Start(old_pos)).unwrap(); + + write_len + } +} + +/// File I/O trait. 文件I/O操作,用于设置文件描述符,值得注意的是,这里的read/write/seek都是不可变引用 +/// +/// 因为文件描述符读取的时候,是用到内部File成员的读取函数,自身应当为不可变,从而可以被Arc指针调用 +pub trait FileIO: AsAny + Send + Sync { + /// 读取操作 + fn read(&self, _buf: &mut [u8]) -> AxResult { + Err(AxError::Unsupported) // 如果没有实现, 则返回Unsupported + } + + /// 写入操作 + fn write(&self, _buf: &[u8]) -> AxResult { + Err(AxError::Unsupported) // 如果没有实现, 则返回Unsupported + } + + /// 刷新操作 + fn flush(&self) -> AxResult<()> { + Err(AxError::Unsupported) // 如果没有实现, 则返回Unsupported + } + + /// 移动指针操作 + fn seek(&self, _pos: SeekFrom) -> AxResult { + Err(AxError::Unsupported) // 如果没有实现, 则返回Unsupported + } + + /// whether the file is readable + fn readable(&self) -> bool; + + /// whether the file is writable + fn writable(&self) -> bool; + + /// whether the file is executable + fn executable(&self) -> bool; + + /// 获取类型 + fn get_type(&self) -> FileIOType; + + /// 获取路径 + fn get_path(&self) -> String { + debug!("Function get_path not implemented"); + String::from("Function get_path not implemented") + } + /// 获取文件信息 + fn get_stat(&self) -> AxResult { + Err(AxError::Unsupported) // 如果没有实现get_stat, 则返回Unsupported + } + + /// 截断文件到指定长度 + fn truncate(&self, _len: usize) -> AxResult<()> { + debug!("Function truncate not implemented"); + Err(AxError::Unsupported) + } + + /// debug + fn print_content(&self) { + debug!("Function print_content not implemented"); + } + + /// 设置文件状态 + fn set_status(&self, _flags: OpenFlags) -> bool { + false + } + + /// 获取文件状态 + fn get_status(&self) -> OpenFlags { + OpenFlags::empty() + } + + /// 设置 close_on_exec 位 + /// 设置成功返回false + fn set_close_on_exec(&self, _is_set: bool) -> bool { + false + } + + /// 处于“意外情况”。在 (p)select 和 (p)poll 中会使用到 + /// + /// 当前基本默认为false + fn in_exceptional_conditions(&self) -> bool { + false + } + + /// 是否已经终止,对pipe来说相当于另一端已经关闭 + /// + /// 对于其他文件类型来说,是在被close的时候终止,但这个时候已经没有对应的filedesc了,所以自然不会调用这个函数 + fn is_hang_up(&self) -> bool { + false + } + + /// 已准备好读。对于 pipe 来说,这意味着读端的buffer内有值 + fn ready_to_read(&self) -> bool { + false + } + /// 已准备好写。对于 pipe 来说,这意味着写端的buffer未满 + fn ready_to_write(&self) -> bool { + false + } + + /// To control the file descriptor + fn ioctl(&self, _request: usize, _arg1: usize) -> AxResult { + Err(AxError::Unsupported) + } +} + +/// `FileExt` 需要满足 `AsAny` 的要求,即可以转化为 `Any` 类型,从而能够进行向下类型转换。 +pub trait AsAny { + /// 把当前对象转化为 `Any` 类型,供后续 downcast 使用 + fn as_any(&self) -> &dyn Any; + /// 供 downcast_mut 使用 + fn as_any_mut(&mut self) -> &mut dyn Any; +} + +impl AsAny for T { + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} +bitflags! { + /// 指定文件打开时的权限 + #[derive(Clone, Copy)] + pub struct AccessMode: u16 { + /// 用户读权限 + const S_IRUSR = 1 << 8; + /// 用户写权限 + const S_IWUSR = 1 << 7; + /// 用户执行权限 + const S_IXUSR = 1 << 6; + /// 用户组读权限 + const S_IRGRP = 1 << 5; + /// 用户组写权限 + const S_IWGRP = 1 << 4; + /// 用户组执行权限 + const S_IXGRP = 1 << 3; + /// 其他用户读权限 + const S_IROTH = 1 << 2; + /// 其他用户写权限 + const S_IWOTH = 1 << 1; + /// 其他用户执行权限 + const S_IXOTH = 1 << 0; + } +} + +impl From for AccessMode { + fn from(val: usize) -> Self { + Self::from_bits_truncate(val as u16) + } +} + +/// IOCTL系统调用支持 +#[allow(missing_docs)] +pub const TCGETS: usize = 0x5401; +#[allow(missing_docs)] +pub const TIOCGPGRP: usize = 0x540F; +#[allow(missing_docs)] +pub const TIOCSPGRP: usize = 0x5410; +#[allow(missing_docs)] +pub const TIOCGWINSZ: usize = 0x5413; +#[allow(missing_docs)] +pub const FIONBIO: usize = 0x5421; +#[allow(missing_docs)] +pub const FIOCLEX: usize = 0x5451; +#[repr(C)] +#[derive(Clone, Copy, Default)] +/// the size of the console window +pub struct ConsoleWinSize { + ws_row: u16, + ws_col: u16, + ws_xpixel: u16, + ws_ypixel: u16, +} diff --git a/modules/axfs/src/dev.rs b/modules/axfs/src/dev.rs new file mode 100644 index 0000000..5977e04 --- /dev/null +++ b/modules/axfs/src/dev.rs @@ -0,0 +1,122 @@ +use axdriver::prelude::*; + +const BLOCK_SIZE: usize = 512; + +/// A disk device with a cursor. +pub struct Disk { + block_id: u64, + offset: usize, + dev: AxBlockDevice, +} + +#[allow(unused)] +impl Disk { + /// Create a new disk. + pub fn new(dev: AxBlockDevice) -> Self { + assert_eq!(BLOCK_SIZE, dev.block_size()); + Self { + block_id: 0, + offset: 0, + dev, + } + } + + /// Get the size of the disk. + pub fn size(&self) -> u64 { + self.dev.num_blocks() * BLOCK_SIZE as u64 + } + + /// Get the position of the cursor. + pub fn position(&self) -> u64 { + self.block_id * BLOCK_SIZE as u64 + self.offset as u64 + } + + /// Set the position of the cursor. + pub fn set_position(&mut self, pos: u64) { + self.block_id = pos / BLOCK_SIZE as u64; + self.offset = pos as usize % BLOCK_SIZE; + } + + /// Read within one block, returns the number of bytes read. + pub fn read_one(&mut self, buf: &mut [u8]) -> DevResult { + // info!("block id: {}", self.block_id); + let read_size = if self.offset == 0 && buf.len() >= BLOCK_SIZE { + // whole block + self.dev + .read_block(self.block_id, &mut buf[0..BLOCK_SIZE])?; + self.block_id += 1; + BLOCK_SIZE + } else { + // partial block + let mut data = [0u8; BLOCK_SIZE]; + let start = self.offset; + let count = buf.len().min(BLOCK_SIZE - self.offset); + if start > BLOCK_SIZE { + info!("block size: {} start {}", BLOCK_SIZE, start); + } + + self.dev.read_block(self.block_id, &mut data)?; + buf[..count].copy_from_slice(&data[start..start + count]); + + self.offset += count; + if self.offset >= BLOCK_SIZE { + self.block_id += 1; + self.offset -= BLOCK_SIZE; + } + count + }; + Ok(read_size) + } + + /// Write within one block, returns the number of bytes written. + pub fn write_one(&mut self, buf: &[u8]) -> DevResult { + let write_size = if self.offset == 0 && buf.len() >= BLOCK_SIZE { + // whole block + self.dev.write_block(self.block_id, &buf[0..BLOCK_SIZE])?; + self.block_id += 1; + BLOCK_SIZE + } else { + // partial block + let mut data = [0u8; BLOCK_SIZE]; + let start = self.offset; + let count = buf.len().min(BLOCK_SIZE - self.offset); + + self.dev.read_block(self.block_id, &mut data)?; + data[start..start + count].copy_from_slice(&buf[..count]); + self.dev.write_block(self.block_id, &data)?; + + self.offset += count; + if self.offset >= BLOCK_SIZE { + self.block_id += 1; + self.offset -= BLOCK_SIZE; + } + count + }; + Ok(write_size) + } + + /// Read a single block starting from the specified offset. + #[allow(unused)] + pub fn read_offset(&mut self, offset: usize) -> [u8; BLOCK_SIZE] { + let block_id = offset / BLOCK_SIZE; + let mut block_data = [0u8; BLOCK_SIZE]; + self.dev + .read_block(block_id as u64, &mut block_data) + .unwrap(); + block_data + } + + /// Write single block starting from the specified offset. + #[allow(unused)] + pub fn write_offset(&mut self, offset: usize, buf: &[u8]) -> DevResult { + assert!( + buf.len() == BLOCK_SIZE, + "Buffer length must be equal to BLOCK_SIZE" + ); + assert!(offset % BLOCK_SIZE == 0); + let block_id = offset / BLOCK_SIZE; + self.dev.write_block(block_id as u64, buf).unwrap(); + Ok(buf.len()) + } +} + diff --git a/modules/axfs/src/fops.rs b/modules/axfs/src/fops.rs new file mode 100644 index 0000000..8e9835a --- /dev/null +++ b/modules/axfs/src/fops.rs @@ -0,0 +1,420 @@ +//! Low-level filesystem operations. + +use axerrno::{ax_err, ax_err_type, AxResult}; +use axfs_vfs::{VfsError, VfsNodeRef}; +use axio::SeekFrom; +use capability::{Cap, WithCap}; +use core::fmt; + +#[cfg(feature = "myfs")] +pub use crate::dev::Disk; +#[cfg(feature = "myfs")] +pub use crate::fs::myfs::MyFileSystemIf; + +/// Alias of [`axfs_vfs::VfsNodeType`]. +pub type FileType = axfs_vfs::VfsNodeType; +/// Alias of [`axfs_vfs::VfsDirEntry`]. +pub type DirEntry = axfs_vfs::VfsDirEntry; +/// Alias of [`axfs_vfs::VfsNodeAttr`]. +pub type FileAttr = axfs_vfs::VfsNodeAttr; +/// Alias of [`axfs_vfs::VfsNodePerm`]. +pub type FilePerm = axfs_vfs::VfsNodePerm; + +/// An opened file object, with open permissions and a cursor. +#[derive(Clone)] +pub struct File { + node: WithCap, + is_append: bool, + offset: u64, +} + +/// An opened directory object, with open permissions and a cursor for +/// [`read_dir`](Directory::read_dir). +pub struct Directory { + node: WithCap, + entry_idx: usize, +} + +/// Options and flags which can be used to configure how a file is opened. +#[derive(Clone)] +pub struct OpenOptions { + // generic + read: bool, + write: bool, + append: bool, + truncate: bool, + create: bool, + create_new: bool, + // system-specific + _custom_flags: i32, + _mode: u32, +} + +impl OpenOptions { + /// Creates a blank new set of options ready for configuration. + pub const fn new() -> Self { + Self { + // generic + read: false, + write: false, + append: false, + truncate: false, + create: false, + create_new: false, + // system-specific + _custom_flags: 0, + _mode: 0o666, + } + } + /// Sets the option for read access. + pub fn read(&mut self, read: bool) { + self.read = read; + } + /// Sets the option for write access. + pub fn write(&mut self, write: bool) { + self.write = write; + } + /// Sets the option for the append mode. + pub fn append(&mut self, append: bool) { + self.append = append; + } + /// Sets the option for truncating a previous file. + pub fn truncate(&mut self, truncate: bool) { + self.truncate = truncate; + } + /// Sets the option to create a new file, or open it if it already exists. + pub fn create(&mut self, create: bool) { + self.create = create; + } + /// Sets the option to create a new file, failing if it already exists. + pub fn create_new(&mut self, create_new: bool) { + self.create_new = create_new; + } + + const fn is_valid(&self) -> bool { + if !self.read && !self.write && !self.append { + return false; + } + match (self.write, self.append) { + (true, false) => {} + (false, false) => { + if self.truncate || self.create || self.create_new { + return false; + } + } + (_, true) => { + if self.truncate && !self.create_new { + return false; + } + } + } + true + } +} + +impl File { + fn _open_at(dir: Option<&VfsNodeRef>, path: &str, opts: &OpenOptions) -> AxResult { + debug!("open file: {} {:?}", path, opts); + if !opts.is_valid() { + return ax_err!(InvalidInput); + } + + let node_option = crate::root::lookup(dir, path); + let node = if opts.create || opts.create_new { + match node_option { + Ok(node) => { + // already exists + if opts.create_new { + return ax_err!(AlreadyExists); + } + node + } + // not exists, create new + Err(VfsError::NotFound) => crate::root::create_file(dir, path)?, + Err(e) => return Err(e), + } + } else { + // just open the existing + node_option? + }; + let attr = node.get_attr()?; + if attr.is_dir() + && (opts.create || opts.create_new || opts.write || opts.append || opts.truncate) + { + return ax_err!(IsADirectory); + } + let access_cap = opts.into(); + if !perm_to_cap(attr.perm()).contains(access_cap) { + return ax_err!(PermissionDenied); + } + node.open()?; + if opts.truncate { + node.truncate(0)?; + } + Ok(Self { + node: WithCap::new(node, access_cap), + is_append: opts.append, + offset: 0, + }) + } + + /// Opens a file at the path relative to the current directory. Returns a + /// [`File`] object. + pub fn open(path: &str, opts: &OpenOptions) -> AxResult { + Self::_open_at(None, path, opts) + } + + /// Truncates the file to the specified size. + pub fn truncate(&self, size: u64) -> AxResult { + self.node.access(Cap::WRITE)?.truncate(size)?; + Ok(()) + } + + /// Reads the file at the current position. Returns the number of bytes + /// read. + /// + /// After the read, the cursor will be advanced by the number of bytes read. + pub fn read(&mut self, buf: &mut [u8]) -> AxResult { + let node = self.node.access(Cap::READ)?; + let read_len = node.read_at(self.offset, buf)?; + self.offset += read_len as u64; + Ok(read_len) + } + + /// Reads the file at the given position. Returns the number of bytes read. + /// + /// It does not update the file cursor. + pub fn read_at(&self, offset: u64, buf: &mut [u8]) -> AxResult { + let node = self.node.access(Cap::READ)?; + let read_len = node.read_at(offset, buf)?; + Ok(read_len) + } + + /// Writes the file at the current position. Returns the number of bytes + /// written. + /// + /// After the write, the cursor will be advanced by the number of bytes + /// written. + pub fn write(&mut self, buf: &[u8]) -> AxResult { + let node = self.node.access(Cap::WRITE)?; + if self.is_append { + self.offset = self.get_attr()?.size(); + }; + let write_len = node.write_at(self.offset, buf)?; + self.offset += write_len as u64; + Ok(write_len) + } + + /// Writes the file at the given position. Returns the number of bytes + /// written. + /// + /// It does not update the file cursor. + pub fn write_at(&self, offset: u64, buf: &[u8]) -> AxResult { + let node = self.node.access(Cap::WRITE)?; + let write_len = node.write_at(offset, buf)?; + Ok(write_len) + } + + /// Flushes the file, writes all buffered data to the underlying device. + pub fn flush(&self) -> AxResult { + self.node.access(Cap::WRITE)?.fsync()?; + Ok(()) + } + + /// Sets the cursor of the file to the specified offset. Returns the new + /// position after the seek. + pub fn seek(&mut self, pos: SeekFrom) -> AxResult { + let size = self.get_attr()?.size(); + let new_offset = match pos { + SeekFrom::Start(pos) => Some(pos), + SeekFrom::Current(off) => self.offset.checked_add_signed(off), + SeekFrom::End(off) => size.checked_add_signed(off), + } + .ok_or_else(|| ax_err_type!(InvalidInput))?; + self.offset = new_offset; + Ok(new_offset) + } + + /// Gets the file attributes. + pub fn get_attr(&self) -> AxResult { + self.node.access(Cap::empty())?.get_attr() + } + + #[allow(unused)] + /// whether the file is readable. + pub fn readable(&self) -> bool { + self.node.can_access(Cap::READ) + } + + #[allow(unused)] + /// whether the file is writable. + pub fn writable(&self) -> bool { + self.node.can_access(Cap::WRITE) + } + + #[allow(unused)] + /// whether the file is executable. + pub fn executable(&self) -> bool { + self.node.can_access(Cap::EXECUTE) + } +} + +impl Directory { + fn _open_dir_at(dir: Option<&VfsNodeRef>, path: &str, opts: &OpenOptions) -> AxResult { + debug!("open dir: {}", path); + if !opts.read { + return ax_err!(InvalidInput); + } + if opts.create || opts.create_new || opts.write || opts.append || opts.truncate { + return ax_err!(InvalidInput); + } + + let node = crate::root::lookup(dir, path)?; + let attr = node.get_attr()?; + if !attr.is_dir() { + return ax_err!(NotADirectory); + } + let access_cap = opts.into(); + if !perm_to_cap(attr.perm()).contains(access_cap) { + return ax_err!(PermissionDenied); + } + + node.open()?; + Ok(Self { + node: WithCap::new(node, access_cap), + entry_idx: 0, + }) + } + + fn access_at(&self, path: &str) -> AxResult> { + if path.starts_with('/') { + Ok(None) + } else { + Ok(Some(self.node.access(Cap::EXECUTE)?)) + } + } + + /// Opens a directory at the path relative to the current directory. + /// Returns a [`Directory`] object. + pub fn open_dir(path: &str, opts: &OpenOptions) -> AxResult { + Self::_open_dir_at(None, path, opts) + } + + /// Opens a directory at the path relative to this directory. Returns a + /// [`Directory`] object. + pub fn open_dir_at(&self, path: &str, opts: &OpenOptions) -> AxResult { + Self::_open_dir_at(self.access_at(path)?, path, opts) + } + + /// Opens a file at the path relative to this directory. Returns a [`File`] + /// object. + pub fn open_file_at(&self, path: &str, opts: &OpenOptions) -> AxResult { + File::_open_at(self.access_at(path)?, path, opts) + } + + /// Creates an empty file at the path relative to this directory. + pub fn create_file(&self, path: &str) -> AxResult { + crate::root::create_file(self.access_at(path)?, path) + } + + /// Creates an empty directory at the path relative to this directory. + pub fn create_dir(&self, path: &str) -> AxResult { + crate::root::create_dir(self.access_at(path)?, path) + } + + /// Removes a file at the path relative to this directory. + pub fn remove_file(&self, path: &str) -> AxResult { + crate::root::remove_file(self.access_at(path)?, path) + } + + /// Removes a directory at the path relative to this directory. + pub fn remove_dir(&self, path: &str) -> AxResult { + crate::root::remove_dir(self.access_at(path)?, path) + } + + /// Reads directory entries starts from the current position into the + /// given buffer. Returns the number of entries read. + /// + /// After the read, the cursor will be advanced by the number of entries + /// read. + pub fn read_dir(&mut self, dirents: &mut [DirEntry]) -> AxResult { + let n = self + .node + .access(Cap::READ)? + .read_dir(self.entry_idx, dirents)?; + self.entry_idx += n; + Ok(n) + } + + /// Rename a file or directory to a new name. + /// Delete the original file if `old` already exists. + /// + /// This only works then the new path is in the same mounted fs. + pub fn rename(&self, old: &str, new: &str) -> AxResult { + crate::root::rename(old, new) + } +} + +impl Drop for File { + fn drop(&mut self) { + unsafe { self.node.access_unchecked().release().ok() }; + } +} + +impl Drop for Directory { + fn drop(&mut self) { + unsafe { self.node.access_unchecked().release().ok() }; + } +} + +impl fmt::Debug for OpenOptions { + #[allow(unused_assignments)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut written = false; + macro_rules! fmt_opt { + ($field: ident, $label: literal) => { + if self.$field { + if written { + write!(f, " | ")?; + } + write!(f, $label)?; + written = true; + } + }; + } + fmt_opt!(read, "READ"); + fmt_opt!(write, "WRITE"); + fmt_opt!(append, "APPEND"); + fmt_opt!(truncate, "TRUNC"); + fmt_opt!(create, "CREATE"); + fmt_opt!(create_new, "CREATE_NEW"); + Ok(()) + } +} + +impl From<&OpenOptions> for Cap { + fn from(opts: &OpenOptions) -> Cap { + let mut cap = Cap::empty(); + if opts.read { + cap |= Cap::READ; + } + if opts.write | opts.append { + cap |= Cap::WRITE; + } + cap + } +} + +fn perm_to_cap(perm: FilePerm) -> Cap { + let mut cap = Cap::empty(); + if perm.owner_readable() { + cap |= Cap::READ; + } + if perm.owner_writable() { + cap |= Cap::WRITE; + } + if perm.owner_executable() { + cap |= Cap::EXECUTE; + } + cap +} diff --git a/modules/axfs/src/fs/another_ext4.rs b/modules/axfs/src/fs/another_ext4.rs new file mode 100644 index 0000000..f2dd09f --- /dev/null +++ b/modules/axfs/src/fs/another_ext4.rs @@ -0,0 +1,281 @@ +use crate::dev::Disk; +use alloc::sync::Arc; +use another_ext4::{ + Block, BlockDevice, ErrCode as Ext4ErrorCode, Ext4, Ext4Error, FileType as EXt4FileType, + InodeMode as Ext4InodeMode, BLOCK_SIZE as EXT4_BLOCK_SIZE, EXT4_ROOT_INO, +}; +use axfs_vfs::{VfsDirEntry, VfsError, VfsNodePerm, VfsResult}; +use axfs_vfs::{VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType, VfsOps}; +use axsync::Mutex; + +pub struct DiskAdapter(Arc>); + +unsafe impl Send for DiskAdapter {} +unsafe impl Sync for DiskAdapter {} + +// The io block size of the disk layer +const DISK_BLOCK_SIZE: usize = 512; + +// The block size of the file system +pub const BLOCK_SIZE: usize = EXT4_BLOCK_SIZE; + +impl BlockDevice for DiskAdapter { + fn read_block(&self, block_id: u64) -> Block { + let mut disk = self.0.lock(); + let base = block_id as usize * EXT4_BLOCK_SIZE; + let mut data = [0u8; EXT4_BLOCK_SIZE]; + // Per-disk-block read + for i in 0..(EXT4_BLOCK_SIZE / DISK_BLOCK_SIZE) { + let dblock = disk.read_offset(base + i * DISK_BLOCK_SIZE); + data[i * DISK_BLOCK_SIZE..(i + 1) * DISK_BLOCK_SIZE].copy_from_slice(&dblock); + } + Block::new(block_id, data) + } + + fn write_block(&self, block: &Block) { + let mut disk = self.0.lock(); + let base = block.id as usize * EXT4_BLOCK_SIZE; + // Per-disk-block write + for i in 0..(EXT4_BLOCK_SIZE / DISK_BLOCK_SIZE) { + let dblock = &block.data[i * DISK_BLOCK_SIZE..(i + 1) * DISK_BLOCK_SIZE]; + let _ = disk.write_offset(base + i * DISK_BLOCK_SIZE, dblock); + } + } +} + +pub struct Ext4FileSystem(Arc); + +impl Ext4FileSystem { + pub fn new(disk: Disk) -> Self { + let block_device = Arc::new(DiskAdapter(Arc::new(Mutex::new(disk)))); + let ext4 = Ext4::load(block_device).expect("Failed to load ext4 filesystem"); + log::info!("Ext4 filesystem loaded"); + Self(Arc::new(ext4)) + } +} + +impl VfsOps for Ext4FileSystem { + fn root_dir(&self) -> VfsNodeRef { + Arc::new(Ext4VirtInode::new(EXT4_ROOT_INO, self.0.clone())) + } + fn umount(&self) -> VfsResult { + self.0.flush_all(); + Ok(()) + } +} + +pub struct Ext4VirtInode { + id: u32, + fs: Arc, +} + +unsafe impl Send for Ext4VirtInode {} +unsafe impl Sync for Ext4VirtInode {} + +impl Ext4VirtInode { + fn new(id: u32, fs: Arc) -> Self { + log::trace!("Create Ext4VirtInode {}", id); + Self { id, fs } + } +} + +impl VfsNodeOps for Ext4VirtInode { + fn open(&self) -> VfsResult { + Ok(()) + } + + fn release(&self) -> VfsResult { + Ok(()) + } + + fn get_attr(&self) -> VfsResult { + self.fs + .getattr(self.id) + .map(|attr| { + VfsNodeAttr::new( + map_perm(attr.perm), + map_type(attr.ftype), + attr.size, + attr.blocks, + ) + }) + .map_err(map_error) + } + + // file operations: + + fn read_at(&self, offset: u64, buf: &mut [u8]) -> VfsResult { + self.fs + .read(self.id, offset as usize, buf) + .map_err(map_error) + } + + fn write_at(&self, offset: u64, buf: &[u8]) -> VfsResult { + self.fs + .write(self.id, offset as usize, buf) + .map_err(map_error) + } + + fn fsync(&self) -> VfsResult { + Ok(()) + } + + fn truncate(&self, size: u64) -> VfsResult { + // TODO: Simple implementation, just set the size, + // not truncate the file in the disk + self.fs + .setattr( + self.id, + None, + None, + None, + Some(size), + None, + None, + None, + None, + ) + .map_err(map_error) + } + + // directory operations: + + fn parent(&self) -> Option { + self.fs.lookup(self.id, "..").map_or(None, |parent| { + Some(Arc::new(Ext4VirtInode::new(parent, self.fs.clone()))) + }) + } + + fn lookup(self: Arc, path: &str) -> VfsResult { + match self.fs.generic_lookup(self.id, path) { + Ok(id) => Ok(Arc::new(Ext4VirtInode::new(id, self.fs.clone()))), + Err(e) => Err(map_error(e)), + } + } + + fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult { + if self.fs.generic_lookup(self.id, path).is_ok() { + return Ok(()); + } + let mode = Ext4InodeMode::from_type_and_perm(map_type_inv(ty), Ext4InodeMode::ALL_RWX); + self.fs + .generic_create(self.id, path, mode) + .map(|_| ()) + .map_err(map_error) + } + + fn remove(&self, path: &str) -> VfsResult { + self.fs.unlink(self.id, path).map_err(map_error) + } + + fn read_dir(&self, start_idx: usize, dirents: &mut [VfsDirEntry]) -> VfsResult { + self.fs + .listdir(self.id) + .map(|entries| { + for (i, entry) in entries.iter().skip(start_idx).enumerate() { + if i >= dirents.len() { + return i; + } + dirents[i] = VfsDirEntry::new(&entry.name(), map_type(entry.file_type())); + } + entries.len() - start_idx + }) + .map_err(map_error) + } + + fn rename(&self, src_path: &str, dst_path: &str) -> VfsResult { + self.fs + .generic_rename(self.id, src_path, dst_path) + .map_err(map_error) + } + + fn as_any(&self) -> &dyn core::any::Any { + self as &dyn core::any::Any + } +} + +fn map_error(ext4_err: Ext4Error) -> VfsError { + log::warn!("Ext4 error: {:?}", ext4_err); + match ext4_err.code() { + Ext4ErrorCode::EPERM => VfsError::PermissionDenied, + Ext4ErrorCode::ENOENT => VfsError::NotFound, + Ext4ErrorCode::EIO => VfsError::Io, + Ext4ErrorCode::ENXIO => VfsError::Io, // ? + Ext4ErrorCode::E2BIG => VfsError::InvalidInput, + Ext4ErrorCode::ENOMEM => VfsError::NoMemory, + Ext4ErrorCode::EACCES => VfsError::PermissionDenied, // ? + Ext4ErrorCode::EFAULT => VfsError::BadAddress, + Ext4ErrorCode::EEXIST => VfsError::AlreadyExists, + Ext4ErrorCode::ENODEV => VfsError::Io, // ? + Ext4ErrorCode::ENOTDIR => VfsError::NotADirectory, + Ext4ErrorCode::EISDIR => VfsError::IsADirectory, + Ext4ErrorCode::EINVAL => VfsError::InvalidData, + Ext4ErrorCode::EFBIG => VfsError::InvalidData, + Ext4ErrorCode::ENOSPC => VfsError::StorageFull, + Ext4ErrorCode::EROFS => VfsError::PermissionDenied, + Ext4ErrorCode::EMLINK => VfsError::Io, // ? + Ext4ErrorCode::ERANGE => VfsError::InvalidData, + Ext4ErrorCode::ENOTEMPTY => VfsError::DirectoryNotEmpty, + Ext4ErrorCode::ENODATA => VfsError::NotFound, // `NotFound` only for entry? + Ext4ErrorCode::ENOTSUP => VfsError::Io, // ? + Ext4ErrorCode::ELINKFAIL => VfsError::Io, // ? + Ext4ErrorCode::EALLOCFAIL => VfsError::StorageFull, // ? + } +} + +fn map_type(ext4_type: EXt4FileType) -> VfsNodeType { + match ext4_type { + EXt4FileType::RegularFile => VfsNodeType::File, + EXt4FileType::Directory => VfsNodeType::Dir, + EXt4FileType::CharacterDev => VfsNodeType::CharDevice, + EXt4FileType::BlockDev => VfsNodeType::BlockDevice, + EXt4FileType::Fifo => VfsNodeType::Fifo, + EXt4FileType::Socket => VfsNodeType::Socket, + EXt4FileType::SymLink => VfsNodeType::SymLink, + EXt4FileType::Unknown => VfsNodeType::File, + } +} + +fn map_type_inv(vfs_type: VfsNodeType) -> EXt4FileType { + match vfs_type { + VfsNodeType::File => EXt4FileType::RegularFile, + VfsNodeType::Dir => EXt4FileType::Directory, + VfsNodeType::CharDevice => EXt4FileType::CharacterDev, + VfsNodeType::BlockDevice => EXt4FileType::BlockDev, + VfsNodeType::Fifo => EXt4FileType::Fifo, + VfsNodeType::Socket => EXt4FileType::Socket, + VfsNodeType::SymLink => EXt4FileType::SymLink, + } +} + +fn map_perm(perm: Ext4InodeMode) -> VfsNodePerm { + let mut vfs_perm = VfsNodePerm::from_bits_truncate(0); + if perm.contains(Ext4InodeMode::USER_READ) { + vfs_perm |= VfsNodePerm::OWNER_READ; + } + if perm.contains(Ext4InodeMode::USER_WRITE) { + vfs_perm |= VfsNodePerm::OWNER_WRITE; + } + if perm.contains(Ext4InodeMode::USER_EXEC) { + vfs_perm |= VfsNodePerm::OWNER_EXEC; + } + if perm.contains(Ext4InodeMode::GROUP_READ) { + vfs_perm |= VfsNodePerm::GROUP_READ; + } + if perm.contains(Ext4InodeMode::GROUP_WRITE) { + vfs_perm |= VfsNodePerm::GROUP_WRITE; + } + if perm.contains(Ext4InodeMode::GROUP_EXEC) { + vfs_perm |= VfsNodePerm::GROUP_EXEC; + } + if perm.contains(Ext4InodeMode::OTHER_READ) { + vfs_perm |= VfsNodePerm::OTHER_READ; + } + if perm.contains(Ext4InodeMode::OTHER_WRITE) { + vfs_perm |= VfsNodePerm::OTHER_WRITE; + } + if perm.contains(Ext4InodeMode::OTHER_EXEC) { + vfs_perm |= VfsNodePerm::OTHER_EXEC; + } + vfs_perm +} diff --git a/modules/axfs/src/fs/ext4_rs.rs b/modules/axfs/src/fs/ext4_rs.rs new file mode 100644 index 0000000..324754d --- /dev/null +++ b/modules/axfs/src/fs/ext4_rs.rs @@ -0,0 +1,409 @@ +use crate::dev::Disk; +use alloc::sync::Arc; +use alloc::vec; +use alloc::vec::*; +use axfs_vfs::{VfsDirEntry, VfsError, VfsNodePerm, VfsResult}; +use axfs_vfs::{VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType, VfsOps}; +use axsync::Mutex; +use core::cell::RefCell; +use ext4_rs::*; + +pub struct DiskAdapter { + inner: RefCell, +} + +unsafe impl Send for DiskAdapter {} +unsafe impl Sync for DiskAdapter {} + +// The io block size of the disk layer +const DISK_BLOCK_SIZE: usize = 512; + +// The block size of the file system +pub const BLOCK_SIZE: usize = 4096; + +impl BlockDevice for DiskAdapter { + fn read_offset(&self, offset: usize) -> Vec { + let mut disk = self.inner.borrow_mut(); + let mut buf = vec![0u8; BLOCK_SIZE]; + + let start_block_id = offset / DISK_BLOCK_SIZE; + let mut offset_in_block = offset % DISK_BLOCK_SIZE; + let mut total_bytes_read = 0; + + while total_bytes_read < buf.len() { + let current_block_id = start_block_id + (total_bytes_read / DISK_BLOCK_SIZE); + let bytes_to_copy = + (buf.len() - total_bytes_read).min(DISK_BLOCK_SIZE - offset_in_block); + + let block_data = disk.read_offset(current_block_id * DISK_BLOCK_SIZE + offset_in_block); + + buf[total_bytes_read..total_bytes_read + bytes_to_copy] + .copy_from_slice(&block_data[offset_in_block..offset_in_block + bytes_to_copy]); + + total_bytes_read += bytes_to_copy; + offset_in_block = 0; // After the first block, subsequent blocks read from the beginning + } + + buf + } + + fn write_offset(&self, offset: usize, buf: &[u8]) { + let mut disk = self.inner.borrow_mut(); + + let start_block_id = offset / DISK_BLOCK_SIZE; + let mut offset_in_block = offset % DISK_BLOCK_SIZE; + + let bytes_to_write = buf.len(); + let mut total_bytes_written = 0; + + while total_bytes_written < bytes_to_write { + let current_block_id = start_block_id + (total_bytes_written / DISK_BLOCK_SIZE); + let bytes_to_copy = + (bytes_to_write - total_bytes_written).min(DISK_BLOCK_SIZE - offset_in_block); + + let mut block_data = disk.read_offset(current_block_id * DISK_BLOCK_SIZE); + + block_data[offset_in_block..offset_in_block + bytes_to_copy] + .copy_from_slice(&buf[total_bytes_written..total_bytes_written + bytes_to_copy]); + + disk.write_offset(current_block_id * DISK_BLOCK_SIZE, &block_data) + .unwrap(); + + total_bytes_written += bytes_to_copy; + offset_in_block = 0; // After the first block, subsequent blocks start at the beginning + } + } +} + +pub struct Ext4FileSystem { + #[allow(unused)] + inner: Arc, + root_dir: VfsNodeRef, +} + +impl Ext4FileSystem { + pub fn new(disk: Disk) -> Self { + let block_device = Arc::new(DiskAdapter { + inner: RefCell::new(disk), + }); + let inner = Ext4::open(block_device); + let root = Arc::new(Ext4FileWrapper::new(inner.clone())); + Self { + inner: inner.clone(), + root_dir: root, + } + } +} + +impl VfsOps for Ext4FileSystem { + fn root_dir(&self) -> VfsNodeRef { + Arc::clone(&self.root_dir) + } + + fn umount(&self) -> VfsResult { + log::info!("umount:"); + todo!() + } +} + +pub struct Ext4FileWrapper { + ext4_file: Mutex, + ext4: Arc, +} + +unsafe impl Send for Ext4FileWrapper {} +unsafe impl Sync for Ext4FileWrapper {} + +impl Ext4FileWrapper { + fn new(ext4: Arc) -> Self { + Self { + ext4_file: Mutex::new(Ext4File::new()), + ext4: ext4, + } + } +} + +impl VfsNodeOps for Ext4FileWrapper { + /// Do something when the node is opened. + fn open(&self) -> VfsResult { + // log::info!("opening file"); + // let mut ext4_file = self.ext4_file.lock(); + // let r = self.ext4.ext4_open(&mut ext4_file, path, "r+", false); + Ok(()) + } + + /// Do something when the node is closed. + fn release(&self) -> VfsResult { + Ok(()) + } + + /// Get the attributes of the node. + fn get_attr(&self) -> VfsResult { + let ext4_file = self.ext4_file.lock(); + let root_inode_ref = + Ext4InodeRef::get_inode_ref(Arc::downgrade(&self.ext4).clone(), ext4_file.inode); + let inode_mode = root_inode_ref.inner.inode.mode; + let size = ext4_file.fsize; + // BLOCK_SIZE / DISK_BLOCK_SIZE + let blocks = root_inode_ref.inner.inode.blocks * 8; + let (ty, perm) = map_imode(inode_mode as u16); + drop(ext4_file); + Ok(VfsNodeAttr::new(perm, ty, size as _, blocks as _)) + } + + // file operations: + + /// Read data from the file at the given offset. + fn read_at(&self, offset: u64, buf: &mut [u8]) -> VfsResult { + let mut ext4_file = self.ext4_file.lock(); + ext4_file.fpos = offset as usize; + + let read_len = buf.len(); + let mut read_cnt = 0; + + let r = self + .ext4 + .ext4_file_read(&mut ext4_file, buf, read_len, &mut read_cnt); + + if let Err(e) = r { + match e.error() { + Errnum::EINVAL => { + drop(ext4_file); + Ok(0) + } + _ => { + drop(ext4_file); + Err(VfsError::InvalidInput) + } + } + } else { + drop(ext4_file); + Ok(read_len) + } + } + + /// Write data to the file at the given offset. + fn write_at(&self, offset: u64, buf: &[u8]) -> VfsResult { + let mut ext4_file = self.ext4_file.lock(); + ext4_file.fpos = offset as usize; + + let write_size = buf.len(); + + self.ext4.ext4_file_write(&mut ext4_file, &buf, write_size); + + Ok(write_size) + } + + /// Flush the file, synchronize the data to disk. + fn fsync(&self) -> VfsResult { + todo!() + } + + /// Truncate the file to the given size. + fn truncate(&self, _size: u64) -> VfsResult { + todo!() + } + + // directory operations: + + /// Get the parent directory of this directory. + /// + /// Return `None` if the node is a file. + fn parent(&self) -> Option { + None + } + + /// Lookup the node with given `path` in the directory. + /// + /// Return the node if found. + fn lookup(self: Arc, path: &str) -> VfsResult { + let mut ext4_file = self.ext4_file.lock(); + let r = self.ext4.ext4_open(&mut ext4_file, path, "r+", false); + + if let Err(e) = r { + match e.error() { + Errnum::ENOENT => Err(VfsError::NotFound), + Errnum::EALLOCFIAL => Err(VfsError::InvalidInput), + Errnum::ELINKFIAL => Err(VfsError::InvalidInput), + + _ => Err(VfsError::InvalidInput), + } + } else { + drop(ext4_file); + // log::error!("file found"); + Ok(self.clone()) + } + } + + /// Create a new node with the given `path` in the directory + /// + /// Return [`Ok(())`](Ok) if it already exists. + fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult { + let types = match ty { + VfsNodeType::Fifo => DirEntryType::EXT4_DE_FIFO, + VfsNodeType::CharDevice => DirEntryType::EXT4_DE_CHRDEV, + VfsNodeType::Dir => DirEntryType::EXT4_DE_DIR, + VfsNodeType::BlockDevice => DirEntryType::EXT4_DE_BLKDEV, + VfsNodeType::File => DirEntryType::EXT4_DE_REG_FILE, + VfsNodeType::SymLink => DirEntryType::EXT4_DE_SYMLINK, + VfsNodeType::Socket => DirEntryType::EXT4_DE_SOCK, + }; + + let mut ext4file = self.ext4_file.lock(); + + if types == DirEntryType::EXT4_DE_DIR { + let _ = self.ext4.ext4_dir_mk(path); + } else { + let _ = self.ext4.ext4_open(&mut ext4file, path, "w+", true); + } + + drop(ext4file); + + Ok(()) + } + + /// Remove the node with the given `path` in the directory. + fn remove(&self, _path: &str) -> VfsResult { + todo!() + } + + /// Read directory entries into `dirents`, starting from `start_idx`. + fn read_dir(&self, start_idx: usize, dirents: &mut [VfsDirEntry]) -> VfsResult { + let ext4_file = self.ext4_file.lock(); + let inode_num = ext4_file.inode; + let entries: Vec = self.ext4.read_dir_entry(inode_num as _); + let mut iter = entries.into_iter().skip(start_idx); + + for (i, out_entry) in dirents.iter_mut().enumerate() { + let x: Option = iter.next(); + match x { + Some(ext4direntry) => { + let name = ext4direntry.name; + let name_len = ext4direntry.name_len; + let file_type = unsafe { ext4direntry.inner.inode_type }; + let (ty, _) = map_dir_imode(file_type as u16); + let name = get_name(name, name_len as usize).unwrap(); + *out_entry = VfsDirEntry::new(name.as_str(), ty); + } + _ => return Ok(i), + } + } + + drop(ext4_file); + Ok(dirents.len()) + } + + /// Renames or moves existing file or directory. + fn rename(&self, _src_path: &str, _dst_path: &str) -> VfsResult { + todo!() + } + + fn as_any(&self) -> &dyn core::any::Any { + self as &dyn core::any::Any + } +} + +fn map_dir_imode(imode: u16) -> (VfsNodeType, VfsNodePerm) { + let diren_type = imode; + let type_code = ext4_rs::DirEntryType::from_bits(diren_type as u8).unwrap(); + let ty = match type_code { + DirEntryType::EXT4_DE_REG_FILE => VfsNodeType::File, + DirEntryType::EXT4_DE_DIR => VfsNodeType::Dir, + DirEntryType::EXT4_DE_CHRDEV => VfsNodeType::CharDevice, + DirEntryType::EXT4_DE_BLKDEV => VfsNodeType::BlockDevice, + DirEntryType::EXT4_DE_FIFO => VfsNodeType::Fifo, + DirEntryType::EXT4_DE_SOCK => VfsNodeType::Socket, + DirEntryType::EXT4_DE_SYMLINK => VfsNodeType::SymLink, + _ => { + // log::info!("{:x?}", imode); + VfsNodeType::File + } + }; + + let perm = ext4_rs::FileMode::from_bits_truncate(imode); + let mut vfs_perm = VfsNodePerm::from_bits_truncate(0); + + if perm.contains(ext4_rs::FileMode::S_IXOTH) { + vfs_perm |= VfsNodePerm::OTHER_EXEC; + } + if perm.contains(ext4_rs::FileMode::S_IWOTH) { + vfs_perm |= VfsNodePerm::OTHER_WRITE; + } + if perm.contains(ext4_rs::FileMode::S_IROTH) { + vfs_perm |= VfsNodePerm::OTHER_READ; + } + + if perm.contains(ext4_rs::FileMode::S_IXGRP) { + vfs_perm |= VfsNodePerm::GROUP_EXEC; + } + if perm.contains(ext4_rs::FileMode::S_IWGRP) { + vfs_perm |= VfsNodePerm::GROUP_WRITE; + } + if perm.contains(ext4_rs::FileMode::S_IRGRP) { + vfs_perm |= VfsNodePerm::GROUP_READ; + } + + if perm.contains(ext4_rs::FileMode::S_IXUSR) { + vfs_perm |= VfsNodePerm::OWNER_EXEC; + } + if perm.contains(ext4_rs::FileMode::S_IWUSR) { + vfs_perm |= VfsNodePerm::OWNER_WRITE; + } + if perm.contains(ext4_rs::FileMode::S_IRUSR) { + vfs_perm |= VfsNodePerm::OWNER_READ; + } + + (ty, vfs_perm) +} + +fn map_imode(imode: u16) -> (VfsNodeType, VfsNodePerm) { + let file_type = (imode & 0xf000) as usize; + let ty = match file_type { + EXT4_INODE_MODE_FIFO => VfsNodeType::Fifo, + EXT4_INODE_MODE_CHARDEV => VfsNodeType::CharDevice, + EXT4_INODE_MODE_DIRECTORY => VfsNodeType::Dir, + EXT4_INODE_MODE_BLOCKDEV => VfsNodeType::BlockDevice, + EXT4_INODE_MODE_FILE => VfsNodeType::File, + EXT4_INODE_MODE_SOFTLINK => VfsNodeType::SymLink, + EXT4_INODE_MODE_SOCKET => VfsNodeType::Socket, + _ => { + // log::info!("{:x?}", imode); + VfsNodeType::File + } + }; + + let perm = ext4_rs::FileMode::from_bits_truncate(imode); + let mut vfs_perm = VfsNodePerm::from_bits_truncate(0); + + if perm.contains(ext4_rs::FileMode::S_IXOTH) { + vfs_perm |= VfsNodePerm::OTHER_EXEC; + } + if perm.contains(ext4_rs::FileMode::S_IWOTH) { + vfs_perm |= VfsNodePerm::OTHER_WRITE; + } + if perm.contains(ext4_rs::FileMode::S_IROTH) { + vfs_perm |= VfsNodePerm::OTHER_READ; + } + + if perm.contains(ext4_rs::FileMode::S_IXGRP) { + vfs_perm |= VfsNodePerm::GROUP_EXEC; + } + if perm.contains(ext4_rs::FileMode::S_IWGRP) { + vfs_perm |= VfsNodePerm::GROUP_WRITE; + } + if perm.contains(ext4_rs::FileMode::S_IRGRP) { + vfs_perm |= VfsNodePerm::GROUP_READ; + } + + if perm.contains(ext4_rs::FileMode::S_IXUSR) { + vfs_perm |= VfsNodePerm::OWNER_EXEC; + } + if perm.contains(ext4_rs::FileMode::S_IWUSR) { + vfs_perm |= VfsNodePerm::OWNER_WRITE; + } + if perm.contains(ext4_rs::FileMode::S_IRUSR) { + vfs_perm |= VfsNodePerm::OWNER_READ; + } + + (ty, vfs_perm) +} diff --git a/modules/axfs/src/fs/fatfs.rs b/modules/axfs/src/fs/fatfs.rs new file mode 100644 index 0000000..e7c4a53 --- /dev/null +++ b/modules/axfs/src/fs/fatfs.rs @@ -0,0 +1,333 @@ +use alloc::sync::Arc; +use core::cell::UnsafeCell; + +use axfs_vfs::{VfsDirEntry, VfsError, VfsNodePerm, VfsResult}; +use axfs_vfs::{VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType, VfsOps}; +use axsync::Mutex; +use fatfs::{Dir, File, LossyOemCpConverter, NullTimeProvider, Read, Seek, SeekFrom, Write}; + +use crate::dev::Disk; + +pub const BLOCK_SIZE: usize = 512; + +pub struct FatFileSystem { + inner: fatfs::FileSystem, + root_dir: UnsafeCell>, +} + +pub struct FileWrapper<'a>(Mutex>); +pub struct DirWrapper<'a>(Dir<'a, Disk, NullTimeProvider, LossyOemCpConverter>); + +unsafe impl Sync for FatFileSystem {} +unsafe impl Send for FatFileSystem {} +unsafe impl<'a> Send for FileWrapper<'a> {} +unsafe impl<'a> Sync for FileWrapper<'a> {} +unsafe impl<'a> Send for DirWrapper<'a> {} +unsafe impl<'a> Sync for DirWrapper<'a> {} + +impl FatFileSystem { + #[cfg(feature = "use-ramdisk")] + pub fn new(mut disk: Disk) -> Self { + let opts = fatfs::FormatVolumeOptions::new(); + fatfs::format_volume(&mut disk, opts).expect("failed to format volume"); + let inner = fatfs::FileSystem::new(disk, fatfs::FsOptions::new()) + .expect("failed to initialize FAT filesystem"); + Self { + inner, + root_dir: UnsafeCell::new(None), + } + } + + #[cfg(not(feature = "use-ramdisk"))] + pub fn new(disk: Disk) -> Self { + let inner = fatfs::FileSystem::new(disk, fatfs::FsOptions::new()) + .expect("failed to initialize FAT filesystem"); + Self { + inner, + root_dir: UnsafeCell::new(None), + } + } + + pub fn init(&'static self) { + // must be called before later operations + unsafe { *self.root_dir.get() = Some(Self::new_dir(self.inner.root_dir())) } + } + + fn new_file(file: File<'_, Disk, NullTimeProvider, LossyOemCpConverter>) -> Arc { + Arc::new(FileWrapper(Mutex::new(file))) + } + + fn new_dir(dir: Dir<'_, Disk, NullTimeProvider, LossyOemCpConverter>) -> Arc { + Arc::new(DirWrapper(dir)) + } +} + +impl VfsNodeOps for FileWrapper<'static> { + axfs_vfs::impl_vfs_non_dir_default! {} + + fn get_attr(&self) -> VfsResult { + let size = self.0.lock().seek(SeekFrom::End(0)).map_err(as_vfs_err)?; + let blocks = (size + BLOCK_SIZE as u64 - 1) / BLOCK_SIZE as u64; + // FAT fs doesn't support permissions, we just set everything to 755 + let perm = VfsNodePerm::from_bits_truncate(0o755); + Ok(VfsNodeAttr::new(perm, VfsNodeType::File, size, blocks)) + } + + fn read_at(&self, offset: u64, buf: &mut [u8]) -> VfsResult { + // let mut file = self.0.lock(); + // file.seek(SeekFrom::Start(offset)).map_err(as_vfs_err)?; // TODO: more efficient + // file.read(buf).map_err(as_vfs_err) + let mut file = self.0.lock(); + file.seek(SeekFrom::Start(offset)).map_err(as_vfs_err)?; // TODO: more efficient + // file.read(buf).map_err(as_vfs_err) + let buf_len = buf.len(); + let mut now_offset = 0; + let mut probe = buf.to_vec(); + while now_offset < buf_len { + let ans = file.read(&mut probe).map_err(as_vfs_err); + if ans.is_err() { + return ans; + } + let read_len = ans.unwrap(); + + if read_len == 0 { + break; + } + buf[now_offset..now_offset + read_len].copy_from_slice(&probe[..read_len]); + now_offset += read_len; + probe = probe[read_len..].to_vec(); + } + Ok(now_offset) + } + + fn write_at(&self, offset: u64, buf: &[u8]) -> VfsResult { + // let mut file = self.0.lock(); + // file.seek(SeekFrom::Start(offset)).map_err(as_vfs_err)?; // TODO: more efficient + // file.write(buf).map_err(as_vfs_err) + let mut file = self.0.lock(); + file.seek(SeekFrom::Start(offset)).map_err(as_vfs_err)?; // TODO: more efficient + // file.write(buf).map_err(as_vfs_err) + let buf_len = buf.len(); + let mut now_offset = 0; + let mut probe = buf.to_vec(); + while now_offset < buf_len { + let ans = file.write(&probe).map_err(as_vfs_err); + if ans.is_err() { + return ans; + } + let write_len = ans.unwrap(); + + if write_len == 0 { + break; + } + now_offset += write_len; + probe = probe[write_len..].to_vec(); + } + Ok(now_offset) + } + + fn truncate(&self, size: u64) -> VfsResult { + let mut file = self.0.lock(); + file.seek(SeekFrom::Start(size)).map_err(as_vfs_err)?; // TODO: more efficient + file.truncate().map_err(as_vfs_err) + } +} + +impl VfsNodeOps for DirWrapper<'static> { + axfs_vfs::impl_vfs_dir_default! {} + + fn get_attr(&self) -> VfsResult { + // FAT fs doesn't support permissions, we just set everything to 755 + Ok(VfsNodeAttr::new( + VfsNodePerm::from_bits_truncate(0o755), + VfsNodeType::Dir, + BLOCK_SIZE as u64, + 1, + )) + } + + fn parent(&self) -> Option { + self.0 + .open_dir("..") + .map_or(None, |dir| Some(FatFileSystem::new_dir(dir))) + } + + fn lookup(self: Arc, path: &str) -> VfsResult { + debug!("lookup at fatfs: {}", path); + let path = path.trim_matches('/'); + if path.is_empty() || path == "." { + return Ok(self.clone()); + } + if let Some(rest) = path.strip_prefix("./") { + return self.lookup(rest); + } + + // TODO: use `fatfs::Dir::find_entry`, but it's not public. + if let Some((dir, rest)) = path.split_once('/') { + return self.lookup(dir)?.lookup(rest); + } + for entry in self.0.iter() { + let Ok(entry) = entry else { + return Err(VfsError::Io); + }; + if entry.file_name() == path { + if entry.is_file() { + return Ok(FatFileSystem::new_file(entry.to_file())); + } else if entry.is_dir() { + return Ok(FatFileSystem::new_dir(entry.to_dir())); + } + } + } + Err(VfsError::NotFound) + } + + fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult { + debug!("create {:?} at fatfs: {}", ty, path); + let path = path.trim_matches('/'); + if path.is_empty() || path == "." { + return Ok(()); + } + if let Some(rest) = path.strip_prefix("./") { + return self.create(rest, ty); + } + + match ty { + VfsNodeType::File => { + self.0.create_file(path).map_err(as_vfs_err)?; + Ok(()) + } + VfsNodeType::Dir => { + self.0.create_dir(path).map_err(as_vfs_err)?; + Ok(()) + } + _ => Err(VfsError::Unsupported), + } + } + + fn remove(&self, path: &str) -> VfsResult { + debug!("remove at fatfs: {}", path); + let path = path.trim_matches('/'); + assert!(!path.is_empty()); // already check at `root.rs` + if let Some(rest) = path.strip_prefix("./") { + return self.remove(rest); + } + self.0.remove(path).map_err(as_vfs_err) + } + + fn read_dir(&self, start_idx: usize, dirents: &mut [VfsDirEntry]) -> VfsResult { + let mut iter = self.0.iter().skip(start_idx); + for (i, out_entry) in dirents.iter_mut().enumerate() { + let x = iter.next(); + match x { + Some(Ok(entry)) => { + let ty = if entry.is_dir() { + VfsNodeType::Dir + } else if entry.is_file() { + VfsNodeType::File + } else { + unreachable!() + }; + *out_entry = VfsDirEntry::new(&entry.file_name(), ty); + } + _ => return Ok(i), + } + } + Ok(dirents.len()) + } + + fn rename(&self, src_path: &str, dst_path: &str) -> VfsResult { + // `src_path` and `dst_path` should in the same mounted fs + debug!( + "rename at fatfs, src_path: {}, dst_path: {}", + src_path, dst_path + ); + let dst_path = dst_path.trim_matches('/'); + self.0 + .rename(src_path, &self.0, dst_path) + .map_err(as_vfs_err) + } +} + +impl VfsOps for FatFileSystem { + fn root_dir(&self) -> VfsNodeRef { + let root_dir = unsafe { (*self.root_dir.get()).as_ref().unwrap() }; + root_dir.clone() + } +} + +impl fatfs::IoBase for Disk { + type Error = (); +} + +impl Read for Disk { + fn read(&mut self, mut buf: &mut [u8]) -> Result { + let mut read_len = 0; + while !buf.is_empty() { + match self.read_one(buf) { + Ok(0) => break, + Ok(n) => { + let tmp = buf; + buf = &mut tmp[n..]; + read_len += n; + } + Err(_) => return Err(()), + } + } + Ok(read_len) + } +} + +impl Write for Disk { + fn write(&mut self, mut buf: &[u8]) -> Result { + let mut write_len = 0; + while !buf.is_empty() { + match self.write_one(buf) { + Ok(0) => break, + Ok(n) => { + buf = &buf[n..]; + write_len += n; + } + Err(_) => return Err(()), + } + } + Ok(write_len) + } + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl Seek for Disk { + fn seek(&mut self, pos: SeekFrom) -> Result { + let size = self.size(); + let new_pos = match pos { + SeekFrom::Start(pos) => Some(pos), + SeekFrom::Current(off) => self.position().checked_add_signed(off), + SeekFrom::End(off) => size.checked_add_signed(off), + } + .ok_or(())?; + if new_pos > size { + warn!("Seek beyond the end of the block device"); + } + self.set_position(new_pos); + Ok(new_pos) + } +} + +const fn as_vfs_err(err: fatfs::Error<()>) -> VfsError { + use fatfs::Error::*; + match err { + AlreadyExists => VfsError::AlreadyExists, + CorruptedFileSystem => VfsError::InvalidData, + DirectoryIsNotEmpty => VfsError::DirectoryNotEmpty, + InvalidInput | InvalidFileNameLength | UnsupportedFileNameCharacter => { + VfsError::InvalidInput + } + NotEnoughSpace => VfsError::StorageFull, + NotFound => VfsError::NotFound, + UnexpectedEof => VfsError::UnexpectedEof, + WriteZero => VfsError::WriteZero, + Io(_) => VfsError::Io, + _ => VfsError::Io, + } +} diff --git a/modules/axfs/src/fs/lwext4_rust.rs b/modules/axfs/src/fs/lwext4_rust.rs new file mode 100644 index 0000000..06bdb54 --- /dev/null +++ b/modules/axfs/src/fs/lwext4_rust.rs @@ -0,0 +1,408 @@ +use crate::alloc::string::String; +use alloc::sync::Arc; +use axerrno::AxError; +use axfs_vfs::{VfsDirEntry, VfsError, VfsNodePerm, VfsResult}; +use axfs_vfs::{VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType, VfsOps}; +use axsync::Mutex; +use lwext4_rust::bindings::{ + O_CREAT, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET, +}; +use lwext4_rust::{Ext4BlockWrapper, Ext4File, InodeTypes, KernelDevOp}; + +use crate::dev::Disk; +pub const BLOCK_SIZE: usize = 512; + +#[allow(dead_code)] +pub struct Ext4FileSystem { + inner: Ext4BlockWrapper, + root: VfsNodeRef, +} + +unsafe impl Sync for Ext4FileSystem {} +unsafe impl Send for Ext4FileSystem {} + +impl Ext4FileSystem { + #[cfg(feature = "use-ramdisk")] + pub fn new(mut disk: Disk) -> Self { + unimplemented!() + } + + #[cfg(not(feature = "use-ramdisk"))] + pub fn new(disk: Disk) -> Self { + info!( + "Got Disk size:{}, position:{}", + disk.size(), + disk.position() + ); + let inner = + Ext4BlockWrapper::::new(disk).expect("failed to initialize EXT4 filesystem"); + let root = Arc::new(FileWrapper::new("/", InodeTypes::EXT4_DE_DIR)); + Self { inner, root } + } +} + +/// The [`VfsOps`] trait provides operations on a filesystem. +impl VfsOps for Ext4FileSystem { + // mount() + + fn root_dir(&self) -> VfsNodeRef { + debug!("Get root_dir"); + //let root_dir = unsafe { (*self.root.get()).as_ref().unwrap() }; + Arc::clone(&self.root) + } +} + +pub struct FileWrapper(Mutex); + +unsafe impl Send for FileWrapper {} +unsafe impl Sync for FileWrapper {} + +impl FileWrapper { + fn new(path: &str, types: InodeTypes) -> Self { + info!("FileWrapper new {:?} {}", types, path); + //file.file_read_test("/test/test.txt", &mut buf); + + Self(Mutex::new(Ext4File::new(path, types))) + } + + fn path_deal_with(&self, path: &str) -> String { + if path.starts_with('/') { + warn!("path_deal_with: {}", path); + } + let p = path.trim_matches('/'); // 首尾去除 + if p.is_empty() || p == "." { + return String::new(); + } + + if let Some(rest) = p.strip_prefix("./") { + //if starts with "./" + return self.path_deal_with(rest); + } + let rest_p = p.replace("//", "/"); + if p != rest_p { + return self.path_deal_with(&rest_p); + } + + //Todo ? ../ + //注:lwext4创建文件必须提供文件path的绝对路径 + let file = self.0.lock(); + let path = file.get_path(); + let fpath = String::from(path.to_str().unwrap().trim_end_matches('/')) + "/" + p; + info!("dealt with full path: {}", fpath.as_str()); + fpath + } +} + +/// The [`VfsNodeOps`] trait provides operations on a file or a directory. +impl VfsNodeOps for FileWrapper { + fn get_attr(&self) -> VfsResult { + let mut file = self.0.lock(); + + let perm = file.file_mode_get().unwrap_or(0o755); + let perm = VfsNodePerm::from_bits_truncate((perm as u16) & 0o777); + + let vtype = file.file_type_get(); + let vtype = match vtype { + InodeTypes::EXT4_INODE_MODE_FIFO => VfsNodeType::Fifo, + InodeTypes::EXT4_INODE_MODE_CHARDEV => VfsNodeType::CharDevice, + InodeTypes::EXT4_INODE_MODE_DIRECTORY => VfsNodeType::Dir, + InodeTypes::EXT4_INODE_MODE_BLOCKDEV => VfsNodeType::BlockDevice, + InodeTypes::EXT4_INODE_MODE_FILE => VfsNodeType::File, + InodeTypes::EXT4_INODE_MODE_SOFTLINK => VfsNodeType::SymLink, + InodeTypes::EXT4_INODE_MODE_SOCKET => VfsNodeType::Socket, + _ => { + warn!("unknown file type: {:?}", vtype); + VfsNodeType::File + } + }; + + let size = if vtype == VfsNodeType::File { + let path = file.get_path(); + let path = path.to_str().unwrap(); + file.file_open(path, O_RDONLY) + .map_err(|e| >::try_into(e).unwrap())?; + let fsize = file.file_size(); + let _ = file.file_close(); + fsize + } else { + 0 // DIR size ? + }; + let blocks = (size + (BLOCK_SIZE as u64 - 1)) / BLOCK_SIZE as u64; + + info!( + "get_attr of {:?} {:?}, size: {}, blocks: {}", + vtype, + file.get_path(), + size, + blocks + ); + + Ok(VfsNodeAttr::new(perm, vtype, size, blocks)) + } + + fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult { + info!("create {:?} on Ext4fs: {}", ty, path); + let fpath = self.path_deal_with(path); + let fpath = fpath.as_str(); + if fpath.is_empty() { + return Ok(()); + } + + let types = match ty { + VfsNodeType::Fifo => InodeTypes::EXT4_DE_FIFO, + VfsNodeType::CharDevice => InodeTypes::EXT4_DE_CHRDEV, + VfsNodeType::Dir => InodeTypes::EXT4_DE_DIR, + VfsNodeType::BlockDevice => InodeTypes::EXT4_DE_BLKDEV, + VfsNodeType::File => InodeTypes::EXT4_DE_REG_FILE, + VfsNodeType::SymLink => InodeTypes::EXT4_DE_SYMLINK, + VfsNodeType::Socket => InodeTypes::EXT4_DE_SOCK, + }; + + let mut file = self.0.lock(); + if file.check_inode_exist(fpath, types.clone()) { + Ok(()) + } else { + if types == InodeTypes::EXT4_DE_DIR { + file.dir_mk(fpath) + .map(|_v| ()) + .map_err(|e| e.try_into().unwrap()) + } else { + file.file_open(fpath, O_WRONLY | O_CREAT | O_TRUNC) + .expect("create file failed"); + file.file_close() + .map(|_v| ()) + .map_err(|e| e.try_into().unwrap()) + } + } + } + + fn remove(&self, path: &str) -> VfsResult { + info!("remove ext4fs: {}", path); + let fpath = self.path_deal_with(path); + let fpath = fpath.as_str(); + + assert!(!fpath.is_empty()); // already check at `root.rs` + + let mut file = self.0.lock(); + if file.check_inode_exist(fpath, InodeTypes::EXT4_DE_DIR) { + // Recursive directory remove + file.dir_rm(fpath) + .map(|_v| ()) + .map_err(|e| e.try_into().unwrap()) + } else { + file.file_remove(fpath) + .map(|_v| ()) + .map_err(|e| e.try_into().unwrap()) + } + } + + /// Get the parent directory of this directory. + /// Return `None` if the node is a file. + fn parent(&self) -> Option { + let file = self.0.lock(); + if file.get_type() == InodeTypes::EXT4_DE_DIR { + let path = file.get_path(); + let path = path.to_str().unwrap(); + info!("Get the parent dir of {}", path); + let path = path.trim_end_matches('/').trim_end_matches(|c| c != '/'); + if !path.is_empty() { + return Some(Arc::new(Self::new(path, InodeTypes::EXT4_DE_DIR))); + } + } + None + } + + /// Read directory entries into `dirents`, starting from `start_idx`. + fn read_dir(&self, start_idx: usize, dirents: &mut [VfsDirEntry]) -> VfsResult { + let file = self.0.lock(); + let (name, inode_type) = file.lwext4_dir_entries().unwrap(); + + let mut name_iter = name.iter().skip(start_idx); + let mut inode_type_iter = inode_type.iter().skip(start_idx); + + for (i, out_entry) in dirents.iter_mut().enumerate() { + let iname = name_iter.next(); + let itypes = inode_type_iter.next(); + + match itypes { + Some(t) => { + let ty = if *t == InodeTypes::EXT4_DE_DIR { + VfsNodeType::Dir + } else if *t == InodeTypes::EXT4_DE_REG_FILE { + VfsNodeType::File + } else if *t == InodeTypes::EXT4_DE_SYMLINK { + VfsNodeType::SymLink + } else { + error!("unknown file type: {:?}", itypes); + unreachable!() + }; + + *out_entry = + VfsDirEntry::new(core::str::from_utf8(iname.unwrap()).unwrap(), ty); + } + _ => return Ok(i), + } + } + + Ok(dirents.len()) + } + + /// Lookup the node with given `path` in the directory. + /// Return the node if found. + fn lookup(self: Arc, path: &str) -> VfsResult { + debug!("lookup ext4fs: {:?}, {}", self.0.lock().get_path(), path); + + let fpath = self.path_deal_with(path); + let fpath = fpath.as_str(); + if fpath.is_empty() { + return Ok(self.clone()); + } + + ///////// + let mut file = self.0.lock(); + if file.check_inode_exist(fpath, InodeTypes::EXT4_DE_DIR) { + debug!("lookup new DIR FileWrapper"); + Ok(Arc::new(Self::new(fpath, InodeTypes::EXT4_DE_DIR))) + } else if file.check_inode_exist(fpath, InodeTypes::EXT4_DE_REG_FILE) { + debug!("lookup new FILE FileWrapper"); + Ok(Arc::new(Self::new(fpath, InodeTypes::EXT4_DE_REG_FILE))) + } else { + Err(VfsError::NotFound) + } + } + + fn read_at(&self, offset: u64, buf: &mut [u8]) -> VfsResult { + info!("To read_at {}, buf len={}", offset, buf.len()); + let mut file = self.0.lock(); + let path = file.get_path(); + let path = path.to_str().unwrap(); + file.file_open(path, O_RDONLY) + .map_err(|e| >::try_into(e).unwrap())?; + + file.file_seek(offset as i64, SEEK_SET) + .map_err(|e| >::try_into(e).unwrap())?; + let r = file.file_read(buf); + + let _ = file.file_close(); + r.map_err(|e| e.try_into().unwrap()) + } + + fn write_at(&self, offset: u64, buf: &[u8]) -> VfsResult { + info!("To write_at {}, buf len={}", offset, buf.len()); + let mut file = self.0.lock(); + let path = file.get_path(); + let path = path.to_str().unwrap(); + file.file_open(path, O_RDWR) + .map_err(|e| >::try_into(e).unwrap())?; + + file.file_seek(offset as i64, SEEK_SET) + .map_err(|e| >::try_into(e).unwrap())?; + let r = file.file_write(buf); + + let _ = file.file_close(); + r.map_err(|e| e.try_into().unwrap()) + } + + fn truncate(&self, size: u64) -> VfsResult { + info!("truncate file to size={}", size); + let mut file = self.0.lock(); + let path = file.get_path(); + let path = path.to_str().unwrap(); + file.file_open(path, O_RDWR | O_CREAT | O_TRUNC) + .map_err(|e| >::try_into(e).unwrap())?; + + let t = file.file_truncate(size); + + let _ = file.file_close(); + t.map(|_v| ()).map_err(|e| e.try_into().unwrap()) + } + + fn rename(&self, src_path: &str, dst_path: &str) -> VfsResult { + info!("rename from {} to {}", src_path, dst_path); + let mut file = self.0.lock(); + file.file_rename(src_path, dst_path) + .map(|_v| ()) + .map_err(|e| e.try_into().unwrap()) + } + + fn as_any(&self) -> &dyn core::any::Any { + self as &dyn core::any::Any + } +} + +impl Drop for FileWrapper { + fn drop(&mut self) { + let mut file = self.0.lock(); + debug!("Drop struct FileWrapper {:?}", file.get_path()); + file.file_close().expect("failed to close fd"); + drop(file); // todo + } +} + +impl KernelDevOp for Disk { + //type DevType = Box; + type DevType = Disk; + + fn read(dev: &mut Disk, mut buf: &mut [u8]) -> Result { + debug!("READ block device buf={}", buf.len()); + let mut read_len = 0; + while !buf.is_empty() { + match dev.read_one(buf) { + Ok(0) => break, + Ok(n) => { + let tmp = buf; + buf = &mut tmp[n..]; + read_len += n; + } + Err(_e) => return Err(-1), + } + } + debug!("READ rt len={}", read_len); + Ok(read_len) + } + fn write(dev: &mut Self::DevType, mut buf: &[u8]) -> Result { + debug!("WRITE block device buf={}", buf.len()); + let mut write_len = 0; + while !buf.is_empty() { + match dev.write_one(buf) { + Ok(0) => break, + Ok(n) => { + buf = &buf[n..]; + write_len += n; + } + Err(_e) => return Err(-1), + } + } + debug!("WRITE rt len={}", write_len); + Ok(write_len) + } + fn flush(_dev: &mut Self::DevType) -> Result { + Ok(0) + } + fn seek(dev: &mut Disk, off: i64, whence: i32) -> Result { + let size = dev.size(); + debug!( + "SEEK block device size:{}, pos:{}, offset={}, whence={}", + size, + &dev.position(), + off, + whence + ); + let new_pos = match whence as u32 { + SEEK_SET => Some(off), + SEEK_CUR => dev.position().checked_add_signed(off).map(|v| v as i64), + SEEK_END => size.checked_add_signed(off).map(|v| v as i64), + _ => { + error!("invalid seek() whence: {}", whence); + Some(off) + } + } + .ok_or(-1)?; + + if new_pos as u64 > size { + warn!("Seek beyond the end of the block device"); + } + dev.set_position(new_pos as u64); + Ok(new_pos) + } +} diff --git a/modules/axfs/src/fs/mod.rs b/modules/axfs/src/fs/mod.rs new file mode 100644 index 0000000..c505482 --- /dev/null +++ b/modules/axfs/src/fs/mod.rs @@ -0,0 +1,26 @@ +cfg_if::cfg_if! { + if #[cfg(feature = "myfs")] { + pub mod myfs; + /// The block size of the file system. + pub const BLOCK_SIZE: usize = 512; + } else if #[cfg(feature = "lwext4_rust")] { + pub mod lwext4_rust; + pub use lwext4_rust::BLOCK_SIZE; + } else if #[cfg(feature = "ext4_rs")] { + pub mod ext4_rs; + pub use ext4_rs::BLOCK_SIZE; + } else if #[cfg(feature = "another_ext4")] { + pub mod another_ext4; + pub use another_ext4::BLOCK_SIZE; + } else if #[cfg(feature = "fatfs")] { + // default to be fatfs + pub mod fatfs; + pub use fatfs::BLOCK_SIZE; + } +} + +#[cfg(feature = "devfs")] +pub use axfs_devfs as devfs; + +#[cfg(feature = "ramfs")] +pub use axfs_ramfs as ramfs; diff --git a/modules/axfs/src/fs/myfs.rs b/modules/axfs/src/fs/myfs.rs new file mode 100644 index 0000000..ca24fd5 --- /dev/null +++ b/modules/axfs/src/fs/myfs.rs @@ -0,0 +1,16 @@ +use crate::dev::Disk; +use alloc::sync::Arc; +use axfs_vfs::VfsOps; + +/// The interface to define custom filesystems in user apps. +#[crate_interface::def_interface] +pub trait MyFileSystemIf { + /// Creates a new instance of the filesystem with initialization. + /// + /// TODO: use generic disk type + fn new_myfs(disk: Disk) -> Arc; +} + +pub(crate) fn new_myfs(disk: Disk) -> Arc { + crate_interface::call_interface!(MyFileSystemIf::new_myfs(disk)) +} diff --git a/modules/axfs/src/lib.rs b/modules/axfs/src/lib.rs new file mode 100644 index 0000000..e5288e1 --- /dev/null +++ b/modules/axfs/src/lib.rs @@ -0,0 +1,50 @@ +//! [ArceOS](https://github.com/rcore-os/arceos) filesystem module. +//! +//! It provides unified filesystem operations for various filesystems. +//! +//! # Cargo Features +//! +//! - `fatfs`: Use [FAT] as the main filesystem and mount it on `/`. This feature +//! is **enabled** by default. +//! - `devfs`: Mount [`axfs_devfs::DeviceFileSystem`] on `/dev`. This feature is +//! **enabled** by default. +//! - `ramfs`: Mount [`axfs_ramfs::RamFileSystem`] on `/tmp`. This feature is +//! **enabled** by default. +//! - `myfs`: Allow users to define their custom filesystems to override the +//! default. In this case, [`MyFileSystemIf`] is required to be implemented +//! to create and initialize other filesystems. This feature is **disabled** by +//! by default, but it will override other filesystem selection features if +//! both are enabled. +//! +//! [FAT]: https://en.wikipedia.org/wiki/File_Allocation_Table +//! [`MyFileSystemIf`]: fops::MyFileSystemIf + +#![cfg_attr(all(not(test), not(doc)), no_std)] +#![feature(doc_auto_cfg)] + +#[macro_use] +extern crate log; +extern crate alloc; + +mod dev; +mod fs; +mod mounts; +mod root; + +pub use fs::BLOCK_SIZE; +pub mod api; +pub mod fops; + +pub use axfs_devfs; +pub use axfs_ramfs; + +use axdriver::{prelude::*, AxDeviceContainer}; + +/// Initializes filesystems by block devices. +pub fn init_filesystems(mut blk_devs: AxDeviceContainer) { + info!("Initialize filesystems..."); + + let dev = blk_devs.take_one().expect("No block device found!"); + info!(" use block device 0: {:?}", dev.device_name()); + self::root::init_rootfs(self::dev::Disk::new(dev)); +} diff --git a/modules/axfs/src/mounts.rs b/modules/axfs/src/mounts.rs new file mode 100644 index 0000000..a5be4eb --- /dev/null +++ b/modules/axfs/src/mounts.rs @@ -0,0 +1,122 @@ +use alloc::sync::Arc; +use axfs_vfs::{VfsNodeType, VfsOps, VfsResult}; + +use crate::fs; + +#[cfg(feature = "devfs")] +pub(crate) fn devfs() -> Arc { + let null = fs::devfs::NullDev; + let zero = fs::devfs::ZeroDev; + let bar = fs::devfs::ZeroDev; + let random = fs::devfs::RandomDev::default(); + let urandom = fs::devfs::RandomDev::default(); + + let devfs = fs::devfs::DeviceFileSystem::new(); + let foo_dir = devfs.mkdir("foo"); + devfs.add("null", Arc::new(null)); + devfs.add("zero", Arc::new(zero)); + foo_dir.add("bar", Arc::new(bar)); + devfs.add("random", Arc::new(random)); + devfs.add("urandom", Arc::new(urandom)); + #[cfg(feature = "monolithic")] + { + // 添加dev文件系统下的配置文件 + // busybox的时候要用到 + // devfs不支持可修改的file,因此取巧直接用了ramfs提供的file实现 + let testrtc = fs::ramfs::FileNode::new(); + let _shm_dir = devfs.mkdir("shm"); + let rtc_dir = devfs.mkdir("misc"); + rtc_dir.add("rtc", Arc::new(testrtc)); + } + Arc::new(devfs) +} + +#[cfg(feature = "ramfs")] +pub(crate) fn ramfs() -> Arc { + Arc::new(fs::ramfs::RamFileSystem::new()) +} + +#[cfg(feature = "procfs")] +pub(crate) fn procfs() -> VfsResult> { + let procfs = fs::ramfs::RamFileSystem::new(); + let proc_root = procfs.root_dir(); + + // Create /proc/sys/net/core/somaxconn + proc_root.create("sys", VfsNodeType::Dir)?; + proc_root.create("sys/net", VfsNodeType::Dir)?; + proc_root.create("sys/net/core", VfsNodeType::Dir)?; + proc_root.create("sys/net/core/somaxconn", VfsNodeType::File)?; + let file_somaxconn = proc_root.clone().lookup("./sys/net/core/somaxconn")?; + file_somaxconn.write_at(0, b"4096\n")?; + + // Create /proc/sys/vm/overcommit_memory + proc_root.create("sys/vm", VfsNodeType::Dir)?; + proc_root.create("sys/vm/overcommit_memory", VfsNodeType::File)?; + let file_over = proc_root.clone().lookup("./sys/vm/overcommit_memory")?; + file_over.write_at(0, b"0\n")?; + + // Create /proc/self/stat + proc_root.create("self", VfsNodeType::Dir)?; + proc_root.create("self/stat", VfsNodeType::File)?; + proc_root.create("self/exe", VfsNodeType::File)?; + proc_root.create("self/status", VfsNodeType::File)?; + + // Create /proc/filesystems + proc_root.create("filesystems", VfsNodeType::File)?; + + #[cfg(feature = "monolithic")] + { + // Create other file to pass the testcases + proc_root.create("meminfo", VfsNodeType::File)?; + proc_root.create("mounts", VfsNodeType::File)?; + proc_root.create("interrupts", VfsNodeType::File)?; + // procfs.mount("interrupts", Arc::new(fs::devfs::Interrupts::default()))?; + } + Ok(Arc::new(procfs)) +} + +#[cfg(feature = "sysfs")] +pub(crate) fn sysfs() -> VfsResult> { + let sysfs = fs::ramfs::RamFileSystem::new(); + let sys_root = sysfs.root_dir(); + + // Create /sys/kernel/mm/transparent_hugepage/enabled + sys_root.create("kernel", VfsNodeType::Dir)?; + sys_root.create("kernel/mm", VfsNodeType::Dir)?; + sys_root.create("kernel/mm/transparent_hugepage", VfsNodeType::Dir)?; + sys_root.create("kernel/mm/transparent_hugepage/enabled", VfsNodeType::File)?; + let file_hp = sys_root + .clone() + .lookup("./kernel/mm/transparent_hugepage/enabled")?; + file_hp.write_at(0, b"always [madvise] never\n")?; + + // Create /sys/devices/system/clocksource/clocksource0/current_clocksource + sys_root.create("devices", VfsNodeType::Dir)?; + sys_root.create("devices/system", VfsNodeType::Dir)?; + sys_root.create("devices/system/clocksource", VfsNodeType::Dir)?; + sys_root.create("devices/system/clocksource/clocksource0", VfsNodeType::Dir)?; + sys_root.create( + "devices/system/clocksource/clocksource0/current_clocksource", + VfsNodeType::File, + )?; + let file_cc = sys_root + .clone() + .lookup("devices/system/clocksource/clocksource0/current_clocksource")?; + file_cc.write_at(0, b"tsc\n")?; + + // create /sys/devices/system/cpu/online + sys_root.create("devices/system/cpu", VfsNodeType::Dir)?; + sys_root.create("devices/system/cpu/online", VfsNodeType::File)?; + let cpu_online = sys_root.clone().lookup("devices/system/cpu/online")?; + let smp = axconfig::SMP; + cpu_online.write_at(0, alloc::format!("0-{}", smp - 1).as_bytes())?; + for cpu_id in 0..smp { + let path = alloc::format!("devices/system/cpu/cpu{}", cpu_id); + sys_root.create(path.as_str(), VfsNodeType::Dir)?; + let path = path + "/online"; + sys_root.create(path.as_str(), VfsNodeType::File)?; + let file = sys_root.clone().lookup(path.as_str())?; + file.write_at(0, b"1")?; + } + Ok(Arc::new(sysfs)) +} diff --git a/modules/axfs/src/root.rs b/modules/axfs/src/root.rs new file mode 100644 index 0000000..16735df --- /dev/null +++ b/modules/axfs/src/root.rs @@ -0,0 +1,347 @@ +//! Root directory of the filesystem +//! +//! TODO: it doesn't work very well if the mount points have containment relationships. + +use alloc::{ + string::{String, ToString}, + sync::Arc, + vec::Vec, +}; +use axerrno::{ax_err, AxError, AxResult}; +use axfs_vfs::{VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType, VfsOps, VfsResult}; +use axsync::Mutex; +use lazy_init::LazyInit; + +use crate::{api::FileType, fs, mounts}; + +static CURRENT_DIR_PATH: Mutex = Mutex::new(String::new()); +static CURRENT_DIR: LazyInit> = LazyInit::new(); + +struct MountPoint { + path: &'static str, + fs: Arc, +} + +struct RootDirectory { + main_fs: Arc, + mounts: Vec, +} + +static ROOT_DIR: LazyInit> = LazyInit::new(); + +impl MountPoint { + pub fn new(path: &'static str, fs: Arc) -> Self { + Self { path, fs } + } +} + +impl Drop for MountPoint { + fn drop(&mut self) { + self.fs.umount().ok(); + } +} + +impl RootDirectory { + pub const fn new(main_fs: Arc) -> Self { + Self { + main_fs, + mounts: Vec::new(), + } + } + + pub fn mount(&mut self, path: &'static str, fs: Arc) -> AxResult { + if path == "/" { + return ax_err!(InvalidInput, "cannot mount root filesystem"); + } + if !path.starts_with('/') { + return ax_err!(InvalidInput, "mount path must start with '/'"); + } + if self.mounts.iter().any(|mp| mp.path == path) { + return ax_err!(InvalidInput, "mount point already exists"); + } + // create the mount point in the main filesystem if it does not exist + self.main_fs.root_dir().create(path, FileType::Dir)?; + fs.mount(path, self.main_fs.root_dir().lookup(path)?)?; + self.mounts.push(MountPoint::new(path, fs)); + Ok(()) + } + + pub fn _umount(&mut self, path: &str) { + self.mounts.retain(|mp| mp.path != path); + } + + pub fn contains(&self, path: &str) -> bool { + self.mounts.iter().any(|mp| mp.path == path) + } + + fn lookup_mounted_fs(&self, path: &str, f: F) -> AxResult + where + F: FnOnce(Arc, &str) -> AxResult, + { + debug!("lookup at root: {}", path); + let path = path.trim_matches('/'); + if let Some(rest) = path.strip_prefix("./") { + return self.lookup_mounted_fs(rest, f); + } + + let mut idx = 0; + let mut max_len = 0; + + // Find the filesystem that has the longest mounted path match + // TODO: more efficient, e.g. trie + + for (i, mp) in self.mounts.iter().enumerate() { + // skip the first '/' + // two conditions + // 1. path == mp.path, e.g. dev + // 2. path == mp.path + '/', e.g. dev/ + let prev = mp.path[1..].to_string() + "/"; + if path.starts_with(&mp.path[1..]) + && (path.len() == prev.len() - 1 || path.starts_with(&prev)) + && prev.len() > max_len + { + max_len = mp.path.len() - 1; + idx = i; + } + } + if max_len == 0 { + f(self.main_fs.clone(), path) // not matched any mount point + } else { + f(self.mounts[idx].fs.clone(), &path[max_len..]) // matched at `idx` + } + } +} + +impl VfsNodeOps for RootDirectory { + axfs_vfs::impl_vfs_dir_default! {} + + fn get_attr(&self) -> VfsResult { + self.main_fs.root_dir().get_attr() + } + + fn lookup(self: Arc, path: &str) -> VfsResult { + self.lookup_mounted_fs(path, |fs, rest_path| { + let dir = fs.root_dir(); + dir.lookup(rest_path) + }) + } + + fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult { + self.lookup_mounted_fs(path, |fs, rest_path| { + if rest_path.is_empty() { + Ok(()) // already exists + } else { + fs.root_dir().create(rest_path, ty) + } + }) + } + + fn remove(&self, path: &str) -> VfsResult { + self.lookup_mounted_fs(path, |fs, rest_path| { + if rest_path.is_empty() { + ax_err!(PermissionDenied) // cannot remove mount points + } else { + fs.root_dir().remove(rest_path) + } + }) + } + + fn rename(&self, src_path: &str, dst_path: &str) -> VfsResult { + self.lookup_mounted_fs(src_path, |fs, rest_path| { + if rest_path.is_empty() { + ax_err!(PermissionDenied) // cannot rename mount points + } else { + fs.root_dir().rename(rest_path, dst_path) + } + }) + } +} + +pub(crate) fn init_rootfs(disk: crate::dev::Disk) { + cfg_if::cfg_if! { + if #[cfg(feature = "myfs")] { // override the default filesystem + let main_fs = fs::myfs::new_myfs(disk); + } else if #[cfg(feature = "lwext4_rust")] { + static EXT4_FS: LazyInit> = LazyInit::new(); + EXT4_FS.init_by(Arc::new(fs::lwext4_rust::Ext4FileSystem::new(disk))); + let main_fs = EXT4_FS.clone(); + } else if #[cfg(feature = "ext4_rs")] { + static EXT4_FS: LazyInit> = LazyInit::new(); + EXT4_FS.init_by(Arc::new(fs::ext4_rs::Ext4FileSystem::new(disk))); + let main_fs = EXT4_FS.clone(); + } else if #[cfg(feature = "another_ext4")] { + static EXT4_FS: LazyInit> = LazyInit::new(); + EXT4_FS.init_by(Arc::new(fs::another_ext4::Ext4FileSystem::new(disk))); + let main_fs = EXT4_FS.clone(); + } else if #[cfg(feature = "fatfs")] { + // default to be fatfs + static FAT_FS: LazyInit> = LazyInit::new(); + FAT_FS.init_by(Arc::new(fs::fatfs::FatFileSystem::new(disk))); + FAT_FS.init(); + let main_fs = FAT_FS.clone(); + } + } + + let mut root_dir = RootDirectory::new(main_fs); + + #[cfg(feature = "devfs")] + root_dir + .mount("/dev", mounts::devfs()) + .expect("failed to mount devfs at /dev"); + + #[cfg(feature = "ramfs")] + root_dir + .mount("/dev/shm", mounts::ramfs()) + .expect("failed to mount devfs at /dev/shm"); + + #[cfg(feature = "ramfs")] + root_dir + .mount("/tmp", mounts::ramfs()) + .expect("failed to mount ramfs at /tmp"); + + #[cfg(feature = "ramfs")] + root_dir + .mount("/var", mounts::ramfs()) + .expect("failed to mount ramfs at /tmp"); + + // Mount another ramfs as procfs + #[cfg(feature = "procfs")] + root_dir // should not fail + .mount("/proc", mounts::procfs().unwrap()) + .expect("fail to mount procfs at /proc"); + + // Mount another ramfs as sysfs + #[cfg(feature = "sysfs")] + root_dir // should not fail + .mount("/sys", mounts::sysfs().unwrap()) + .expect("fail to mount sysfs at /sys"); + + ROOT_DIR.init_by(Arc::new(root_dir)); + CURRENT_DIR.init_by(Mutex::new(ROOT_DIR.clone())); + *CURRENT_DIR_PATH.lock() = "/".into(); +} + +fn parent_node_of(dir: Option<&VfsNodeRef>, path: &str) -> VfsNodeRef { + if path.starts_with('/') { + ROOT_DIR.clone() + } else { + dir.cloned().unwrap_or_else(|| CURRENT_DIR.lock().clone()) + } +} + +pub(crate) fn absolute_path(path: &str) -> AxResult { + if path.starts_with('/') { + Ok(axfs_vfs::path::canonicalize(path)) + } else { + let path = CURRENT_DIR_PATH.lock().clone() + path; + Ok(axfs_vfs::path::canonicalize(&path)) + } +} + +pub(crate) fn lookup(dir: Option<&VfsNodeRef>, path: &str) -> AxResult { + if path.is_empty() { + return ax_err!(NotFound); + } + let node = parent_node_of(dir, path).lookup(path)?; + if path.ends_with('/') && !node.get_attr()?.is_dir() { + ax_err!(NotADirectory) + } else { + Ok(node) + } +} + +pub(crate) fn create_file(dir: Option<&VfsNodeRef>, path: &str) -> AxResult { + if path.is_empty() { + return ax_err!(NotFound); + } else if path.ends_with('/') { + return ax_err!(NotADirectory); + } + let parent = parent_node_of(dir, path); + parent.create(path, VfsNodeType::File)?; + parent.lookup(path) +} + +pub(crate) fn create_dir(dir: Option<&VfsNodeRef>, path: &str) -> AxResult { + match lookup(dir, path) { + Ok(_) => ax_err!(AlreadyExists), + Err(AxError::NotFound) => parent_node_of(dir, path).create(path, VfsNodeType::Dir), + Err(e) => Err(e), + } +} + +pub(crate) fn remove_file(dir: Option<&VfsNodeRef>, path: &str) -> AxResult { + let node = lookup(dir, path)?; + let attr = node.get_attr()?; + if attr.is_dir() { + ax_err!(IsADirectory) + } else if !attr.perm().owner_writable() { + ax_err!(PermissionDenied) + } else { + parent_node_of(dir, path).remove(path) + } +} + +pub(crate) fn remove_dir(dir: Option<&VfsNodeRef>, path: &str) -> AxResult { + if path.is_empty() { + return ax_err!(NotFound); + } + let path_check = path.trim_matches('/'); + if path_check.is_empty() { + return ax_err!(DirectoryNotEmpty); // rm -d '/' + } else if path_check == "." + || path_check == ".." + || path_check.ends_with("/.") + || path_check.ends_with("/..") + { + return ax_err!(InvalidInput); + } + if ROOT_DIR.contains(&absolute_path(path)?) { + return ax_err!(PermissionDenied); + } + + let node = lookup(dir, path)?; + let attr = node.get_attr()?; + if !attr.is_dir() { + ax_err!(NotADirectory) + } else if !attr.perm().owner_writable() { + ax_err!(PermissionDenied) + } else { + parent_node_of(dir, path).remove(path) + } +} + +pub(crate) fn current_dir() -> AxResult { + Ok(CURRENT_DIR_PATH.lock().clone()) +} + +pub(crate) fn set_current_dir(path: &str) -> AxResult { + let mut abs_path = absolute_path(path)?; + if !abs_path.ends_with('/') { + abs_path += "/"; + } + if abs_path == "/" { + *CURRENT_DIR.lock() = ROOT_DIR.clone(); + *CURRENT_DIR_PATH.lock() = "/".into(); + return Ok(()); + } + + let node = lookup(None, &abs_path)?; + let attr = node.get_attr()?; + if !attr.is_dir() { + ax_err!(NotADirectory) + } else if !attr.perm().owner_executable() { + ax_err!(PermissionDenied) + } else { + *CURRENT_DIR.lock() = node; + *CURRENT_DIR_PATH.lock() = abs_path; + Ok(()) + } +} + +pub(crate) fn rename(old: &str, new: &str) -> AxResult { + if parent_node_of(None, new).lookup(new).is_ok() { + warn!("dst file already exist, now remove it"); + remove_file(None, new)?; + } + parent_node_of(None, old).rename(old, new) +} diff --git a/modules/axfs/tests/test_common/mod.rs b/modules/axfs/tests/test_common/mod.rs new file mode 100644 index 0000000..4746e37 --- /dev/null +++ b/modules/axfs/tests/test_common/mod.rs @@ -0,0 +1,262 @@ +use axfs::api as fs; +use axio as io; + +use fs::{File, FileType, OpenOptions}; +use io::{prelude::*, Error, Result}; + +macro_rules! assert_err { + ($expr: expr) => { + assert!(($expr).is_err()) + }; + ($expr: expr, $err: ident) => { + assert_eq!(($expr).err(), Some(Error::$err)) + }; +} + +fn test_read_write_file() -> Result<()> { + let fname = "///very/long//.././long//./path/./test.txt"; + println!("read and write file {:?}:", fname); + + // read and write + let mut file = File::options().read(true).write(true).open(fname)?; + let file_size = file.metadata()?.len(); + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + print!("{}", contents); + assert_eq!(contents.len(), file_size as usize); + assert_eq!(file.write(b"Hello, world!\n")?, 14); // append + drop(file); + + // read again and check + let new_contents = fs::read_to_string(fname)?; + print!("{}", new_contents); + assert_eq!(new_contents, contents + "Hello, world!\n"); + + // append and check + let mut file = OpenOptions::new().append(true).open(fname)?; + assert_eq!(file.write(b"new line\n")?, 9); + drop(file); + + let new_contents2 = fs::read_to_string(fname)?; + print!("{}", new_contents2); + assert_eq!(new_contents2, new_contents + "new line\n"); + + // open a non-exist file + assert_err!(File::open("/not/exist/file"), NotFound); + + println!("test_read_write_file() OK!"); + Ok(()) +} + +fn test_read_dir() -> Result<()> { + let dir = "/././//./"; + println!("list directory {:?}:", dir); + for entry in fs::read_dir(dir)? { + let entry = entry?; + println!(" {}", entry.file_name()); + } + println!("test_read_dir() OK!"); + Ok(()) +} + +fn test_file_permission() -> Result<()> { + let fname = "./short.txt"; + println!("test permission {:?}:", fname); + + // write a file that open with read-only mode + let mut buf = [0; 256]; + let mut file = File::open(fname)?; + let n = file.read(&mut buf)?; + assert_err!(file.write(&buf), PermissionDenied); + drop(file); + + // read a file that open with write-only mode + let mut file = File::create(fname)?; + assert_err!(file.read(&mut buf), PermissionDenied); + assert!(file.write(&buf[..n]).is_ok()); + drop(file); + + // open with empty options + assert_err!(OpenOptions::new().open(fname), InvalidInput); + + // read as a directory + assert_err!(fs::read_dir(fname), NotADirectory); + assert_err!(fs::read("short.txt/"), NotADirectory); + assert_err!(fs::metadata("/short.txt/"), NotADirectory); + + // create as a directory + assert_err!(fs::write("error/", "should not create"), NotADirectory); + assert_err!(fs::metadata("error/"), NotFound); + assert_err!(fs::metadata("error"), NotFound); + + // read/write a directory + assert_err!(fs::read_to_string("/dev"), IsADirectory); + assert_err!(fs::write(".", "test"), IsADirectory); + + println!("test_file_permisson() OK!"); + Ok(()) +} + +fn test_create_file_dir() -> Result<()> { + // create a file and test existence + let fname = "././/very-long-dir-name/..///new-file.txt"; + println!("test create file {:?}:", fname); + assert_err!(fs::metadata(fname), NotFound); + let contents = "create a new file!\n"; + fs::write(fname, contents)?; + + let dirents = fs::read_dir(".")? + .map(|e| e.unwrap().file_name()) + .collect::>(); + println!("dirents = {:?}", dirents); + assert!(dirents.contains(&"new-file.txt".into())); + assert_eq!(fs::read_to_string(fname)?, contents); + assert_err!(File::create_new(fname), AlreadyExists); + + // create a directory and test existence + let dirname = "///././/very//.//long/./new-dir"; + println!("test create dir {:?}:", dirname); + assert_err!(fs::metadata(dirname), NotFound); + fs::create_dir(dirname)?; + + let dirents = fs::read_dir("./very/long")? + .map(|e| e.unwrap().file_name()) + .collect::>(); + println!("dirents = {:?}", dirents); + assert!(dirents.contains(&"new-dir".into())); + assert!(fs::metadata(dirname)?.is_dir()); + assert_err!(fs::create_dir(dirname), AlreadyExists); + + println!("test_create_file_dir() OK!"); + Ok(()) +} + +fn test_remove_file_dir() -> Result<()> { + // remove a file and test existence + let fname = "//very-long-dir-name/..///new-file.txt"; + println!("test remove file {:?}:", fname); + assert_err!(fs::remove_dir(fname), NotADirectory); + assert!(fs::remove_file(fname).is_ok()); + assert_err!(fs::metadata(fname), NotFound); + assert_err!(fs::remove_file(fname), NotFound); + + // remove a directory and test existence + let dirname = "very//.//long/../long/.//./new-dir////"; + println!("test remove dir {:?}:", dirname); + assert_err!(fs::remove_file(dirname), IsADirectory); + assert!(fs::remove_dir(dirname).is_ok()); + assert_err!(fs::metadata(dirname), NotFound); + assert_err!(fs::remove_dir(fname), NotFound); + + // error cases + assert_err!(fs::remove_file(""), NotFound); + assert_err!(fs::remove_dir("/"), DirectoryNotEmpty); + assert_err!(fs::remove_dir("."), InvalidInput); + assert_err!(fs::remove_dir("../"), InvalidInput); + assert_err!(fs::remove_dir("./././/"), InvalidInput); + assert_err!(fs::remove_file("///very/./"), IsADirectory); + assert_err!(fs::remove_file("short.txt/"), NotADirectory); + assert_err!(fs::remove_dir(".///"), InvalidInput); + assert_err!(fs::remove_dir("/./very///"), DirectoryNotEmpty); + assert_err!(fs::remove_dir("very/long/.."), InvalidInput); + + println!("test_remove_file_dir() OK!"); + Ok(()) +} + +fn test_devfs_ramfs() -> Result<()> { + const N: usize = 32; + let mut buf = [1; N]; + + // list '/' and check if /dev and /tmp exist + let dirents = fs::read_dir("././//.//")? + .map(|e| e.unwrap().file_name()) + .collect::>(); + assert!(dirents.contains(&"dev".into())); + assert!(dirents.contains(&"tmp".into())); + + // read and write /dev/null + let mut file = File::options().read(true).write(true).open("/dev/./null")?; + assert_eq!(file.read_to_end(&mut Vec::new())?, 0); + assert_eq!(file.write(&buf)?, N); + assert_eq!(buf, [1; N]); + + // read and write /dev/zero + let mut file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open("////dev/zero")?; + assert_eq!(file.read(&mut buf)?, N); + assert!(file.write_all(&buf).is_ok()); + assert_eq!(buf, [0; N]); + + // list /dev + let dirents = fs::read_dir("/dev")? + .map(|e| e.unwrap().file_name()) + .collect::>(); + assert!(dirents.contains(&"null".into())); + assert!(dirents.contains(&"zero".into())); + + // stat /dev + let dname = "/dev"; + let dir = File::open(dname)?; + let md = dir.metadata()?; + println!("metadata of {:?}: {:?}", dname, md); + assert_eq!(md.file_type(), FileType::Dir); + assert!(!md.is_file()); + assert!(md.is_dir()); + + // stat /dev/foo/bar + let fname = ".//.///././/./dev///.///./foo//././bar"; + let file = File::open(fname)?; + let md = file.metadata()?; + println!("metadata of {:?}: {:?}", fname, md); + assert_eq!(md.file_type(), FileType::CharDevice); + assert!(!md.is_dir()); + + // error cases + assert_err!(fs::metadata("/dev/null/"), NotADirectory); + assert_err!(fs::create_dir("dev"), AlreadyExists); + assert_err!(File::create_new("/dev/"), AlreadyExists); + assert_err!(fs::create_dir("/dev/zero"), AlreadyExists); + assert_err!(fs::write("/dev/stdout", "test"), PermissionDenied); + assert_err!(fs::create_dir("/dev/test"), PermissionDenied); + assert_err!(fs::remove_file("/dev/null"), PermissionDenied); + assert_err!(fs::remove_dir("./dev"), PermissionDenied); + assert_err!(fs::remove_dir("./dev/."), InvalidInput); + assert_err!(fs::remove_dir("///dev//..//"), InvalidInput); + + // parent of '/dev' + assert_eq!(fs::create_dir("///dev//..//233//"), Ok(())); + assert_eq!(fs::write(".///dev//..//233//.///test.txt", "test"), Ok(())); + assert_err!(fs::remove_file("./dev//../..//233//.///test.txt"), NotFound); + assert_eq!(fs::remove_file("./dev//..//233//../233/./test.txt"), Ok(())); + assert_eq!(fs::remove_dir("dev//foo/../foo/../.././/233"), Ok(())); + assert_err!(fs::remove_dir("very/../dev//"), PermissionDenied); + + // tests in /tmp + assert_eq!(fs::metadata("tmp")?.file_type(), FileType::Dir); + assert_eq!(fs::create_dir(".///tmp///././dir"), Ok(())); + assert_eq!(fs::read_dir("tmp").unwrap().count(), 1); + assert_eq!(fs::write(".///tmp///dir//.///test.txt", "test"), Ok(())); + assert_eq!(fs::read("tmp//././/dir//.///test.txt"), Ok("test".into())); + // assert_err!(fs::remove_dir("dev/../tmp//dir"), DirectoryNotEmpty); // TODO + assert_err!(fs::remove_dir("/tmp/dir/../dir"), DirectoryNotEmpty); + assert_eq!(fs::remove_file("./tmp//dir//test.txt"), Ok(())); + assert_eq!(fs::remove_dir("tmp/dir/.././dir///"), Ok(())); + assert_eq!(fs::read_dir("tmp").unwrap().count(), 0); + + println!("test_devfs_ramfs() OK!"); + Ok(()) +} + +pub fn test_all() { + test_read_write_file().expect("test_read_write_file() failed"); + test_read_dir().expect("test_read_dir() failed"); + test_file_permission().expect("test_file_permission() failed"); + test_create_file_dir().expect("test_create_file_dir() failed"); + test_remove_file_dir().expect("test_remove_file_dir() failed"); + test_devfs_ramfs().expect("test_devfs_ramfs() failed"); +} diff --git a/modules/axfs/tests/test_fatfs.rs b/modules/axfs/tests/test_fatfs.rs new file mode 100644 index 0000000..f8d046f --- /dev/null +++ b/modules/axfs/tests/test_fatfs.rs @@ -0,0 +1,27 @@ +#![cfg(not(feature = "myfs"))] + +mod test_common; + +use axdriver::AxDeviceContainer; +use driver_block::ramdisk::RamDisk; + +const IMG_PATH: &str = "resources/fat16.img"; + +fn make_disk() -> std::io::Result { + let path = std::env::current_dir()?.join(IMG_PATH); + println!("Loading disk image from {:?} ...", path); + let data = std::fs::read(path)?; + println!("size = {} bytes", data.len()); + Ok(RamDisk::from(&data)) +} + +#[test] +fn test_fatfs() { + println!("Testing fatfs with ramdisk ..."); + + let disk = make_disk().expect("failed to load disk image"); + axtask::init_scheduler(); // call this to use `axsync::Mutex`. + axfs::init_filesystems(AxDeviceContainer::from_one(disk)); + + test_common::test_all(); +} diff --git a/modules/axfs/tests/test_ramfs.rs b/modules/axfs/tests/test_ramfs.rs new file mode 100644 index 0000000..09a9297 --- /dev/null +++ b/modules/axfs/tests/test_ramfs.rs @@ -0,0 +1,56 @@ +#![cfg(feature = "myfs")] + +mod test_common; + +use std::sync::Arc; + +use axdriver::AxDeviceContainer; +use axfs::api::{self as fs, File}; +use axfs::fops::{Disk, MyFileSystemIf}; +use axfs_ramfs::RamFileSystem; +use axfs_vfs::VfsOps; +use axio::{Result, Write}; +use driver_block::ramdisk::RamDisk; + +struct MyFileSystemIfImpl; + +#[crate_interface::impl_interface] +impl MyFileSystemIf for MyFileSystemIfImpl { + fn new_myfs(_disk: Disk) -> Arc { + Arc::new(RamFileSystem::new()) + } +} + +fn create_init_files() -> Result<()> { + fs::write("./short.txt", "Rust is cool!\n")?; + let mut file = File::create_new("/long.txt")?; + for _ in 0..100 { + file.write_fmt(format_args!("Rust is cool!\n"))?; + } + + fs::create_dir("very-long-dir-name")?; + fs::write( + "very-long-dir-name/very-long-file-name.txt", + "Rust is cool!\n", + )?; + + fs::create_dir("very")?; + fs::create_dir("//very/long")?; + fs::create_dir("/./very/long/path")?; + fs::write(".//very/long/path/test.txt", "Rust is cool!\n")?; + Ok(()) +} + +#[test] +fn test_ramfs() { + println!("Testing ramfs ..."); + + axtask::init_scheduler(); // call this to use `axsync::Mutex`. + axfs::init_filesystems(AxDeviceContainer::from_one(RamDisk::default())); // dummy disk, actually not used. + + if let Err(e) = create_init_files() { + log::warn!("failed to create init files: {:?}", e); + } + + test_common::test_all(); +} diff --git a/modules/axfutex/.gitignore b/modules/axfutex/.gitignore new file mode 100644 index 0000000..ada8be9 --- /dev/null +++ b/modules/axfutex/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb \ No newline at end of file diff --git a/modules/axfutex/.gitrepo b/modules/axfutex/.gitrepo new file mode 100644 index 0000000..650b731 --- /dev/null +++ b/modules/axfutex/.gitrepo @@ -0,0 +1,12 @@ +; DO NOT EDIT (unless you know what you are doing) +; +; This subdirectory is a git "subrepo", and this file is maintained by the +; git-subrepo command. See https://github.com/ingydotnet/git-subrepo#readme +; +[subrepo] + remote = git@github.com:Starry-OS/axfutex.git + branch = main + commit = 5eee19d07e750720c1b540dc8990dbf96c565433 + parent = e65ad8dacb25ad048e06ba7d10b1ddc081fbab7b + method = merge + cmdver = 0.4.9 diff --git a/modules/axfutex/Cargo.toml b/modules/axfutex/Cargo.toml new file mode 100644 index 0000000..629cdd4 --- /dev/null +++ b/modules/axfutex/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "axfutex" +version = "0.1.0" +edition = "2021" +authors = ["XinHao Li <3253290158@qq.com>"] +description = "A compatibility layer for futex on Starry" + +[features] + +fs = ["axfs"] + +monolithic = ["fs" ,"axfs/monolithic", "axhal/monolithic", "axtask/monolithic"] + +default = ["monolithic"] + +[dependencies] +axfs = { path = "../axfs", optional = true } +axhal = { path = "../axhal" } +log = "0.4.21" +axlog = { git = "https://github.com/Starry-OS/axlog.git" } +axtask = { path = "../axtask" } +axerrno = { git = "https://github.com/Starry-OS/axerrno.git" } +axsync = { path = "../axsync" } +hashbrown = "0.11" +lazy_static = "1.5.0" +bitflags = "2.6" +numeric-enum-macro = { git = "https://github.com/mexus/numeric-enum-macro" } \ No newline at end of file diff --git a/modules/axfutex/src/flags.rs b/modules/axfutex/src/flags.rs new file mode 100644 index 0000000..9e0d170 --- /dev/null +++ b/modules/axfutex/src/flags.rs @@ -0,0 +1,44 @@ +//! Futex operations and flags for posix syscall. +//! +//! Details are listed in + +numeric_enum_macro::numeric_enum! { + #[repr(i32)] + #[allow(missing_docs)] + #[allow(non_camel_case_types)] + #[derive(Eq, PartialEq, Debug, Copy, Clone)] + /// Futex operation for posix syscall listed in + /// + pub enum FutexOp { + WAIT = 0, + WAKE = 1, + FD = 2, + REQUEUE = 3, + CMP_REQUEUE = 4, + WAKE_OP = 5, + LOCK_PI = 6, + UNLOCK_PI = 7, + TRYLOCK_PI = 8, + WAIT_BITSET = 9, + WAKE_BITSET = 10, + WAIT_REQUEUE_PI = 11, + CMP_REQUEUE_PI = 12, + LOCK_PI2 = 13, + } +} + +bitflags::bitflags! { + #[allow(missing_docs)] + #[derive(PartialEq, Eq, Debug)] + /// Futex flags for posix syscall listed in + pub struct FutexFlags: i32 { + /// Futex is shared with other processes + const SHARED = 0x10; + /// Futex is process-private and not shared with another process + const PRIVATE = 128; + /// Futex is associated with CLOCK_REALTIME + const CLOCK_REALTIME = 256; + /// If the futex contains this flag,it will matche any bitset value + const BITSET_MATCH_ANY = -1; + } +} diff --git a/modules/axfutex/src/futex.rs b/modules/axfutex/src/futex.rs new file mode 100644 index 0000000..74ff0c3 --- /dev/null +++ b/modules/axfutex/src/futex.rs @@ -0,0 +1,50 @@ +use axtask::AxTaskRef; + +/// Kernel futex +pub struct FutexQ { + /// The `val` of the futex + /// the task in the queue waiting for the same futex may have different `val` + pub key: FutexKey, + /// the task which is waiting for the futex + pub task: AxTaskRef, + /// the bitset of the futex + pub bitset: u32, +} + +impl FutexQ { + /// Create a new futex queue + pub fn new(key: FutexKey, task: AxTaskRef, bitset: u32) -> Self { + Self { key, task, bitset } + } + /// check if the futex queues matches the key + pub fn match_key(&self, key: &FutexKey) -> bool { + self.key == *key + } +} + +/// Futexes are matched on equal values of this key. +/// +/// The key type depends on whether it's a shared or private mapping. +/// use pid to replace the mm_struct pointer +/// **only support private futex now** +#[derive(Copy, Clone, Default, Ord, PartialOrd, Eq, PartialEq, Debug)] +pub struct FutexKey { + /// use pid to replace the mm_struct pointer to distinguish different processes + /// only support private futex now + pub pid: u32, + // aligned to page size addr + pub(crate) aligned: u32, + // offset in page + pub(crate) offset: u32, +} + +impl FutexKey { + #[allow(missing_docs)] + pub fn new(pid: u64, aligned: usize, offset: u32) -> Self { + Self { + pid: pid as u32, + aligned: aligned as u32, + offset, + } + } +} diff --git a/modules/axfutex/src/jhash.rs b/modules/axfutex/src/jhash.rs new file mode 100644 index 0000000..64b5835 --- /dev/null +++ b/modules/axfutex/src/jhash.rs @@ -0,0 +1,91 @@ +const JHASH_INITVAL: u32 = 0xdeadbeef; + +#[inline(always)] +pub(crate) fn jhash_mix(a: &mut u32, b: &mut u32, c: &mut u32) { + *a = a.wrapping_sub(*c); + *a ^= c.rotate_left(4); + *c = c.wrapping_add(*b); + + *b = b.wrapping_sub(*a); + *b ^= a.rotate_left(6); + *a = a.wrapping_add(*c); + + *c = c.wrapping_sub(*b); + *c ^= b.rotate_left(8); + *b = b.wrapping_add(*a); + + *a = a.wrapping_sub(*c); + *a ^= c.rotate_left(16); + *c = c.wrapping_add(*b); + + *b = b.wrapping_sub(*a); + *b ^= a.rotate_left(19); + *a = a.wrapping_add(*c); + + *c = c.wrapping_sub(*b); + *c ^= b.rotate_left(4); + *b = b.wrapping_add(*a); +} + +pub(crate) fn jhash_final(mut a: u32, mut b: u32, mut c: u32) -> u32 { + c ^= b; + c = c.wrapping_sub(b.rotate_left(14)); + + a ^= c; + a = a.wrapping_sub(c.rotate_left(11)); + + b ^= a; + b = b.wrapping_sub(a.rotate_left(25)); + + c ^= b; + c = c.wrapping_sub(b.rotate_left(16)); + + a ^= c; + a = a.wrapping_sub(c.rotate_left(4)); + + b ^= a; + b = b.wrapping_sub(a.rotate_left(14)); + + c ^= b; + c = c.wrapping_sub(b.rotate_left(24)); + c +} + +pub(crate) fn jhash2(mut key: &[u32], initval: u32) -> u32 { + let mut a = JHASH_INITVAL + .wrapping_add(key.len() as u32) + .wrapping_add(initval); + let mut b = a; + let mut c = a; + + /* Handle most of the key */ + while key.len() > 3 { + a = a.wrapping_add(key[0]); + b = b.wrapping_add(key[1]); + c = c.wrapping_add(key[2]); + jhash_mix(&mut a, &mut b, &mut c); + key = &key[3..]; + } + + match key.len() { + 3 => { + c = c.wrapping_add(key[2]); + b = b.wrapping_add(key[1]); + a = a.wrapping_add(key[0]); + } + 2 => { + b = b.wrapping_add(key[1]); + a = a.wrapping_add(key[0]); + } + 1 => { + a = a.wrapping_add(key[0]); + } + 0 => { + return c; + } + _ => { + unreachable!("Never happen"); + } + } + jhash_final(a, b, c) +} diff --git a/modules/axfutex/src/lib.rs b/modules/axfutex/src/lib.rs new file mode 100644 index 0000000..cab93bf --- /dev/null +++ b/modules/axfutex/src/lib.rs @@ -0,0 +1,15 @@ +//! A module to implement futexes with jhash algorithm. +//! +//! Futex is a fast userspace mutex, which is used to implement synchronization primitives. +#![cfg_attr(all(not(test), not(doc)), no_std)] +#![feature(stmt_expr_attributes)] + +extern crate alloc; + +pub mod flags; +mod futex; +mod jhash; +mod queues; +pub use queues::{futex_hash, FUTEXQUEUES}; + +pub use futex::{FutexKey, FutexQ}; diff --git a/modules/axfutex/src/queues.rs b/modules/axfutex/src/queues.rs new file mode 100644 index 0000000..3dc3cfa --- /dev/null +++ b/modules/axfutex/src/queues.rs @@ -0,0 +1,65 @@ +use crate::futex::{FutexKey, FutexQ}; +use alloc::boxed::Box; +use alloc::collections::VecDeque; +use alloc::vec::Vec; +use axlog::info; + +use crate::jhash::jhash2; +use axsync::Mutex; +use lazy_static::lazy_static; + +/// the number of hash buckets, must be a power of 2 +const FUTEX_HASH_SIZE: usize = 256; + +lazy_static! { + /// A global futex queues, which stores all futex queues + /// It only holds the mutex through `futex_hash_bucket` + pub static ref FUTEXQUEUES: FutexQueues = { + info!("Initializing futex queues"); + FutexQueues::new(FUTEX_HASH_SIZE) + }; +} + +#[allow(unused)] +/// print all futex_q that are in the FUTEXQUEUES +pub fn display_futexqueues() { + axlog::warn!("[display_futexqueues]"); + for i in 0..FUTEX_HASH_SIZE { + let hash_bucket = FUTEXQUEUES.buckets[i].lock(); + if !hash_bucket.is_empty() { + for futex_q in hash_bucket.iter() { + axlog::warn!( + "task {} is still wait for {:?}", + futex_q.task.id().as_u64(), + futex_q.key + ); + } + } + drop(hash_bucket); + } +} + +/// the outer vector is the bucket, the inner vector is the futex queue +pub struct FutexQueues { + /// buckets of futex queues + pub buckets: Box<[Mutex>]>, +} + +impl FutexQueues { + fn new(size: usize) -> Self { + let mut buckets = Vec::with_capacity(size); + for _ in 0..size { + buckets.push(Mutex::new(VecDeque::new())); + } + Self { + buckets: buckets.into_boxed_slice(), + } + } +} + +/// Hash a [`FutexKey`] to a bucket index +pub fn futex_hash(futex_key: &FutexKey) -> usize { + let key = &[futex_key.pid, futex_key.aligned, futex_key.offset]; + let hash = jhash2(key, key[2]); + hash as usize & (FUTEX_HASH_SIZE - 1) +} diff --git a/modules/axhal/.gitignore b/modules/axhal/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/modules/axhal/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/modules/axhal/Cargo.toml b/modules/axhal/Cargo.toml new file mode 100644 index 0000000..7edac78 --- /dev/null +++ b/modules/axhal/Cargo.toml @@ -0,0 +1,64 @@ +[package] +name = "axhal" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "ArceOS hardware abstraction layer, provides unified APIs for platform-specific operations" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axhal" +documentation = "https://rcore-os.github.io/arceos/axhal/index.html" +keywords = ["Starry"] + +[features] +smp = [] +alloc = [] +fp_simd = ["taskctx/fp_simd"] +paging = ["axalloc", "page_table"] +irq = [] +tls = ["alloc"] +monolithic = ["paging", "dep:axfs_ramfs"] +default = [] + +[dependencies] +log = "0.4" +cfg-if = "1.0" +bitflags = "2.6" +static_assertions = "1.1.0" +axlog = { workspace = true } +axconfig = { workspace = true } +axalloc = { workspace = true, optional = true } +kernel_guard = { git = "https://github.com/Starry-OS/kernel_guard.git" } +spinlock = { git = "https://github.com/Starry-OS/spinlock.git" } +ratio = { git = "https://github.com/Starry-OS/ratio.git" } +lazy_init = { git = "https://github.com/Starry-OS/lazy_init.git" } +page_table = { git = "https://github.com/Starry-OS/page_table.git", optional = true } +page_table_entry = { git = "https://github.com/Starry-OS/page_table_entry.git" } +percpu = { git = "https://github.com/Starry-OS/percpu.git" } +memory_addr = { git = "https://github.com/Starry-OS/memory_addr.git" } +handler_table = { git = "https://github.com/Starry-OS/handler_table.git" } +crate_interface = { git = "https://github.com/Starry-OS/crate_interface.git" } +axfs_ramfs = { git = "https://github.com/Starry-OS/axfs_ramfs.git", optional = true } +taskctx = { git = "https://github.com/Starry-OS/taskctx.git" } +of = { git = "https://github.com/Starry-OS/of.git"} + +[target.'cfg(target_arch = "x86_64")'.dependencies] +x86 = "0.52" +x86_64 = "0.15" +x2apic = "0.4" +raw-cpuid = "11.0" + +[target.'cfg(any(target_arch = "riscv32", target_arch = "riscv64"))'.dependencies] +riscv = "0.11" +sbi-rt = { version = "0.0.3", features = ["legacy"] } + +[target.'cfg(target_arch = "aarch64")'.dependencies] +aarch64-cpu = "9.4" +tock-registers = "0.8" +either = {version = "1.6", default-features = false} +arm_gic = { git = "https://github.com/Starry-OS/arm_gic.git" } +arm_pl011 = { git = "https://github.com/Starry-OS/arm_pl011.git" } +dw_apb_uart = { git = "https://github.com/Starry-OS/dw_apb_uart.git" } + +[build-dependencies] +axconfig = { workspace = true } diff --git a/modules/axhal/build.rs b/modules/axhal/build.rs new file mode 100644 index 0000000..fd514bf --- /dev/null +++ b/modules/axhal/build.rs @@ -0,0 +1,75 @@ +use std::io::Result; +use std::path::Path; + +const BUILTIN_PLATFORMS: &[&str] = &[ + "aarch64-bsta1000b", + "aarch64-qemu-virt", + "aarch64-raspi4", + "aarch64-phytiumpi", + "riscv64-qemu-virt", + "aarch64-rk3588j", + "x86_64-pc-oslab", + "x86_64-qemu-q35", +]; + +const BUILTIN_PLATFORM_FAMILIES: &[&str] = &[ + "aarch64-bsta1000b", + "aarch64-qemu-virt", + "aarch64-raspi", + "aarch64-phytiumpi", + "aarch64-rk3588j", + "riscv64-qemu-virt", + "x86-pc", +]; + +fn make_cfg_values(str_list: &[&str]) -> String { + str_list + .iter() + .map(|s| format!("{:?}", s)) + .collect::>() + .join(", ") +} + +fn main() { + let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + let platform = axconfig::PLATFORM; + if platform != "dummy" { + gen_linker_script(&arch, platform).unwrap(); + } + + println!("cargo:rustc-cfg=platform=\"{}\"", platform); + println!("cargo:rustc-cfg=platform_family=\"{}\"", axconfig::FAMILY); + println!( + "cargo::rustc-check-cfg=cfg(platform, values({}))", + make_cfg_values(BUILTIN_PLATFORMS) + ); + println!( + "cargo::rustc-check-cfg=cfg(platform_family, values({}))", + make_cfg_values(BUILTIN_PLATFORM_FAMILIES) + ); +} + +fn gen_linker_script(arch: &str, platform: &str) -> Result<()> { + let fname = format!("linker_{}.lds", platform); + let output_arch = if arch == "x86_64" { + "i386:x86-64" + } else if arch.contains("riscv") { + "riscv" // OUTPUT_ARCH of both riscv32/riscv64 is "riscv" + } else { + arch + }; + let ld_content = std::fs::read_to_string("linker.lds.S")?; + let ld_content = ld_content.replace("%ARCH%", output_arch); + let ld_content = ld_content.replace( + "%KERNEL_BASE%", + &format!("{:#x}", axconfig::KERNEL_BASE_VADDR), + ); + let ld_content = ld_content.replace("%SMP%", &format!("{}", axconfig::SMP)); + + // target///build/axhal-xxxx/out + let out_dir = std::env::var("AX_WORK_DIR").unwrap(); + // target///linker_xxxx.lds + let out_path = Path::new(&out_dir).join(fname); + std::fs::write(out_path, ld_content)?; + Ok(()) +} diff --git a/modules/axhal/linker.lds.S b/modules/axhal/linker.lds.S new file mode 100644 index 0000000..eb6fb8b --- /dev/null +++ b/modules/axhal/linker.lds.S @@ -0,0 +1,96 @@ +OUTPUT_ARCH(%ARCH%) + +BASE_ADDRESS = %KERNEL_BASE%; + +ENTRY(_start) +SECTIONS +{ + . = BASE_ADDRESS; + _skernel = .; + + .text : ALIGN(4K) { + _stext = .; + *(.text.boot) + . = ALIGN(4K); + *(.text.signal_trampoline) + . = ALIGN(4K); + *(.text .text.*) + . = ALIGN(4K); + _etext = .; + } + + .rodata : ALIGN(4K) { + _srodata = .; + *(.rodata .rodata.*) + *(.srodata .srodata.*) + *(.sdata2 .sdata2.*) + . = ALIGN(4K); + _erodata = .; + } + + .data : ALIGN(4K) { + _sdata = .; + *(.data.boot_page_table) + . = ALIGN(4K); + _img_start = .; + . = ALIGN(4K); + _img_end = .; + . = ALIGN(4K); + *(.data .data.*) + *(.sdata .sdata.*) + *(.got .got.*) + _initcall = .; + KEEP(*(.initcall)) + _initcall_end =.; + } + + .tdata : ALIGN(0x10) { + _stdata = .; + *(.tdata .tdata.*) + _etdata = .; + } + + .tbss : ALIGN(0x10) { + _stbss = .; + *(.tbss .tbss.*) + *(.tcommon) + _etbss = .; + } + + . = ALIGN(4K); + _percpu_start = .; + .percpu 0x0 : AT(_percpu_start) { + _percpu_load_start = .; + *(.percpu .percpu.*) + _percpu_load_end = .; + . = ALIGN(64); + _percpu_size_aligned = .; + + . = _percpu_load_start + _percpu_size_aligned * %SMP%; + } + . = _percpu_start + SIZEOF(.percpu); + _percpu_end = .; + + . = ALIGN(4K); + _edata = .; + + .bss : ALIGN(4K) { + boot_stack = .; + *(.bss.stack) + . = ALIGN(4K); + boot_stack_top = .; + + _sbss = .; + *(.bss .bss.*) + *(.sbss .sbss.*) + *(COMMON) + . = ALIGN(4K); + _ebss = .; + } + + _ekernel = .; + + /DISCARD/ : { + *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) + } +} diff --git a/modules/axhal/src/arch/aarch64/context.rs b/modules/axhal/src/arch/aarch64/context.rs new file mode 100644 index 0000000..8e69739 --- /dev/null +++ b/modules/axhal/src/arch/aarch64/context.rs @@ -0,0 +1,151 @@ +use taskctx::TaskContext; + +/// Saved registers when a trap (exception) occurs. +#[repr(C)] +#[derive(Debug, Default, Clone, Copy)] +pub struct TrapFrame { + /// General-purpose registers (R0..R30). + pub r: [usize; 31], + /// Stack Poiter + pub usp: usize, + /// Exception Link Register (ELR_EL1). + pub elr: usize, + /// Saved Process Status Register (SPSR_EL1). + pub spsr: usize, + /// Saved tpidr_el0. + pub tpidr_el0: usize, +} + +impl TrapFrame { + /// To set the stack pointer + pub fn set_user_sp(&mut self, user_sp: usize) { + self.usp = user_sp; + } + + pub fn get_sp(&self) -> usize { + self.usp + } + + pub fn get_pc(&self) -> usize { + self.elr + } + + pub fn set_pc(&mut self, pc: usize) { + self.elr = pc; + } + + /// pc 倒退到 syscall 指令的长度 + pub fn rewind_pc(&mut self) { + self.elr -= 4; + } + + pub fn set_tls(&mut self, tls: usize) { + self.tpidr_el0 = tls; + } + + pub fn set_ret_code(&mut self, ret: usize) { + self.r[0] = ret; + } + + pub fn get_ret_code(&self) -> usize { + self.r[0] + } + + pub fn set_arg0(&mut self, param: usize) { + self.r[0] = param; + } + + pub fn set_arg1(&mut self, param: usize) { + self.r[1] = param; + } + + pub fn set_arg2(&mut self, param: usize) { + self.r[2] = param; + } + + /// set the return address + pub fn set_ra(&mut self, param: usize) { + self.r[30] = param; + } + + /// 用于第一次进入应用程序时的初始化 + pub fn app_init_context(app_entry: usize, user_sp: usize) -> Self { + let mut trap_frame = TrapFrame::default(); + trap_frame.set_user_sp(user_sp); + trap_frame.elr = app_entry; + trap_frame.spsr = 0x00000000; + trap_frame + } +} + +#[no_mangle] +#[cfg(feature = "monolithic")] +/// To handle the first time into the user space +/// +/// 1. push the given trap frame into the kernel stack +/// 2. go into the user space +/// +/// args: +/// +/// 1. kernel_sp: the top of the kernel stack +pub fn first_into_user(kernel_sp: usize) -> ! { + use crate::arch::disable_irqs; + + let trap_frame_size = core::mem::size_of::(); + let kernel_base = kernel_sp - trap_frame_size; + info!("kernel_base {:#x} kernel_sp{:#x}", kernel_base, kernel_sp); + // 在保证将寄存器都存储好之后,再开启中断 + disable_irqs(); + crate::arch::flush_tlb(None); + crate::arch::flush_icache_all(); + //crate::arch::flush_dcache_all(); + unsafe { + core::arch::asm!( + r" + mov sp, {kernel_base} + ldp x30, x9, [sp, 30 * 8] // load user sp_el0 + ldp x10, x11, [sp, 32 * 8] // load ELR, SPSR + msr elr_el1, x10 + msr spsr_el1, x11 + + ldr x12, [sp, 34 * 8] + + msr tpidr_el0, x12 // restore user tls pointer + + mrs x13, sp_el0 // save current ktask ptr + str x13, [sp, 31 * 8] + msr sp_el0, x9 // restore user sp + + ldp x28, x29, [sp, 28 * 8] + ldp x26, x27, [sp, 26 * 8] + ldp x24, x25, [sp, 24 * 8] + ldp x22, x23, [sp, 22 * 8] + ldp x20, x21, [sp, 20 * 8] + ldp x18, x19, [sp, 18 * 8] + ldp x16, x17, [sp, 16 * 8] + ldp x14, x15, [sp, 14 * 8] + ldp x12, x13, [sp, 12 * 8] + ldp x10, x11, [sp, 10 * 8] + ldp x8, x9, [sp, 8 * 8] + ldp x6, x7, [sp, 6 * 8] + ldp x4, x5, [sp, 4 * 8] + ldp x2, x3, [sp, 2 * 8] + ldp x0, x1, [sp] + add sp, sp, 35 * 8 + eret + ", + kernel_base = in(reg) kernel_base, + ) + } + core::panic!("already in user mode!") +} + +/// Switches to another task. +/// +/// It first saves the current task's context from CPU to this place, and then +/// restores the next task's context from `next_ctx` to CPU. +pub fn task_context_switch(prev_ctx: &mut TaskContext, next_ctx: &TaskContext) { + #[cfg(feature = "fp_simd")] + prev_ctx.fp_state.switch_to(&next_ctx.fp_state); + unsafe { taskctx::context_switch(prev_ctx, next_ctx) } +} diff --git a/modules/axhal/src/arch/aarch64/mod.rs b/modules/axhal/src/arch/aarch64/mod.rs new file mode 100644 index 0000000..3fa759f --- /dev/null +++ b/modules/axhal/src/arch/aarch64/mod.rs @@ -0,0 +1,135 @@ +mod context; +pub use context::task_context_switch; +use core::arch::asm; + +#[cfg(feature = "monolithic")] +pub use context::first_into_user; + +use aarch64_cpu::registers::{DAIF, TPIDR_EL0, TTBR0_EL1, TTBR1_EL1}; +use memory_addr::{PhysAddr, VirtAddr}; +use tock_registers::interfaces::{Readable, Writeable}; + +pub use self::context::TrapFrame; + +/// Allows the current CPU to respond to interrupts. +#[inline] +pub fn enable_irqs() { + unsafe { asm!("msr daifclr, #2") }; +} + +/// Makes the current CPU to ignore interrupts. +#[inline] +pub fn disable_irqs() { + unsafe { asm!("msr daifset, #2") }; +} + +/// Returns whether the current CPU is allowed to respond to interrupts. +#[inline] +pub fn irqs_enabled() -> bool { + !DAIF.matches_all(DAIF::I::Masked) +} + +/// Relaxes the current CPU and waits for interrupts. +/// +/// It must be called with interrupts enabled, otherwise it will never return. +#[inline] +pub fn wait_for_irqs() { + aarch64_cpu::asm::wfi(); +} + +/// Halt the current CPU. +#[inline] +pub fn halt() { + disable_irqs(); + aarch64_cpu::asm::wfi(); // should never return +} + +/// Reads the register that stores the current page table root. +/// +/// Returns the physical address of the page table root. +#[inline] +pub fn read_page_table_root() -> PhysAddr { + let root = TTBR1_EL1.get(); + PhysAddr::from(root as usize) +} + +/// Reads the `TTBR0_EL1` register. +pub fn read_page_table_root0() -> PhysAddr { + let root = TTBR0_EL1.get(); + PhysAddr::from(root as usize) +} + +/// Writes the register to update the current page table root. +/// +/// # Safety +/// +/// This function is unsafe as it changes the virtual memory address space. +pub unsafe fn write_page_table_root(root_paddr: PhysAddr) { + let old_root = read_page_table_root(); + trace!("set page table root: {:#x} => {:#x}", old_root, root_paddr); + if old_root != root_paddr { + // kernel space page table use TTBR1 (0xffff_0000_0000_0000..0xffff_ffff_ffff_ffff) + TTBR1_EL1.set(root_paddr.as_usize() as _); + flush_tlb(None); + } +} + +/// Writes the `TTBR0_EL1` register. +/// +/// # Safety +/// +/// This function is unsafe as it changes the virtual memory address space. +pub unsafe fn write_page_table_root0(root_paddr: PhysAddr) { + TTBR0_EL1.set(root_paddr.as_usize() as _); + flush_tlb(None); +} + +/// Flushes the TLB. +/// +/// If `vaddr` is [`None`], flushes the entire TLB. Otherwise, flushes the TLB +/// entry that maps the given virtual address. +#[inline] +pub fn flush_tlb(vaddr: Option) { + unsafe { + if let Some(vaddr) = vaddr { + asm!("tlbi vaae1is, {}; dsb sy; isb", in(reg) vaddr.as_usize()) + } else { + // flush the entire TLB + asm!("tlbi vmalle1; dsb sy; isb") + } + } +} + +/// Flushes the entire instruction cache. +#[inline] +pub fn flush_icache_all() { + unsafe { asm!("ic iallu; dsb sy; isb") }; +} + +/// Flushes the data cache line (64 bytes) at the given virtual address +#[inline] +pub fn flush_dcache_line(vaddr: VirtAddr) { + unsafe { asm!("dc ivac, {0:x}; dsb sy; isb", in(reg) vaddr.as_usize()) }; +} + +/// Reads the thread pointer of the current CPU. +/// +/// It is used to implement TLS (Thread Local Storage). +#[inline] +pub fn read_thread_pointer() -> usize { + TPIDR_EL0.get() as usize +} + +/// Writes the thread pointer of the current CPU. +/// +/// It is used to implement TLS (Thread Local Storage). +/// +/// # Safety +/// +/// This function is unsafe as it changes the CPU states. +#[inline] +pub unsafe fn write_thread_pointer(tpidr_el0: usize) { + TPIDR_EL0.set(tpidr_el0 as _) +} + +core::arch::global_asm!(include_str!("signal.S")); diff --git a/modules/axhal/src/arch/aarch64/signal.S b/modules/axhal/src/arch/aarch64/signal.S new file mode 100644 index 0000000..ec2f360 --- /dev/null +++ b/modules/axhal/src/arch/aarch64/signal.S @@ -0,0 +1,8 @@ +# To create the sigreturn trampoline +.equ __NR_sigreturn, 139 +.section .text.signal_trampoline +.balign 4 +.global start_signal_trampoline +start_signal_trampoline: + mov x8, #139 // 设置系统调用号为 139 + svc #0 // 触发系统调用 \ No newline at end of file diff --git a/modules/axhal/src/arch/mod.rs b/modules/axhal/src/arch/mod.rs new file mode 100644 index 0000000..35f4c52 --- /dev/null +++ b/modules/axhal/src/arch/mod.rs @@ -0,0 +1,40 @@ +//! Architecture-specific types and operations. + +cfg_if::cfg_if! { + if #[cfg(target_arch = "x86_64")] { + mod x86_64; + pub use self::x86_64::*; + } else if #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] { + mod riscv; + pub use self::riscv::*; + } else if #[cfg(target_arch = "aarch64")]{ + mod aarch64; + pub use self::aarch64::*; + } +} + +#[cfg(feature = "monolithic")] +/// To write the trap frame into the kernel stack +/// +/// # Safety +/// +/// It should be guaranteed that the kstack address is valid and writable. +pub fn write_trapframe_to_kstack(kstack_top: usize, trap_frame: &TrapFrame) { + let trap_frame_size = core::mem::size_of::(); + let trap_frame_ptr = (kstack_top - trap_frame_size) as *mut TrapFrame; + unsafe { + *trap_frame_ptr = *trap_frame; + } +} + +#[cfg(feature = "monolithic")] +/// To read the trap frame from the kernel stack +/// +/// # Safety +/// +/// It should be guaranteed that the kstack address is valid and readable. +pub fn read_trapframe_from_kstack(kstack_top: usize) -> TrapFrame { + let trap_frame_size = core::mem::size_of::(); + let trap_frame_ptr = (kstack_top - trap_frame_size) as *mut TrapFrame; + unsafe { *trap_frame_ptr } +} diff --git a/modules/axhal/src/arch/riscv/context.rs b/modules/axhal/src/arch/riscv/context.rs new file mode 100644 index 0000000..0ff925b --- /dev/null +++ b/modules/axhal/src/arch/riscv/context.rs @@ -0,0 +1,214 @@ +use riscv::register::sstatus::{self, Sstatus}; +use taskctx::TaskContext; +include_asm_marcos!(); + +/// General registers of RISC-V. +#[allow(missing_docs)] +#[repr(C)] +#[derive(Debug, Default, Clone, Copy)] +pub struct GeneralRegisters { + pub ra: usize, + pub sp: usize, + pub gp: usize, // only valid for user traps + pub tp: usize, // only valid for user traps + pub t0: usize, + pub t1: usize, + pub t2: usize, + pub s0: usize, + pub s1: usize, + pub a0: usize, + pub a1: usize, + pub a2: usize, + pub a3: usize, + pub a4: usize, + pub a5: usize, + pub a6: usize, + pub a7: usize, + pub s2: usize, + pub s3: usize, + pub s4: usize, + pub s5: usize, + pub s6: usize, + pub s7: usize, + pub s8: usize, + pub s9: usize, + pub s10: usize, + pub s11: usize, + pub t3: usize, + pub t4: usize, + pub t5: usize, + pub t6: usize, +} + +/// Saved registers when a trap (interrupt or exception) occurs. +#[repr(C)] +#[derive(Debug, Default, Clone, Copy)] +pub struct TrapFrame { + /// All general registers. + pub regs: GeneralRegisters, + /// Supervisor Exception Program Counter. + pub sepc: usize, + /// Supervisor Status Register. + pub sstatus: usize, + /// 浮点数寄存器 + pub fs: [usize; 2], +} + +impl TrapFrame { + pub fn set_user_sp(&mut self, user_sp: usize) { + self.regs.sp = user_sp; + } + + /// 用于第一次进入应用程序时的初始化 + pub fn app_init_context(app_entry: usize, user_sp: usize) -> Self { + let sstatus = sstatus::read(); + // 当前版本的riscv不支持使用set_spp函数,需要手动修改 + // 修改当前的sstatus为User,即是第8位置0 + let mut trap_frame = TrapFrame::default(); + trap_frame.set_user_sp(user_sp); + trap_frame.sepc = app_entry; + trap_frame.sstatus = + unsafe { (*(&sstatus as *const Sstatus as *const usize) & !(1 << 8)) & !(1 << 1) }; + unsafe { + // a0为参数个数 + // a1存储的是用户栈底,即argv + trap_frame.regs.a0 = *(user_sp as *const usize); + trap_frame.regs.a1 = *(user_sp as *const usize).add(1); + } + trap_frame + } + + /// 设置返回值 + pub fn set_ret_code(&mut self, ret_value: usize) { + self.regs.a0 = ret_value; + } + + /// 设置TLS + pub fn set_tls(&mut self, tls_value: usize) { + self.regs.tp = tls_value; + } + + /// 获取 sp + pub fn get_sp(&self) -> usize { + self.regs.sp + } + + /// 设置 pc + pub fn set_pc(&mut self, pc: usize) { + self.sepc = pc; + } + + /// pc 倒退到 syscall 指令的长度 + pub fn rewind_pc(&mut self) { + self.sepc -= 4; + } + + /// 设置 arg0 + pub fn set_arg0(&mut self, arg: usize) { + self.regs.a0 = arg; + } + + /// 设置 arg1 + pub fn set_arg1(&mut self, arg: usize) { + self.regs.a1 = arg; + } + + /// 设置 arg2 + pub fn set_arg2(&mut self, arg: usize) { + self.regs.a2 = arg; + } + + /// 获取 pc + pub fn get_pc(&self) -> usize { + self.sepc + } + + /// 获取 ret + pub fn get_ret_code(&self) -> usize { + self.regs.a0 + } + + /// 设置返回地址 + pub fn set_ra(&mut self, ra: usize) { + self.regs.ra = ra; + } + + /// 获取所有 syscall 参数 + pub fn get_syscall_args(&self) -> [usize; 6] { + [ + self.regs.a0, + self.regs.a1, + self.regs.a2, + self.regs.a3, + self.regs.a4, + self.regs.a5, + ] + } + + /// 获取 syscall id + pub fn get_syscall_num(&self) -> usize { + self.regs.a7 as _ + } +} + +#[no_mangle] +#[cfg(feature = "monolithic")] +/// To handle the first time into the user space +/// +/// 1. push the given trap frame into the kernel stack +/// 2. go into the user space +/// +/// args: +/// +/// 1. kernel_sp: the top of the kernel stack +/// +/// 2. frame_base: the address of the trap frame which will be pushed into the kernel stack +pub fn first_into_user(kernel_sp: usize) { + // Make sure that all csr registers are stored before enable the interrupt + use crate::arch::{disable_irqs, flush_tlb}; + + disable_irqs(); + flush_tlb(None); + + let trap_frame_size = core::mem::size_of::(); + let kernel_base = kernel_sp - trap_frame_size; + unsafe { + core::arch::asm!( + r" + mv sp, {kernel_base} + .short 0x2432 // fld fs0,264(sp) + .short 0x24d2 // fld fs1,272(sp) + LDR t0, sp, 2 + STR gp, sp, 2 + mv gp, t0 + LDR t0, sp, 3 + STR tp, sp, 3 // save supervisor tp. Note that it is stored on the kernel stack rather than in sp, in which case the ID of the currently running CPU should be stored + mv tp, t0 // tp: now it stores the TLS pointer to the corresponding thread + csrw sscratch, {kernel_sp} // put supervisor sp to scratch + LDR t0, sp, 31 + LDR t1, sp, 32 + csrw sepc, t0 + csrw sstatus, t1 + POP_GENERAL_REGS + LDR sp, sp, 1 + sret + ", + kernel_sp = in(reg) kernel_sp, + kernel_base = in(reg) kernel_base, + ); + }; +} + +#[allow(unused)] +/// To switch the context between two tasks +pub fn task_context_switch(prev_ctx: &mut TaskContext, next_ctx: &TaskContext) { + #[cfg(feature = "tls")] + { + prev_ctx.tp = super::read_thread_pointer(); + unsafe { super::write_thread_pointer(next_ctx.tp) }; + } + unsafe { + // TODO: switch FP states + taskctx::context_switch(prev_ctx, next_ctx) + } +} diff --git a/modules/axhal/src/arch/riscv/macros.rs b/modules/axhal/src/arch/riscv/macros.rs new file mode 100644 index 0000000..c36c4da --- /dev/null +++ b/modules/axhal/src/arch/riscv/macros.rs @@ -0,0 +1,81 @@ +macro_rules! include_asm_marcos { + () => { + #[cfg(target_arch = "riscv32")] + core::arch::global_asm!( + r" + .ifndef XLENB + .equ XLENB, 4 + + .macro LDR rd, rs, off + lw \rd, \off*XLENB(\rs) + .endm + .macro STR rs2, rs1, off + sw \rs2, \off*XLENB(\rs1) + .endm + + .endif" + ); + + #[cfg(target_arch = "riscv64")] + core::arch::global_asm!( + r" + .ifndef XLENB + .equ XLENB, 8 + + .macro LDR rd, rs, off + ld \rd, \off*XLENB(\rs) + .endm + .macro STR rs2, rs1, off + sd \rs2, \off*XLENB(\rs1) + .endm + + .endif", + ); + + core::arch::global_asm!( + r" + .ifndef .LPUSH_POP_GENERAL_REGS + .equ .LPUSH_POP_GENERAL_REGS, 0 + + .macro PUSH_POP_GENERAL_REGS, op + \op ra, sp, 0 + \op t0, sp, 4 + \op t1, sp, 5 + \op t2, sp, 6 + \op s0, sp, 7 + \op s1, sp, 8 + \op a0, sp, 9 + \op a1, sp, 10 + \op a2, sp, 11 + \op a3, sp, 12 + \op a4, sp, 13 + \op a5, sp, 14 + \op a6, sp, 15 + \op a7, sp, 16 + \op s2, sp, 17 + \op s3, sp, 18 + \op s4, sp, 19 + \op s5, sp, 20 + \op s6, sp, 21 + \op s7, sp, 22 + \op s8, sp, 23 + \op s9, sp, 24 + \op s10, sp, 25 + \op s11, sp, 26 + \op t3, sp, 27 + \op t4, sp, 28 + \op t5, sp, 29 + \op t6, sp, 30 + .endm + + .macro PUSH_GENERAL_REGS + PUSH_POP_GENERAL_REGS STR + .endm + .macro POP_GENERAL_REGS + PUSH_POP_GENERAL_REGS LDR + .endm + + .endif" + ); + }; +} diff --git a/modules/axhal/src/arch/riscv/mod.rs b/modules/axhal/src/arch/riscv/mod.rs new file mode 100644 index 0000000..b1a9c2f --- /dev/null +++ b/modules/axhal/src/arch/riscv/mod.rs @@ -0,0 +1,112 @@ +#[macro_use] +mod macros; + +mod context; + +pub use self::context::{GeneralRegisters, TrapFrame}; + +#[cfg(feature = "monolithic")] +pub use context::first_into_user; + +pub use context::task_context_switch; + +use memory_addr::{PhysAddr, VirtAddr}; +use riscv::asm; +use riscv::register::{satp, sstatus}; + +/// Allows the current CPU to respond to interrupts. +#[inline] +pub fn enable_irqs() { + unsafe { sstatus::set_sie() } +} + +/// Makes the current CPU to ignore interrupts. +#[inline] +pub fn disable_irqs() { + unsafe { sstatus::clear_sie() } +} + +/// Returns whether the current CPU is allowed to respond to interrupts. +#[inline] +pub fn irqs_enabled() -> bool { + sstatus::read().sie() +} + +/// Relaxes the current CPU and waits for interrupts. +/// +/// It must be called with interrupts enabled, otherwise it will never return. +#[inline] +pub fn wait_for_irqs() { + riscv::asm::wfi() +} + +/// Halt the current CPU. +#[inline] +pub fn halt() { + disable_irqs(); + riscv::asm::wfi() // should never return +} + +/// Reads the register that stores the current page table root. +/// +/// Returns the physical address of the page table root. +#[inline] +pub fn read_page_table_root() -> PhysAddr { + PhysAddr::from(satp::read().ppn() << 12) +} + +/// Writes the register to update the current page table root. +/// +/// # Safety +/// +/// This function is unsafe as it changes the virtual memory address space. +pub unsafe fn write_page_table_root(root_paddr: PhysAddr) { + let old_root = read_page_table_root(); + trace!("set page table root: {:#x} => {:#x}", old_root, root_paddr); + if old_root != root_paddr { + satp::set(satp::Mode::Sv39, 0, root_paddr.as_usize() >> 12); + asm::sfence_vma_all(); + } +} +pub use self::write_page_table_root as write_page_table_root0; + +/// Flushes the TLB. +/// +/// If `vaddr` is [`None`], flushes the entire TLB. Otherwise, flushes the TLB +/// entry that maps the given virtual address. +#[inline] +pub fn flush_tlb(vaddr: Option) { + unsafe { + if let Some(vaddr) = vaddr { + asm::sfence_vma(0, vaddr.as_usize()) + } else { + asm::sfence_vma_all(); + } + } +} + +/// Reads the thread pointer of the current CPU. +/// +/// It is used to implement TLS (Thread Local Storage). +#[inline] +pub fn read_thread_pointer() -> usize { + let tp; + unsafe { core::arch::asm!("mv {}, tp", out(reg) tp) }; + tp +} + +/// Writes the thread pointer of the current CPU. +/// +/// It is used to implement TLS (Thread Local Storage). +/// +/// # Safety +/// +/// This function is unsafe as it changes the CPU states. +#[inline] +pub unsafe fn write_thread_pointer(tp: usize) { + core::arch::asm!("mv tp, {}", in(reg) tp) +} + +include_asm_marcos!(); + +core::arch::global_asm!(include_str!("signal.S")); diff --git a/modules/axhal/src/arch/riscv/signal.S b/modules/axhal/src/arch/riscv/signal.S new file mode 100644 index 0000000..c9d6259 --- /dev/null +++ b/modules/axhal/src/arch/riscv/signal.S @@ -0,0 +1,9 @@ +# To create the sigreturn trampoline +.equ __NR_sigreturn, 139 +.section .text.signal_trampoline +.balign 4 +.global start_signal_trampoline +start_signal_trampoline: + li a7, __NR_sigreturn + li a0, 0 + ecall \ No newline at end of file diff --git a/modules/axhal/src/arch/x86_64/context.rs b/modules/axhal/src/arch/x86_64/context.rs new file mode 100644 index 0000000..18fa7c1 --- /dev/null +++ b/modules/axhal/src/arch/x86_64/context.rs @@ -0,0 +1,214 @@ +use taskctx::TaskContext; + +use super::GdtStruct; + +/// Saved registers when a trap (interrupt or exception) occurs. +#[allow(missing_docs)] +#[repr(C)] +#[derive(Debug, Default, Clone, Copy)] +pub struct TrapFrame { + pub rax: u64, + pub rcx: u64, + pub rdx: u64, + pub rbx: u64, + pub rbp: u64, + pub rsi: u64, + pub rdi: u64, + pub r8: u64, + pub r9: u64, + pub r10: u64, + pub r11: u64, + pub r12: u64, + pub r13: u64, + pub r14: u64, + pub r15: u64, + + // Pushed by `trap.S` + pub vector: u64, + pub error_code: u64, + + // Pushed by CPU + pub rip: u64, + pub cs: u64, + pub rflags: u64, + pub rsp: u64, + pub ss: u64, +} + +impl TrapFrame { + /// Whether the trap is from userspace. + pub const fn is_user(&self) -> bool { + self.cs & 0b11 == 3 + } + + /// To set the stack pointer + pub fn set_user_sp(&mut self, user_sp: usize) { + self.rsp = user_sp as _; + } + + /// 用于第一次进入应用程序时的初始化 + pub fn app_init_context(app_entry: usize, user_sp: usize) -> Self { + TrapFrame { + rip: app_entry as _, + cs: GdtStruct::UCODE64_SELECTOR.0 as _, + #[cfg(feature = "irq")] + rflags: x86_64::registers::rflags::RFlags::INTERRUPT_FLAG.bits() as _, + rsp: user_sp as _, + ss: GdtStruct::UDATA_SELECTOR.0 as _, + ..Default::default() + } + } + + /// set the return code + pub fn set_ret_code(&mut self, ret_value: usize) { + self.rax = ret_value as _; + } + + /// 设置TLS + pub fn set_tls(&mut self, _tls_value: usize) { + // panic!("set tls: {:#x}", tls_value); + // unsafe { + // write_thread_pointer(tls_value); + // } + todo!("set tls"); + } + + /// 获取 sp + pub fn get_sp(&self) -> usize { + self.rsp as _ + } + + /// 设置 arg0 + pub fn set_arg0(&mut self, arg: usize) { + self.rdi = arg as _; + } + + /// 设置 arg1 + pub fn set_arg1(&mut self, arg: usize) { + self.rsi = arg as _; + } + + /// 设置 arg2 + pub fn set_arg2(&mut self, arg: usize) { + self.rdx = arg as _; + } + + /// 获取 pc + pub fn get_pc(&self) -> usize { + self.rip as _ + } + + /// 设置 pc + pub fn set_pc(&mut self, pc: usize) { + self.rip = pc as _; + } + + /// pc 倒退到 syscall 指令的长度 + pub fn rewind_pc(&mut self) { + self.rip -= 2; + } + + /// 获取 ret + pub fn get_ret_code(&self) -> usize { + self.rax as _ + } + + /// 设置返回地址 + pub fn set_ra(&mut self, _ra: usize) { + todo!() + } + + /// 获取所有 syscall 参数 + pub fn get_syscall_args(&self) -> [usize; 6] { + [self.rdi, self.rsi, self.rdx, self.r10, self.r8, self.r9].map(|n| n as _) + } + + /// 获取 syscall id + pub fn get_syscall_num(&self) -> usize { + self.rax as _ + } +} + +#[no_mangle] +#[cfg(feature = "monolithic")] +/// To handle the first time into the user space +/// +/// 1. push the given trap frame into the kernel stack +/// 2. go into the user space +/// +/// args: +/// +/// 1. kernel_sp: the top of the kernel stack +/// +/// 2. frame_base: the address of the trap frame which will be pushed into the kernel stack +pub fn first_into_user(kernel_sp: usize) { + // Make sure that all csr registers are stored before enable the interrupt + use memory_addr::VirtAddr; + + use crate::arch::flush_tlb; + + use super::disable_irqs; + disable_irqs(); + flush_tlb(None); + + let trap_frame_size = core::mem::size_of::(); + let kernel_base = kernel_sp - trap_frame_size; + crate::set_tss_stack_top(VirtAddr::from(kernel_sp)); + unsafe { + core::arch::asm!( + r" + mov gs:[offset __PERCPU_KERNEL_RSP_OFFSET], {kernel_sp} + + mov rsp, {kernel_base} + + pop rax + pop rcx + pop rdx + pop rbx + pop rbp + pop rsi + pop rdi + pop r8 + pop r9 + pop r10 + pop r11 + pop r12 + pop r13 + pop r14 + pop r15 + add rsp, 16 + + swapgs + iretq + ", + kernel_sp = in(reg) kernel_sp, + kernel_base = in(reg) kernel_base, + ); + }; +} + +/// To switch the context between two tasks +pub fn task_context_switch(prev_ctx: &mut TaskContext, next_ctx: &TaskContext) { + #[cfg(feature = "fp_simd")] + { + prev_ctx.ext_state.save(); + next_ctx.ext_state.restore(); + } + #[cfg(any(feature = "tls", feature = "monolithic"))] + { + prev_ctx.fs_base = super::read_thread_pointer(); + unsafe { super::write_thread_pointer(next_ctx.fs_base) }; + } + #[cfg(feature = "monolithic")] + unsafe { + // change gs data + core::arch::asm!("mov gs:[offset __PERCPU_KERNEL_RSP_OFFSET], {kernel_sp}", + kernel_sp = in(reg) next_ctx.kstack_top.as_usize() + core::mem::size_of::()); + } + crate::set_tss_stack_top(next_ctx.kstack_top + core::mem::size_of::()); + + unsafe { + // TODO: switch FP states + taskctx::context_switch(&mut prev_ctx.rsp, &next_ctx.rsp) + } +} diff --git a/modules/axhal/src/arch/x86_64/gdt.rs b/modules/axhal/src/arch/x86_64/gdt.rs new file mode 100644 index 0000000..e0ab99a --- /dev/null +++ b/modules/axhal/src/arch/x86_64/gdt.rs @@ -0,0 +1,88 @@ +use core::fmt; + +use x86_64::instructions::tables::{lgdt, load_tss}; +use x86_64::registers::segmentation::{Segment, SegmentSelector, CS}; +use x86_64::structures::gdt::{Descriptor, DescriptorFlags}; +use x86_64::structures::{tss::TaskStateSegment, DescriptorTablePointer}; +use x86_64::{addr::VirtAddr, PrivilegeLevel}; + +/// A wrapper of the Global Descriptor Table (GDT) with maximum 16 entries. +#[repr(align(16))] +pub struct GdtStruct { + table: [u64; 16], +} + +impl GdtStruct { + /// Kernel code segment for 32-bit mode. + pub const KCODE32_SELECTOR: SegmentSelector = SegmentSelector::new(1, PrivilegeLevel::Ring0); + /// Kernel code segment for 64-bit mode. + pub const KCODE64_SELECTOR: SegmentSelector = SegmentSelector::new(2, PrivilegeLevel::Ring0); + /// Kernel data segment. + pub const KDATA_SELECTOR: SegmentSelector = SegmentSelector::new(3, PrivilegeLevel::Ring0); + /// User code segment for 32-bit mode. + pub const UCODE32_SELECTOR: SegmentSelector = SegmentSelector::new(4, PrivilegeLevel::Ring3); + /// User data segment. + pub const UDATA_SELECTOR: SegmentSelector = SegmentSelector::new(5, PrivilegeLevel::Ring3); + /// User code segment for 64-bit mode. + pub const UCODE64_SELECTOR: SegmentSelector = SegmentSelector::new(6, PrivilegeLevel::Ring3); + /// TSS segment. + pub const TSS_SELECTOR: SegmentSelector = SegmentSelector::new(7, PrivilegeLevel::Ring0); + + /// Constructs a new GDT struct that filled with the default segment + /// descriptors, including the given TSS segment. + pub fn new(tss: &'static TaskStateSegment) -> Self { + let mut table = [0; 16]; + // first 3 entries are the same as in multiboot.S + table[1] = DescriptorFlags::KERNEL_CODE32.bits(); // 0x00cf9b000000ffff + table[2] = DescriptorFlags::KERNEL_CODE64.bits(); // 0x00af9b000000ffff + table[3] = DescriptorFlags::KERNEL_DATA.bits(); // 0x00cf93000000ffff + table[4] = DescriptorFlags::USER_CODE32.bits(); // 0x00cffb000000ffff + table[5] = DescriptorFlags::USER_DATA.bits(); // 0x00cff3000000ffff + table[6] = DescriptorFlags::USER_CODE64.bits(); // 0x00affb000000ffff + if let Descriptor::SystemSegment(low, high) = Descriptor::tss_segment(tss) { + table[7] = low; + table[8] = high; + } + Self { table } + } + + /// Returns the GDT pointer (base and limit) that can be used in `lgdt` + /// instruction. + pub fn pointer(&self) -> DescriptorTablePointer { + DescriptorTablePointer { + base: VirtAddr::new(self.table.as_ptr() as u64), + limit: (core::mem::size_of_val(&self.table) - 1) as u16, + } + } + + /// Loads the GDT into the CPU (executes the `lgdt` instruction), and + /// updates the code segment register (`CS`). + /// + /// # Safety + /// + /// This function is unsafe because it manipulates the CPU's privileged + /// states. + pub unsafe fn load(&'static self) { + lgdt(&self.pointer()); + CS::set_reg(Self::KCODE64_SELECTOR); + } + + /// Loads the TSS into the CPU (executes the `ltr` instruction). + /// + /// # Safety + /// + /// This function is unsafe because it manipulates the CPU's privileged + /// states. + pub unsafe fn load_tss(&'static self) { + load_tss(Self::TSS_SELECTOR); + } +} + +impl fmt::Debug for GdtStruct { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("GdtStruct") + .field("pointer", &self.pointer()) + .field("table", &self.table) + .finish() + } +} diff --git a/modules/axhal/src/arch/x86_64/mod.rs b/modules/axhal/src/arch/x86_64/mod.rs new file mode 100644 index 0000000..d146f3a --- /dev/null +++ b/modules/axhal/src/arch/x86_64/mod.rs @@ -0,0 +1,114 @@ +mod context; +pub use context::task_context_switch; +mod gdt; + +#[cfg(feature = "monolithic")] +pub use context::first_into_user; + +use core::arch::asm; + +use memory_addr::{PhysAddr, VirtAddr}; +use x86::{controlregs, msr, tlb}; +use x86_64::instructions::interrupts; + +pub use self::context::TrapFrame; +pub use self::gdt::GdtStruct; + +pub use x86_64::structures::tss::TaskStateSegment; + +/// Allows the current CPU to respond to interrupts. +#[inline] +pub fn enable_irqs() { + #[cfg(target_os = "none")] + interrupts::enable(); +} + +/// Makes the current CPU to ignore interrupts. +#[inline] +pub fn disable_irqs() { + #[cfg(target_os = "none")] + interrupts::disable() +} + +/// Returns whether the current CPU is allowed to respond to interrupts. +#[inline] +pub fn irqs_enabled() -> bool { + interrupts::are_enabled() +} + +/// Relaxes the current CPU and waits for interrupts. +/// +/// It must be called with interrupts enabled, otherwise it will never return. +#[inline] +pub fn wait_for_irqs() { + if cfg!(target_os = "none") { + unsafe { asm!("hlt") } + } else { + core::hint::spin_loop() + } +} + +/// Halt the current CPU. +#[inline] +pub fn halt() { + disable_irqs(); + wait_for_irqs(); // should never return +} + +/// Reads the register that stores the current page table root. +/// +/// Returns the physical address of the page table root. +#[inline] +pub fn read_page_table_root() -> PhysAddr { + PhysAddr::from(unsafe { controlregs::cr3() } as usize).align_down_4k() +} + +/// Writes the register to update the current page table root. +/// +/// # Safety +/// +/// This function is unsafe as it changes the virtual memory address space. +pub unsafe fn write_page_table_root(root_paddr: PhysAddr) { + let old_root = read_page_table_root(); + trace!("set page table root: {:#x} => {:#x}", old_root, root_paddr); + if old_root != root_paddr { + controlregs::cr3_write(root_paddr.as_usize() as _) + } +} + +pub use self::write_page_table_root as write_page_table_root0; + +/// Flushes the TLB. +/// +/// If `vaddr` is [`None`], flushes the entire TLB. Otherwise, flushes the TLB +/// entry that maps the given virtual address. +#[inline] +pub fn flush_tlb(vaddr: Option) { + if let Some(vaddr) = vaddr { + unsafe { tlb::flush(vaddr.into()) } + } else { + unsafe { tlb::flush_all() } + } +} + +/// Reads the thread pointer of the current CPU. +/// +/// It is used to implement TLS (Thread Local Storage). +#[inline] +pub fn read_thread_pointer() -> usize { + unsafe { msr::rdmsr(msr::IA32_FS_BASE) as usize } +} + +/// Writes the thread pointer of the current CPU. +/// +/// It is used to implement TLS (Thread Local Storage). +/// +/// # Safety +/// +/// This function is unsafe as it changes the CPU states. +#[inline] +pub unsafe fn write_thread_pointer(fs_base: usize) { + unsafe { msr::wrmsr(msr::IA32_FS_BASE, fs_base as u64) } +} + +core::arch::global_asm!(include_str!("signal.S")); diff --git a/modules/axhal/src/arch/x86_64/signal.S b/modules/axhal/src/arch/x86_64/signal.S new file mode 100644 index 0000000..a64ff1e --- /dev/null +++ b/modules/axhal/src/arch/x86_64/signal.S @@ -0,0 +1,9 @@ +# To create the sigreturn trampoline + +.section .text.signal_trampoline +.code64 +.global start_signal_trampoline +start_signal_trampoline: + # syscall id rdi = 15 + mov rax, 0xf + syscall \ No newline at end of file diff --git a/modules/axhal/src/cpu.rs b/modules/axhal/src/cpu.rs new file mode 100644 index 0000000..379a951 --- /dev/null +++ b/modules/axhal/src/cpu.rs @@ -0,0 +1,44 @@ +//! CPU-related operations. + +#[percpu::def_percpu] +static CPU_ID: usize = 0; + +#[percpu::def_percpu] +static IS_BSP: bool = false; + +#[percpu::def_percpu] +static CURRENT_TASK_PTR: usize = 0; + +/// Returns the ID of the current CPU. +#[inline] +pub fn this_cpu_id() -> usize { + CPU_ID.read_current() +} + +/// Returns whether the current CPU is the primary CPU (aka the bootstrap +/// processor or BSP) +#[inline] +pub fn this_cpu_is_bsp() -> bool { + IS_BSP.read_current() +} + +#[allow(dead_code)] +/// Initializes the primary CPU for its pointer. +pub fn init_primary(cpu_id: usize) { + percpu::init(axconfig::SMP); + percpu::set_local_thread_pointer(cpu_id); + unsafe { + CPU_ID.write_current_raw(cpu_id); + IS_BSP.write_current_raw(true); + } +} + +#[allow(dead_code)] +/// Initializes the secondary CPU for its pointer. +pub fn init_secondary(cpu_id: usize) { + percpu::set_local_thread_pointer(cpu_id); + unsafe { + CPU_ID.write_current_raw(cpu_id); + IS_BSP.write_current_raw(false); + } +} diff --git a/modules/axhal/src/irq.rs b/modules/axhal/src/irq.rs new file mode 100644 index 0000000..46160d4 --- /dev/null +++ b/modules/axhal/src/irq.rs @@ -0,0 +1,35 @@ +//! Interrupt management. + +use handler_table::HandlerTable; + +use crate::platform::irq::MAX_IRQ_COUNT; + +pub use crate::platform::irq::{dispatch_irq, register_handler, set_enable}; + +/// The type if an IRQ handler. +pub type IrqHandler = handler_table::Handler; + +static IRQ_HANDLER_TABLE: HandlerTable = HandlerTable::new(); + +/// Platform-independent IRQ dispatching. +#[allow(dead_code)] +pub(crate) fn dispatch_irq_common(irq_num: usize) { + trace!("IRQ {}", irq_num); + if !IRQ_HANDLER_TABLE.handle(irq_num) { + warn!("Unhandled IRQ {}", irq_num); + } +} + +/// Platform-independent IRQ handler registration. +/// +/// It also enables the IRQ if the registration succeeds. It returns `false` if +/// the registration failed. +#[allow(dead_code)] +pub(crate) fn register_handler_common(irq_num: usize, handler: IrqHandler) -> bool { + if irq_num < MAX_IRQ_COUNT && IRQ_HANDLER_TABLE.register_handler(irq_num, handler) { + set_enable(irq_num, true); + return true; + } + warn!("register handler for IRQ {} failed", irq_num); + false +} diff --git a/modules/axhal/src/lib.rs b/modules/axhal/src/lib.rs new file mode 100644 index 0000000..f7b8dce --- /dev/null +++ b/modules/axhal/src/lib.rs @@ -0,0 +1,82 @@ +//! [ArceOS] hardware abstraction layer, provides unified APIs for +//! platform-specific operations. +//! +//! It does the bootstrapping and initialization process for the specified +//! platform, and provides useful operations on the hardware. +//! +//! Currently supported platforms (specify by cargo features): +//! +//! - `x86-pc`: Standard PC with x86_64 ISA. +//! - `riscv64-qemu-virt`: QEMU virt machine with RISC-V ISA. +//! - `aarch64-qemu-virt`: QEMU virt machine with AArch64 ISA. +//! - `aarch64-raspi`: Raspberry Pi with AArch64 ISA. +//! - `dummy`: If none of the above platform is selected, the dummy platform +//! will be used. In this platform, most of the operations are no-op or +//! `unimplemented!()`. This platform is mainly used for [cargo test]. +//! +//! # Cargo Features +//! +//! - `smp`: Enable SMP (symmetric multiprocessing) support. +//! - `fp_simd`: Enable floating-point and SIMD support. +//! - `paging`: Enable page table manipulation. +//! - `irq`: Enable interrupt handling support. +//! +//! [ArceOS]: https://github.com/rcore-os/arceos +//! [cargo test]: https://doc.rust-lang.org/cargo/guide/tests.html + +#![no_std] +#![feature(asm_const)] +#![feature(naked_functions)] +#![feature(const_option)] +#![feature(doc_auto_cfg)] +#![feature(stmt_expr_attributes)] +#[allow(unused_imports)] +#[macro_use] +extern crate log; + +#[cfg(feature = "monolithic")] +/// The kernel process ID, which is always 1. +pub const KERNEL_PROCESS_ID: u64 = 1; + +#[allow(missing_docs)] +pub mod platform; + +pub mod arch; +pub mod cpu; +pub mod mem; +pub mod time; + +#[cfg(feature = "tls")] +pub mod tls; + +#[cfg(feature = "irq")] +pub mod irq; + +#[cfg(feature = "paging")] +pub mod paging; + +/// Console input and output. +pub mod console { + pub use super::platform::console::*; + + /// Write a slice of bytes to the console. + pub fn write_bytes(bytes: &[u8]) { + for c in bytes { + putchar(*c); + } + } +} + +/// Miscellaneous operation, e.g. terminate the system. +pub mod misc { + pub use super::platform::misc::*; +} + +pub use self::platform::platform_init; +pub use self::platform::platform_name; + +#[cfg(target_arch = "x86_64")] +pub use self::platform::set_tss_stack_top; + +#[cfg(feature = "smp")] +pub use self::platform::platform_init_secondary; diff --git a/modules/axhal/src/mem.rs b/modules/axhal/src/mem.rs new file mode 100644 index 0000000..712b6b0 --- /dev/null +++ b/modules/axhal/src/mem.rs @@ -0,0 +1,177 @@ +//! Physical memory management. + +use core::fmt; + +#[doc(no_inline)] +pub use memory_addr::{PhysAddr, VirtAddr, PAGE_SIZE_4K}; + +bitflags::bitflags! { + /// The flags of a physical memory region. + pub struct MemRegionFlags: usize { + /// Readable. + const READ = 1 << 0; + /// Writable. + const WRITE = 1 << 1; + /// Executable. + const EXECUTE = 1 << 2; + /// Device memory. (e.g., MMIO regions) + const DEVICE = 1 << 4; + /// Uncachable memory. (e.g., framebuffer) + const UNCACHED = 1 << 5; + /// Reserved memory, do not use for allocation. + const RESERVED = 1 << 6; + /// Free memory for allocation. + const FREE = 1 << 7; + } +} + +impl fmt::Debug for MemRegionFlags { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.0, f) + } +} + +/// A physical memory region. +#[derive(Debug)] +pub struct MemRegion { + /// The start physical address of the region. + pub paddr: PhysAddr, + /// The size in bytes of the region. + pub size: usize, + /// The region flags, see [`MemRegionFlags`]. + pub flags: MemRegionFlags, + /// The region name, used for identification. + pub name: &'static str, +} + +/// Converts a virtual address to a physical address. +/// +/// It assumes that there is a linear mapping with the offset +/// [`PHYS_VIRT_OFFSET`], that maps all the physical memory to the virtual +/// space at the address plus the offset. So we have +/// `paddr = vaddr - PHYS_VIRT_OFFSET`. +/// +/// [`PHYS_VIRT_OFFSET`]: axconfig::PHYS_VIRT_OFFSET +#[inline] +pub const fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr { + PhysAddr::from(vaddr.as_usize() - axconfig::PHYS_VIRT_OFFSET) +} + +/// Converts a physical address to a virtual address. +/// +/// It assumes that there is a linear mapping with the offset +/// [`PHYS_VIRT_OFFSET`], that maps all the physical memory to the virtual +/// space at the address plus the offset. So we have +/// `vaddr = paddr + PHYS_VIRT_OFFSET`. +/// +/// [`PHYS_VIRT_OFFSET`]: axconfig::PHYS_VIRT_OFFSET +#[inline] +pub const fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { + VirtAddr::from(paddr.as_usize() + axconfig::PHYS_VIRT_OFFSET) +} + +/// Returns an iterator over all physical memory regions. +pub fn memory_regions() -> impl Iterator { + kernel_image_regions().chain(crate::platform::mem::platform_regions()) +} + +/// Returns the memory regions of the kernel image (code and data sections). +fn kernel_image_regions() -> impl Iterator { + [ + MemRegion { + paddr: virt_to_phys((_stext as usize).into()), + size: _etext as usize - _stext as usize, + flags: MemRegionFlags::RESERVED | MemRegionFlags::READ | MemRegionFlags::EXECUTE, + name: ".text", + }, + MemRegion { + paddr: virt_to_phys((_srodata as usize).into()), + size: _erodata as usize - _srodata as usize, + flags: MemRegionFlags::RESERVED | MemRegionFlags::READ, + name: ".rodata", + }, + MemRegion { + paddr: virt_to_phys((_sdata as usize).into()), + size: _edata as usize - _sdata as usize, + flags: MemRegionFlags::RESERVED | MemRegionFlags::READ | MemRegionFlags::WRITE, + name: ".data .tdata .tbss .percpu", + }, + MemRegion { + paddr: virt_to_phys((boot_stack as usize).into()), + size: boot_stack_top as usize - boot_stack as usize, + flags: MemRegionFlags::RESERVED | MemRegionFlags::READ | MemRegionFlags::WRITE, + name: "boot stack", + }, + MemRegion { + paddr: virt_to_phys((_sbss as usize).into()), + size: _ebss as usize - _sbss as usize, + flags: MemRegionFlags::RESERVED | MemRegionFlags::READ | MemRegionFlags::WRITE, + name: ".bss", + }, + ] + .into_iter() +} + +/// Returns the default MMIO memory regions (from [`axconfig::MMIO_REGIONS`]). +#[allow(dead_code)] +pub(crate) fn default_mmio_regions() -> impl Iterator { + axconfig::MMIO_REGIONS.iter().map(|reg| MemRegion { + paddr: reg.0.into(), + size: reg.1, + flags: MemRegionFlags::RESERVED + | MemRegionFlags::DEVICE + | MemRegionFlags::READ + | MemRegionFlags::WRITE, + name: "mmio", + }) +} + +/// Returns the default free memory regions (kernel image end to physical memory end). +#[allow(dead_code)] +pub(crate) fn default_free_regions() -> impl Iterator { + let start = virt_to_phys((_ekernel as usize).into()).align_up_4k(); + let end = PhysAddr::from(axconfig::PHYS_MEMORY_END).align_down_4k(); + core::iter::once(MemRegion { + paddr: start, + size: end.as_usize() - start.as_usize(), + flags: MemRegionFlags::FREE | MemRegionFlags::READ | MemRegionFlags::WRITE, + name: "free memory", + }) +} + +/// Return the extend free memory regions to prepare for the monolithic_userboot +/// +/// extend to [0xffff_ffc0_a000_0000, 0xffff_ffc0_f000_0000) +#[allow(dead_code)] +pub(crate) fn extend_free_regions() -> impl Iterator { + let start = virt_to_phys(VirtAddr::from(0xffff_ffc0_a000_0000)).align_up_4k(); + let end: PhysAddr = PhysAddr::from(0x1_a000_0000).align_down_4k(); + core::iter::once(MemRegion { + paddr: start, + size: end.as_usize() - start.as_usize(), + flags: MemRegionFlags::FREE | MemRegionFlags::READ | MemRegionFlags::WRITE, + name: "extend free memory", + }) +} +/// Fills the `.bss` section with zeros. +#[allow(dead_code)] +pub fn clear_bss() { + unsafe { + core::slice::from_raw_parts_mut(_sbss as usize as *mut u8, _ebss as usize - _sbss as usize) + .fill(0); + } +} + +extern "C" { + fn _stext(); + fn _etext(); + fn _srodata(); + fn _erodata(); + fn _sdata(); + fn _edata(); + fn _sbss(); + fn _ebss(); + fn _ekernel(); + fn boot_stack(); + fn boot_stack_top(); +} diff --git a/modules/axhal/src/paging.rs b/modules/axhal/src/paging.rs new file mode 100644 index 0000000..2d035e5 --- /dev/null +++ b/modules/axhal/src/paging.rs @@ -0,0 +1,66 @@ +//! Page table manipulation. + +use axalloc::global_allocator; +use page_table::PagingIf; + +use crate::mem::{phys_to_virt, virt_to_phys, MemRegionFlags, PhysAddr, VirtAddr, PAGE_SIZE_4K}; + +#[doc(no_inline)] +pub use page_table::{MappingFlags, PageSize, PagingError, PagingResult}; + +impl From for MappingFlags { + fn from(f: MemRegionFlags) -> Self { + let mut ret = Self::empty(); + if f.contains(MemRegionFlags::READ) { + ret |= Self::READ; + } + if f.contains(MemRegionFlags::WRITE) { + ret |= Self::WRITE; + } + if f.contains(MemRegionFlags::EXECUTE) { + ret |= Self::EXECUTE; + } + if f.contains(MemRegionFlags::DEVICE) { + ret |= Self::DEVICE; + } + if f.contains(MemRegionFlags::UNCACHED) { + ret |= Self::UNCACHED; + } + ret + } +} + +/// Implementation of [`PagingIf`], to provide physical memory manipulation to +/// the [page_table] crate. +pub struct PagingIfImpl; + +impl PagingIf for PagingIfImpl { + fn alloc_frame() -> Option { + global_allocator() + .alloc_pages(1, PAGE_SIZE_4K) + .map(|vaddr| virt_to_phys(vaddr.into())) + .ok() + } + + fn dealloc_frame(paddr: PhysAddr) { + global_allocator().dealloc_pages(phys_to_virt(paddr).as_usize(), 1) + } + + #[inline] + fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { + phys_to_virt(paddr) + } +} + +cfg_if::cfg_if! { + if #[cfg(target_arch = "x86_64")] { + /// The architecture-specific page table. + pub type PageTable = page_table::x86_64::X64PageTable; + } else if #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] { + /// The architecture-specific page table. + pub type PageTable = page_table::riscv::Sv39PageTable; + } else if #[cfg(target_arch = "aarch64")]{ + /// The architecture-specific page table. + pub type PageTable = page_table::aarch64::A64PageTable; + } +} diff --git a/modules/axhal/src/platform/aarch64_bsta1000b/misc.rs b/modules/axhal/src/platform/aarch64_bsta1000b/misc.rs new file mode 100644 index 0000000..8954132 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_bsta1000b/misc.rs @@ -0,0 +1,60 @@ +pub use crate::platform::aarch64_common::psci::system_off as terminate; + +use crate::mem::phys_to_virt; +use crate::time::{busy_wait, Duration}; +use core::ptr::{read_volatile, write_volatile}; + +/// Do QSPI reset +pub fn reset_qspi() { + // qspi exit 4-byte mode + // exit_4byte_qspi(); + + let ptr = phys_to_virt((axconfig::A1000BASE_SAFETYCRM + 0x8).into()).as_mut_ptr() as *mut u32; + unsafe { + let value = read_volatile(ptr); + trace!("SAFETY CRM RESET CTRL = {:#x}", value); + write_volatile(ptr, value & !(0b11 << 15)); + busy_wait(Duration::from_millis(100)); + + write_volatile(ptr, value | (0b11 << 15)); + busy_wait(Duration::from_millis(100)); + } +} + +/// Do CPU reset +pub fn reset_cpu() { + reset_qspi(); + + //Data Width = 32 + let ptr = phys_to_virt((axconfig::A1000BASE_SAFETYCRM + 0x8).into()).as_mut_ptr() as *mut u32; + unsafe { + write_volatile(ptr, read_volatile(ptr) & !0b1); + } + + loop {} +} + +/// reboot system +#[allow(dead_code)] +pub fn do_reset() { + axlog::ax_println!("resetting ...\n"); + + // wait 50 ms + busy_wait(Duration::from_millis(50)); + + // disable_interrupts(); + + reset_cpu(); + + // NOT REACHED + warn!("NOT REACHED Resetting"); +} + +/// bootmode define bit [27:26], from strap pin +#[allow(dead_code)] +pub fn get_bootmode() -> u32 { + unsafe { + let ptr = phys_to_virt((axconfig::A1000BASE_TOPCRM).into()).as_mut_ptr() as *mut u32; + (ptr.read_volatile() >> 26) & 0x7 + } +} diff --git a/modules/axhal/src/platform/aarch64_bsta1000b/mod.rs b/modules/axhal/src/platform/aarch64_bsta1000b/mod.rs new file mode 100644 index 0000000..91f82b7 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_bsta1000b/mod.rs @@ -0,0 +1 @@ +pub mod misc; diff --git a/modules/axhal/src/platform/aarch64_common/dw_apb_uart.rs b/modules/axhal/src/platform/aarch64_common/dw_apb_uart.rs new file mode 100644 index 0000000..9fc51f3 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_common/dw_apb_uart.rs @@ -0,0 +1,51 @@ +//! snps,dw-apb-uart serial driver + +use crate::mem::phys_to_virt; +use dw_apb_uart::DW8250; +use memory_addr::PhysAddr; +use spinlock::SpinNoIrq; + +const UART_BASE: PhysAddr = PhysAddr::from(axconfig::UART_PADDR); + +static UART: SpinNoIrq = SpinNoIrq::new(DW8250::new(phys_to_virt(UART_BASE).as_usize())); + +/// Writes a byte to the console. +pub fn putchar(c: u8) { + let mut uart = UART.lock(); + match c { + b'\r' | b'\n' => { + uart.putchar(b'\r'); + uart.putchar(b'\n'); + } + c => uart.putchar(c), + } +} + +/// Reads a byte from the console, or returns [`None`] if no input is available. +pub fn getchar() -> Option { + UART.lock().getchar() +} + +/// UART simply initialize +pub fn init_early() { + // SAFETY: idmap console mmio mem before paging + unsafe { + crate::platform::aarch64_common::mem::idmap_device(UART_BASE.as_usize()); + } + //rk3588 uboot init uart, kernel not init uart + //FEATURE: UART param init from dtb + #[cfg(not(platform_family = "aarch64-rk3588j"))] + UART.lock().init(); +} + +/// Set UART IRQ Enable +#[cfg(feature = "irq")] +pub fn init_irq() { + UART.lock().set_ier(true); + crate::irq::register_handler(crate::platform::irq::UART_IRQ_NUM, handle); +} + +/// UART IRQ Handler +pub fn handle() { + trace!("Uart IRQ Handler"); +} diff --git a/modules/axhal/src/platform/aarch64_common/generic_timer.rs b/modules/axhal/src/platform/aarch64_common/generic_timer.rs new file mode 100644 index 0000000..fa92e46 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_common/generic_timer.rs @@ -0,0 +1,60 @@ +#![allow(unused_imports)] + +use aarch64_cpu::registers::{CNTFRQ_EL0, CNTPCT_EL0, CNTP_CTL_EL0, CNTP_TVAL_EL0}; +use ratio::Ratio; +use tock_registers::interfaces::{Readable, Writeable}; + +static mut CNTPCT_TO_NANOS_RATIO: Ratio = Ratio::zero(); +static mut NANOS_TO_CNTPCT_RATIO: Ratio = Ratio::zero(); + +/// Returns the current clock time in hardware ticks. +#[inline] +pub fn current_ticks() -> u64 { + CNTPCT_EL0.get() +} + +/// Converts hardware ticks to nanoseconds. +#[inline] +pub fn ticks_to_nanos(ticks: u64) -> u64 { + unsafe { CNTPCT_TO_NANOS_RATIO.mul_trunc(ticks) } +} + +/// Converts nanoseconds to hardware ticks. +#[inline] +pub fn nanos_to_ticks(nanos: u64) -> u64 { + unsafe { NANOS_TO_CNTPCT_RATIO.mul_trunc(nanos) } +} + +/// Set a one-shot timer. +/// +/// A timer interrupt will be triggered at the given deadline (in nanoseconds). +#[cfg(feature = "irq")] +pub fn set_oneshot_timer(deadline_ns: u64) { + let cnptct = CNTPCT_EL0.get(); + let cnptct_deadline = nanos_to_ticks(deadline_ns); + if cnptct < cnptct_deadline { + let interval = cnptct_deadline - cnptct; + debug_assert!(interval <= u32::MAX as u64); + CNTP_TVAL_EL0.set(interval); + } else { + CNTP_TVAL_EL0.set(0); + } +} + +/// Early stage initialization: stores the timer frequency. +pub fn init_early() { + let freq = CNTFRQ_EL0.get(); + unsafe { + CNTPCT_TO_NANOS_RATIO = Ratio::new(crate::time::NANOS_PER_SEC as u32, freq as u32); + NANOS_TO_CNTPCT_RATIO = CNTPCT_TO_NANOS_RATIO.inverse(); + } +} + +pub(crate) fn init_percpu() { + #[cfg(feature = "irq")] + { + CNTP_CTL_EL0.write(CNTP_CTL_EL0::ENABLE::SET); + CNTP_TVAL_EL0.set(0); + crate::platform::irq::set_enable(crate::platform::irq::TIMER_IRQ_NUM, true); + } +} diff --git a/modules/axhal/src/platform/aarch64_common/gic.rs b/modules/axhal/src/platform/aarch64_common/gic.rs new file mode 100644 index 0000000..7c6a77d --- /dev/null +++ b/modules/axhal/src/platform/aarch64_common/gic.rs @@ -0,0 +1,82 @@ +use crate::{irq::IrqHandler, mem::phys_to_virt}; +use arm_gic::{translate_irq, GenericArmGic, IntId, InterruptType}; +use memory_addr::PhysAddr; +use spinlock::SpinNoIrq; + +/// The maximum number of IRQs. +pub const MAX_IRQ_COUNT: usize = IntId::GIC_MAX_IRQ; +/// The timer IRQ number. +pub const TIMER_IRQ_NUM: usize = translate_irq(14, InterruptType::PPI).unwrap(); + +/// The UART IRQ number. +pub const UART_IRQ_NUM: usize = translate_irq(axconfig::UART_IRQ, InterruptType::SPI).unwrap(); + +const GICD_BASE: PhysAddr = PhysAddr::from(axconfig::GICD_PADDR); +const GICC_BASE: PhysAddr = PhysAddr::from(axconfig::GICC_PADDR); + +cfg_if::cfg_if! { + if #[cfg(platform_family= "aarch64-rk3588j")] { + use arm_gic::GicV3; + static mut GIC: SpinNoIrq = + SpinNoIrq::new(GicV3::new(phys_to_virt(GICD_BASE).as_mut_ptr(), phys_to_virt(GICC_BASE).as_mut_ptr())); + } else { + use arm_gic::GicV2; + static mut GIC: SpinNoIrq = + SpinNoIrq::new(GicV2::new(phys_to_virt(GICD_BASE).as_mut_ptr(), phys_to_virt(GICC_BASE).as_mut_ptr())); + } +} + +/// Enables or disables the given IRQ. +pub fn set_enable(irq_num: usize, enabled: bool) { + trace!("GICD set enable: {} {}", irq_num, enabled); + + // SAFETY: + // access percpu interface through get_mut, no need to lock + // it will introduce side effects: need to add unsafe + // Acceptable compared to data competition + unsafe { + if enabled { + GIC.lock().enable_interrupt(irq_num.into()); + } else { + GIC.lock().disable_interrupt(irq_num.into()); + } + } +} + +/// Registers an IRQ handler for the given IRQ. +/// +/// It also enables the IRQ if the registration succeeds. It returns `false` if +/// the registration failed. +pub fn register_handler(irq_num: usize, handler: IrqHandler) -> bool { + trace!("register handler irq {}", irq_num); + crate::irq::register_handler_common(irq_num, handler) +} + +/// Dispatches the IRQ. +/// +/// This function is called by the common interrupt handler. It looks +/// up in the IRQ handler table and calls the corresponding handler. If +/// necessary, it also acknowledges the interrupt controller after handling. +pub fn dispatch_irq(_unused: usize) { + // actually no need to lock + let intid = unsafe { GIC.get_mut().get_and_acknowledge_interrupt() }; + if let Some(id) = intid { + crate::irq::dispatch_irq_common(id.into()); + unsafe { + GIC.get_mut().end_interrupt(id); + } + } +} + +/// Initializes GICD, GICC on the primary CPU. +pub(crate) fn init_primary() { + info!("Initialize GICv2..."); + unsafe { GIC.lock().init_primary() }; +} + +/// Initializes GICC on secondary CPUs. +#[cfg(feature = "smp")] +pub(crate) fn init_secondary() { + // per cpu handle, no need lock + unsafe { GIC.get_mut().per_cpu_init() }; +} diff --git a/modules/axhal/src/platform/aarch64_common/mem.rs b/modules/axhal/src/platform/aarch64_common/mem.rs new file mode 100644 index 0000000..4f733dd --- /dev/null +++ b/modules/axhal/src/platform/aarch64_common/mem.rs @@ -0,0 +1,203 @@ +use aarch64_cpu::{asm::barrier, registers::*}; +use tock_registers::interfaces::{ReadWriteable, Writeable}; + +use crate::mem::virt_to_phys; +use crate::mem::{MemRegion, MemRegionFlags, PhysAddr}; +use page_table_entry::aarch64::{MemAttr, A64PTE}; +use page_table_entry::{GenericPTE, MappingFlags}; + +use either::{Either, Left, Right}; + +/// Returns platform-specific memory regions. +pub(crate) fn platform_regions() -> impl Iterator { + // Feature, should registerd by user, should'n use hard coding + let iterator: Either<_, _> = if of::machin_name().is_some_and(|name| { + name.contains("raspi") || name.contains("phytiumpi") + }) { + Left( + core::iter::once(MemRegion { + paddr: 0x0.into(), + size: 0x1000, + flags: MemRegionFlags::RESERVED | MemRegionFlags::READ | MemRegionFlags::WRITE, + name: "spintable", + }) + .chain(core::iter::once(fdt_region())) + .chain(free_regions()) + .chain(crate::mem::default_mmio_regions()), + ) + } else { + Right( + core::iter::once(fdt_region()) + .chain(free_regions()) + .chain(crate::mem::default_mmio_regions()), + ) + }; + iterator.into_iter() +} + +fn split_region(region: MemRegion, region2: &MemRegion) -> impl Iterator { + let start1 = region.paddr.as_usize(); + let end1 = region.paddr.as_usize() + region.size; + + let start2 = region2.paddr.as_usize(); + let end2 = region2.paddr.as_usize() + region2.size; + + // mem region include region2 + let iterator: Either<_, _> = if start1 <= start2 && end1 >= end2 { + let head_region = MemRegion { + paddr: region.paddr, + size: start2 - start1, + flags: MemRegionFlags::FREE | MemRegionFlags::READ | MemRegionFlags::WRITE, + name: region.name, + }; + let tail_region = MemRegion { + paddr: PhysAddr::from(end2), + size: end1 - end2, + flags: MemRegionFlags::FREE | MemRegionFlags::READ | MemRegionFlags::WRITE, + name: region.name, + }; + let size_tup = (head_region.size, tail_region.size); + // Top(down) left size < 4K, need drop + match size_tup { + (x, y) if x < 0x1000 && y < 0x1000 => panic!("no vailid left region"), + (x, _) if x < 0x1000 => Right([tail_region]), + (_, y) if y < 0x1000 => Right([head_region]), + _ => Left([head_region, tail_region]), + } + } else { + Right([region]) + }; + iterator.into_iter() +} + +// Free mem regions equal memory minus kernel and fdt region +fn free_regions() -> impl Iterator { + let iter = if let Some(nodes) = of::memory_nodes() { + let all_mem = nodes.flat_map(|m| { + m.regions().filter_map(|r| { + if r.size.unwrap() > 0 { + Some(MemRegion { + paddr: PhysAddr::from(r.starting_address as usize).align_up_4k(), + size: r.size.unwrap(), + flags: MemRegionFlags::FREE | MemRegionFlags::READ | MemRegionFlags::WRITE, + name: "free memory", + }) + } else { + None + } + }) + }); + + let hack_k_region = MemRegion { + paddr: virt_to_phys((_stext as usize).into()).align_up_4k(), + size: _ekernel as usize - _stext as usize, + flags: MemRegionFlags::FREE, + name: "kernel memory", + }; + + let filter_kernel_mem = all_mem.flat_map(move |m| split_region(m, &hack_k_region)); + Left(filter_kernel_mem.flat_map(move |m| split_region(m, &fdt_region()))) + } else { + Right(crate::mem::default_free_regions()) + }; + + iter.into_iter() +} + +const FDT_FIX_SIZE: usize = 0x10_0000; //1M +fn fdt_region() -> MemRegion { + let fdt_ptr = of::get_fdt_ptr(); + MemRegion { + paddr: virt_to_phys((fdt_ptr.unwrap() as usize).into()).align_up_4k(), + size: FDT_FIX_SIZE, + flags: MemRegionFlags::RESERVED | MemRegionFlags::READ, + name: "fdt reserved", + } +} + +#[link_section = ".data.boot_page_table"] +static mut BOOT_PT_L0: [A64PTE; 512] = [A64PTE::empty(); 512]; + +#[link_section = ".data.boot_page_table"] +static mut BOOT_PT_L1: [A64PTE; 512] = [A64PTE::empty(); 512]; + +/// Initialize the MMU +/// +/// # Safety +/// +/// This function is unsafe because it directly manipulates the CPU registers. +pub unsafe fn init_mmu() { + MAIR_EL1.set(MemAttr::MAIR_VALUE); + + // Enable TTBR0 and TTBR1 walks, page size = 4K, vaddr size = 48 bits, paddr size = 40 bits. + let tcr_flags0 = TCR_EL1::EPD0::EnableTTBR0Walks + + TCR_EL1::TG0::KiB_4 + + TCR_EL1::SH0::Inner + + TCR_EL1::ORGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable + + TCR_EL1::IRGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable + + TCR_EL1::T0SZ.val(16); + let tcr_flags1 = TCR_EL1::EPD1::EnableTTBR1Walks + + TCR_EL1::TG1::KiB_4 + + TCR_EL1::SH1::Inner + + TCR_EL1::ORGN1::WriteBack_ReadAlloc_WriteAlloc_Cacheable + + TCR_EL1::IRGN1::WriteBack_ReadAlloc_WriteAlloc_Cacheable + + TCR_EL1::T1SZ.val(16); + TCR_EL1.write(TCR_EL1::IPS::Bits_48 + tcr_flags0 + tcr_flags1); + barrier::isb(barrier::SY); + + // Set both TTBR0 and TTBR1 + let root_paddr = PhysAddr::from(BOOT_PT_L0.as_ptr() as usize).as_usize() as _; + TTBR0_EL1.set(root_paddr); + TTBR1_EL1.set(root_paddr); + + // Flush the entire TLB + crate::arch::flush_tlb(None); + + // Enable the MMU and turn on I-cache and D-cache + SCTLR_EL1.modify(SCTLR_EL1::M::Enable + SCTLR_EL1::C::Cacheable + SCTLR_EL1::I::Cacheable); + barrier::isb(barrier::SY); +} + +const BOOT_MAP_SHIFT: usize = 30; // 1GB +const BOOT_MAP_SIZE: usize = 1 << BOOT_MAP_SHIFT; // 1GB + +/// Map the kernel image to the virtual address space. +/// +/// # Safety +/// +/// The function is unsafe because it directly modify the page table entries by dereferencing raw pointers. +pub unsafe fn idmap_kernel(kernel_phys_addr: usize) { + let aligned_address = (kernel_phys_addr) & !(BOOT_MAP_SIZE - 1); + let l1_index = kernel_phys_addr >> BOOT_MAP_SHIFT; + + // 0x0000_0000_0000 ~ 0x0080_0000_0000, table + BOOT_PT_L0[0] = A64PTE::new_table(PhysAddr::from(BOOT_PT_L1.as_ptr() as usize)); + // 1G block, kernel img + BOOT_PT_L1[l1_index] = A64PTE::new_page( + PhysAddr::from(aligned_address), + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE, + true, + ); +} + +/// Map a device with the given physical address to the page table. +/// +/// # Safety +/// +/// The function is unsafe because it directly modify the page table entries by dereferencing raw pointers. +pub unsafe fn idmap_device(phys_addr: usize) { + let aligned_address = (phys_addr) & !(BOOT_MAP_SIZE - 1); + let l1_index = phys_addr >> BOOT_MAP_SHIFT; + if BOOT_PT_L1[l1_index].is_unused() { + BOOT_PT_L1[l1_index] = A64PTE::new_page( + PhysAddr::from(aligned_address), + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::DEVICE, + true, + ); + } +} + +extern "C" { + fn _stext(); + fn _ekernel(); +} diff --git a/modules/axhal/src/platform/aarch64_common/mod.rs b/modules/axhal/src/platform/aarch64_common/mod.rs new file mode 100644 index 0000000..6b29fe0 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_common/mod.rs @@ -0,0 +1,59 @@ +#[cfg(not(any( + all(platform_family = "aarch64-raspi"), + all(platform_family = "aarch64-phytiumpi"), +)))] +pub mod psci; + +cfg_if::cfg_if! { + if #[cfg(feature = "irq")] { + mod gic; + pub mod irq { + pub use super::gic::*; + } + } +} + +mod generic_timer; +pub mod time { + pub use super::generic_timer::*; +} + +cfg_if::cfg_if! { + if #[cfg(any(platform_family = "aarch64-bsta1000b", platform_family= "aarch64-rk3588j"))] { + mod dw_apb_uart; + pub mod console { + pub use super::dw_apb_uart::*; + } + } else if #[cfg(any(platform_family = "aarch64-raspi", platform_family = "aarch64-qemu-virt",platform_family = "aarch64-phytiumpi"))] { + mod pl011; + pub mod console { + pub use super::pl011::*; + } + } +} + +pub mod mem; + +/// Initializes the platform devices for the primary CPU. +/// +/// For example, the interrupt controller and the timer. +pub fn platform_init() { + #[cfg(feature = "irq")] + crate::platform::irq::init_primary(); + crate::platform::time::init_percpu(); + #[cfg(feature = "irq")] + crate::platform::console::init_irq(); +} + +/// Initializes the platform devices for secondary CPUs. +#[cfg(feature = "smp")] +pub fn platform_init_secondary() { + #[cfg(feature = "irq")] + crate::platform::irq::init_secondary(); + crate::platform::time::init_percpu(); +} + +/// Returns the name of the platform. +pub fn platform_name() -> &'static str { + of::machin_name().unwrap_or(axconfig::PLATFORM) +} diff --git a/modules/axhal/src/platform/aarch64_common/pl011.rs b/modules/axhal/src/platform/aarch64_common/pl011.rs new file mode 100644 index 0000000..bee465e --- /dev/null +++ b/modules/axhal/src/platform/aarch64_common/pl011.rs @@ -0,0 +1,54 @@ +//! PL011 UART. + +use arm_pl011::pl011::Pl011Uart; +use memory_addr::PhysAddr; +use spinlock::SpinNoIrq; + +use crate::mem::phys_to_virt; + +const UART_BASE: PhysAddr = PhysAddr::from(axconfig::UART_PADDR); + +static UART: SpinNoIrq = + SpinNoIrq::new(Pl011Uart::new(phys_to_virt(UART_BASE).as_mut_ptr())); + +/// Writes a byte to the console. +pub fn putchar(c: u8) { + let mut uart = UART.lock(); + match c { + b'\n' => { + uart.putchar(b'\r'); + uart.putchar(b'\n'); + } + c => uart.putchar(c), + } +} + +/// Reads a byte from the console, or returns [`None`] if no input is available. +pub fn getchar() -> Option { + UART.lock().getchar() +} + +/// Initialize the UART +pub fn init_early() { + unsafe { + crate::platform::aarch64_common::mem::idmap_device(UART_BASE.as_usize()); + } + UART.lock().init(); +} + +/// Set UART IRQ Enable +#[cfg(feature = "irq")] +pub fn init_irq() { + crate::irq::set_enable(crate::platform::irq::UART_IRQ_NUM, true); +} + +/// UART IRQ Handler +pub fn handle() { + let is_receive_interrupt = UART.lock().is_receive_interrupt(); + UART.lock().ack_interrupts(); + if is_receive_interrupt { + while let Some(c) = getchar() { + putchar(c); + } + } +} diff --git a/modules/axhal/src/platform/aarch64_common/psci.rs b/modules/axhal/src/platform/aarch64_common/psci.rs new file mode 100644 index 0000000..feca7ba --- /dev/null +++ b/modules/axhal/src/platform/aarch64_common/psci.rs @@ -0,0 +1,129 @@ +//! ARM Power State Coordination Interface. + +#![allow(dead_code)] + +pub const PSCI_0_2_FN_BASE: u32 = 0x84000000; +pub const PSCI_0_2_64BIT: u32 = 0x40000000; +pub const PSCI_0_2_FN_CPU_SUSPEND: u32 = PSCI_0_2_FN_BASE + 1; +pub const PSCI_0_2_FN_CPU_OFF: u32 = PSCI_0_2_FN_BASE + 2; +pub const PSCI_0_2_FN_CPU_ON: u32 = PSCI_0_2_FN_BASE + 3; +pub const PSCI_0_2_FN_MIGRATE: u32 = PSCI_0_2_FN_BASE + 5; +pub const PSCI_0_2_FN_SYSTEM_OFF: u32 = PSCI_0_2_FN_BASE + 8; +pub const PSCI_0_2_FN_SYSTEM_RESET: u32 = PSCI_0_2_FN_BASE + 9; +pub const PSCI_0_2_FN64_CPU_SUSPEND: u32 = PSCI_0_2_FN_BASE + PSCI_0_2_64BIT + 1; +pub const PSCI_0_2_FN64_CPU_ON: u32 = PSCI_0_2_FN_BASE + PSCI_0_2_64BIT + 3; +pub const PSCI_0_2_FN64_MIGRATE: u32 = PSCI_0_2_FN_BASE + PSCI_0_2_64BIT + 5; + +/// PSCI return values, inclusive of all PSCI versions. +#[derive(PartialEq, Debug)] +#[repr(i32)] +pub enum PsciError { + NotSupported = -1, + InvalidParams = -2, + Denied = -3, + AlreadyOn = -4, + OnPending = -5, + InternalFailure = -6, + NotPresent = -7, + Disabled = -8, + InvalidAddress = -9, +} + +impl From for PsciError { + fn from(code: i32) -> PsciError { + use PsciError::*; + match code { + -1 => NotSupported, + -2 => InvalidParams, + -3 => Denied, + -4 => AlreadyOn, + -5 => OnPending, + -6 => InternalFailure, + -7 => NotPresent, + -8 => Disabled, + -9 => InvalidAddress, + _ => panic!("Unknown PSCI error code: {}", code), + } + } +} + +/// arm,psci method: smc +/// when SMCCC_CONDUIT_SMC = 1 +fn arm_smccc_smc(func: u32, arg0: usize, arg1: usize, arg2: usize) -> usize { + let mut ret; + unsafe { + core::arch::asm!( + "smc #0", + inlateout("x0") func as usize => ret, + in("x1") arg0, + in("x2") arg1, + in("x3") arg2, + ) + } + ret +} + +/// psci "hvc" method call +fn psci_hvc_call(func: u32, arg0: usize, arg1: usize, arg2: usize) -> usize { + let ret; + unsafe { + core::arch::asm!( + "hvc #0", + inlateout("x0") func as usize => ret, + in("x1") arg0, + in("x2") arg1, + in("x3") arg2, + ) + } + ret +} + +fn psci_call(func: u32, arg0: usize, arg1: usize, arg2: usize) -> Result<(), PsciError> { + let ret = match axconfig::PSCI_METHOD { + "smc" => arm_smccc_smc(func, arg0, arg1, arg2), + "hvc" => psci_hvc_call(func, arg0, arg1, arg2), + _ => panic!("Unknown PSCI method: {}", axconfig::PSCI_METHOD), + }; + if ret == 0 { + Ok(()) + } else { + Err(PsciError::from(ret as i32)) + } +} + +/// Shutdown the whole system, including all CPUs. +pub fn system_off() -> ! { + info!("Shutting down..."); + psci_call(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0).ok(); + warn!("It should shutdown!"); + loop { + crate::arch::halt(); + } +} + +/// Power up a core. This call is used to power up cores that either: +/// +/// * Have not yet been booted into the calling supervisory software. +/// * Have been previously powered down with a `cpu_off` call. +/// +/// `target_cpu` contains a copy of the affinity fields of the MPIDR register. +/// `entry_point` is the physical address of the secondary CPU's entry point. +/// `arg` will be passed to the `X0` register of the secondary CPU. +pub fn cpu_on(target_cpu: usize, entry_point: usize, arg: usize) { + info!("Starting CPU {:x} ON ...", target_cpu); + let res = psci_call(PSCI_0_2_FN64_CPU_ON, target_cpu, entry_point, arg); + if let Err(e) = res { + error!("failed to boot CPU {:x} ({:?})", target_cpu, e); + } +} + +/// Power down the calling core. This call is intended for use in hotplug. A +/// core that is powered down by `cpu_off` can only be powered up again in +/// response to a `cpu_on`. +pub fn cpu_off() { + const PSCI_POWER_STATE_TYPE_STANDBY: u32 = 0; + const PSCI_POWER_STATE_TYPE_POWER_DOWN: u32 = 1; + const PSCI_0_2_POWER_STATE_TYPE_SHIFT: u32 = 16; + let state: u32 = PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT; + psci_call(PSCI_0_2_FN_CPU_OFF, state as usize, 0, 0).ok(); +} diff --git a/modules/axhal/src/platform/aarch64_phytiumpi/mod.rs b/modules/axhal/src/platform/aarch64_phytiumpi/mod.rs new file mode 100644 index 0000000..c3f3302 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_phytiumpi/mod.rs @@ -0,0 +1,8 @@ +pub mod misc { + pub fn terminate() -> ! { + info!("Shutting down..."); + loop { + crate::arch::halt(); + } + } +} diff --git a/modules/axhal/src/platform/aarch64_qemu_virt/mod.rs b/modules/axhal/src/platform/aarch64_qemu_virt/mod.rs new file mode 100644 index 0000000..13e47d3 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_qemu_virt/mod.rs @@ -0,0 +1,3 @@ +pub mod misc { + pub use crate::platform::aarch64_common::psci::system_off as terminate; +} diff --git a/modules/axhal/src/platform/aarch64_raspi/mod.rs b/modules/axhal/src/platform/aarch64_raspi/mod.rs new file mode 100644 index 0000000..c3f3302 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_raspi/mod.rs @@ -0,0 +1,8 @@ +pub mod misc { + pub fn terminate() -> ! { + info!("Shutting down..."); + loop { + crate::arch::halt(); + } + } +} diff --git a/modules/axhal/src/platform/aarch64_rk3588j/mod.rs b/modules/axhal/src/platform/aarch64_rk3588j/mod.rs new file mode 100644 index 0000000..13e47d3 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_rk3588j/mod.rs @@ -0,0 +1,3 @@ +pub mod misc { + pub use crate::platform::aarch64_common::psci::system_off as terminate; +} diff --git a/modules/axhal/src/platform/dummy/mod.rs b/modules/axhal/src/platform/dummy/mod.rs new file mode 100644 index 0000000..9dd4866 --- /dev/null +++ b/modules/axhal/src/platform/dummy/mod.rs @@ -0,0 +1,98 @@ +#![allow(unused_variables)] +#![allow(dead_code)] + +pub mod console { + /// Writes a byte to the console. + pub fn putchar(c: u8) { + unimplemented!() + } + + /// Reads a byte from the console, or returns [`None`] if no input is available. + pub fn getchar() -> Option { + unimplemented!() + } + + /// Initializes the console. + pub fn init() {} +} + +pub mod misc { + /// Shutdown the whole system, including all CPUs. + pub fn terminate() -> ! { + unimplemented!() + } +} + +#[cfg(feature = "smp")] +pub mod mp { + /// Starts the given secondary CPU with its boot stack. + pub fn start_secondary_cpu(cpu_id: usize, stack_top: crate::mem::PhysAddr) {} +} + +pub mod mem { + /// Returns platform-specific memory regions. + pub(crate) fn platform_regions() -> impl Iterator { + core::iter::empty() + } +} + +pub mod time { + /// Returns the current clock time in hardware ticks. + pub fn current_ticks() -> u64 { + 0 + } + + /// Converts hardware ticks to nanoseconds. + pub fn ticks_to_nanos(ticks: u64) -> u64 { + ticks + } + + /// Converts nanoseconds to hardware ticks. + pub fn nanos_to_ticks(nanos: u64) -> u64 { + nanos + } + + /// Set a one-shot timer. + /// + /// A timer interrupt will be triggered at the given deadline (in nanoseconds). + pub fn set_oneshot_timer(deadline_ns: u64) {} +} + +#[cfg(feature = "irq")] +pub mod irq { + /// The maximum number of IRQs. + pub const MAX_IRQ_COUNT: usize = 256; + + /// The timer IRQ number. + pub const TIMER_IRQ_NUM: usize = 0; + + /// Enables or disables the given IRQ. + pub fn set_enable(irq_num: usize, enabled: bool) {} + + /// Registers an IRQ handler for the given IRQ. + pub fn register_handler(irq_num: usize, handler: crate::irq::IrqHandler) -> bool { + false + } + + /// Dispatches the IRQ. + /// + /// This function is called by the common interrupt handler. It looks + /// up in the IRQ handler table and calls the corresponding handler. If + /// necessary, it also acknowledges the interrupt controller after handling. + pub fn dispatch_irq(irq_num: usize) {} +} + +/// Initializes the platform devices for the primary CPU. +pub fn platform_init() {} + +/// Initializes the platform devices for secondary CPUs. +#[cfg(feature = "smp")] +pub fn platform_init_secondary() {} + +/// To be called by the kernel to set the top of the TSS stack. +pub fn set_tss_stack_top(_kernel_stack_top: memory_addr::VirtAddr) {} + +/// Returns the name of the platform. +pub fn platform_name() -> &'static str { + "dummy" +} diff --git a/modules/axhal/src/platform/mod.rs b/modules/axhal/src/platform/mod.rs new file mode 100644 index 0000000..6eb52b8 --- /dev/null +++ b/modules/axhal/src/platform/mod.rs @@ -0,0 +1,39 @@ +//! Platform-specific operations. + +cfg_if::cfg_if! { + if #[cfg(all(target_arch = "x86_64", platform_family = "x86-pc"))] { + mod x86_pc; + pub use self::x86_pc::*; + } else if #[cfg(all(target_arch = "riscv64", platform_family = "riscv64-qemu-virt"))] { + mod riscv64_qemu_virt; + pub use self::riscv64_qemu_virt::*; + } else if #[cfg(all(target_arch = "aarch64", platform_family = "aarch64-qemu-virt"))] { + mod aarch64_qemu_virt; + pub use self::aarch64_qemu_virt::*; + mod aarch64_common; + pub use self::aarch64_common::*; + } else if #[cfg(all(target_arch = "aarch64", platform_family = "aarch64-raspi"))] { + mod aarch64_raspi; + pub use self::aarch64_raspi::*; + mod aarch64_common; + pub use self::aarch64_common::*; + } else if #[cfg(all(target_arch = "aarch64", platform_family = "aarch64-bsta1000b"))] { + mod aarch64_bsta1000b; + pub use self::aarch64_bsta1000b::*; + mod aarch64_common; + pub use self::aarch64_common::*; + } else if #[cfg(all(target_arch = "aarch64", platform_family = "aarch64-phytiumpi"))] { + mod aarch64_phytiumpi; + pub use self::aarch64_phytiumpi::*; + mod aarch64_common; + pub use self::aarch64_common::*; + } else if #[cfg(all(target_arch = "aarch64", platform_family = "aarch64-rk3588j"))] { + mod aarch64_rk3588j; + pub use self::aarch64_rk3588j::*; + mod aarch64_common; + pub use self::aarch64_common::*; + } else { + mod dummy; + pub use self::dummy::*; + } +} diff --git a/modules/axhal/src/platform/riscv64_qemu_virt/console.rs b/modules/axhal/src/platform/riscv64_qemu_virt/console.rs new file mode 100644 index 0000000..a7ec3e6 --- /dev/null +++ b/modules/axhal/src/platform/riscv64_qemu_virt/console.rs @@ -0,0 +1,14 @@ +/// Writes a byte to the console. +pub fn putchar(c: u8) { + #[allow(deprecated)] + sbi_rt::legacy::console_putchar(c as usize); +} + +/// Reads a byte from the console, or returns [`None`] if no input is available. +pub fn getchar() -> Option { + #[allow(deprecated)] + match sbi_rt::legacy::console_getchar() as isize { + -1 => None, + c => Some(c as u8), + } +} diff --git a/modules/axhal/src/platform/riscv64_qemu_virt/irq.rs b/modules/axhal/src/platform/riscv64_qemu_virt/irq.rs new file mode 100644 index 0000000..f8effde --- /dev/null +++ b/modules/axhal/src/platform/riscv64_qemu_virt/irq.rs @@ -0,0 +1,85 @@ +//! TODO: PLIC + +use crate::irq::IrqHandler; +use lazy_init::LazyInit; +use riscv::register::sie; + +/// `Interrupt` bit in `scause` +pub(super) const INTC_IRQ_BASE: usize = 1 << (usize::BITS - 1); + +/// Supervisor software interrupt in `scause` +#[allow(unused)] +pub(super) const S_SOFT: usize = INTC_IRQ_BASE + 1; + +/// Supervisor timer interrupt in `scause` +pub(super) const S_TIMER: usize = INTC_IRQ_BASE + 5; + +/// Supervisor external interrupt in `scause` +pub(super) const S_EXT: usize = INTC_IRQ_BASE + 9; + +static TIMER_HANDLER: LazyInit = LazyInit::new(); + +/// The maximum number of IRQs. +pub const MAX_IRQ_COUNT: usize = 1024; + +/// The timer IRQ number (supervisor timer interrupt in `scause`). +pub const TIMER_IRQ_NUM: usize = S_TIMER; + +macro_rules! with_cause { + ($cause: expr, @TIMER => $timer_op: expr, @EXT => $ext_op: expr $(,)?) => { + match $cause { + S_TIMER => $timer_op, + S_EXT => $ext_op, + _ => panic!("invalid trap cause: {:#x}", $cause), + } + }; +} + +/// Enables or disables the given IRQ. +pub fn set_enable(scause: usize, _enabled: bool) { + if scause == S_EXT { + // TODO: set enable in PLIC + } +} + +/// Registers an IRQ handler for the given IRQ. +/// +/// It also enables the IRQ if the registration succeeds. It returns `false` if +/// the registration failed. +pub fn register_handler(scause: usize, handler: IrqHandler) -> bool { + with_cause!( + scause, + @TIMER => if !TIMER_HANDLER.is_init() { + TIMER_HANDLER.init_by(handler); + true + } else { + false + }, + @EXT => crate::irq::register_handler_common(scause & !INTC_IRQ_BASE, handler), + ) +} + +/// Dispatches the IRQ. +/// +/// This function is called by the common interrupt handler. It looks +/// up in the IRQ handler table and calls the corresponding handler. If +/// necessary, it also acknowledges the interrupt controller after handling. +pub fn dispatch_irq(scause: usize) { + with_cause!( + scause, + @TIMER => { + trace!("IRQ: timer"); + TIMER_HANDLER(); + }, + @EXT => crate::irq::dispatch_irq_common(0), // TODO: get IRQ number from PLIC + ); +} + +pub(super) fn init_percpu() { + // enable soft interrupts, timer interrupts, and external interrupts + unsafe { + sie::set_ssoft(); + sie::set_stimer(); + sie::set_sext(); + } +} diff --git a/modules/axhal/src/platform/riscv64_qemu_virt/mem.rs b/modules/axhal/src/platform/riscv64_qemu_virt/mem.rs new file mode 100644 index 0000000..da2c637 --- /dev/null +++ b/modules/axhal/src/platform/riscv64_qemu_virt/mem.rs @@ -0,0 +1,12 @@ +use crate::mem::MemRegion; + +/// Returns platform-specific memory regions. +pub(crate) fn platform_regions() -> impl Iterator { + #[cfg(not(feature = "monolithic"))] + return crate::mem::default_free_regions().chain(crate::mem::default_mmio_regions()); + + #[cfg(feature = "monolithic")] + return crate::mem::default_free_regions() + .chain(crate::mem::default_mmio_regions()) + .chain(crate::mem::extend_free_regions()); +} diff --git a/modules/axhal/src/platform/riscv64_qemu_virt/misc.rs b/modules/axhal/src/platform/riscv64_qemu_virt/misc.rs new file mode 100644 index 0000000..6b6e02f --- /dev/null +++ b/modules/axhal/src/platform/riscv64_qemu_virt/misc.rs @@ -0,0 +1,9 @@ +/// Shutdown the whole system, including all CPUs. +pub fn terminate() -> ! { + info!("Shutting down..."); + sbi_rt::system_reset(sbi_rt::Shutdown, sbi_rt::NoReason); + warn!("It should shutdown!"); + loop { + crate::arch::halt(); + } +} diff --git a/modules/axhal/src/platform/riscv64_qemu_virt/mod.rs b/modules/axhal/src/platform/riscv64_qemu_virt/mod.rs new file mode 100644 index 0000000..eab7206 --- /dev/null +++ b/modules/axhal/src/platform/riscv64_qemu_virt/mod.rs @@ -0,0 +1,29 @@ +pub mod console; +pub mod mem; +pub mod misc; +pub mod time; + +#[cfg(feature = "irq")] +pub mod irq; + +/// Initializes the platform devices for the primary CPU. +/// +/// For example, the interrupt controller and the timer. +pub fn platform_init() { + #[cfg(feature = "irq")] + self::irq::init_percpu(); + self::time::init_percpu(); +} + +/// Initializes the platform devices for secondary CPUs. +#[cfg(feature = "smp")] +pub fn platform_init_secondary() { + #[cfg(feature = "irq")] + self::irq::init_percpu(); + self::time::init_percpu(); +} + +/// Returns the name of the platform. +pub fn platform_name() -> &'static str { + "riscv64_qemu_virt" +} diff --git a/modules/axhal/src/platform/riscv64_qemu_virt/time.rs b/modules/axhal/src/platform/riscv64_qemu_virt/time.rs new file mode 100644 index 0000000..053e616 --- /dev/null +++ b/modules/axhal/src/platform/riscv64_qemu_virt/time.rs @@ -0,0 +1,77 @@ +use lazy_init::LazyInit; +use riscv::register::time; + +static CPU_FREQ: LazyInit = LazyInit::new(); +static LOOPS_PRE_TICK: LazyInit = LazyInit::new(); +const NANOS_PER_TICK: u64 = crate::time::NANOS_PER_SEC / axconfig::TIMER_FREQUENCY as u64; + +// Initializes the frequency of cpu. +#[inline] +fn init_cpu_freq(freq: u64) { + if !CPU_FREQ.is_init() { + CPU_FREQ.init_by(freq); + } + + if !LOOPS_PRE_TICK.is_init() { + LOOPS_PRE_TICK.init_by(freq / axconfig::TIMER_FREQUENCY as u64); + } +} + +/// Returns the frequency of cpu. +#[inline] +#[allow(unused)] +pub fn cpu_freq() -> u64 { + *CPU_FREQ +} + +/// Returns loops per tick. +#[inline] +pub fn loops_pre_tick() -> u64 { + *LOOPS_PRE_TICK +} + +/// Returns the current clock time in hardware ticks. +#[inline] +pub fn current_ticks() -> u64 { + time::read() as u64 / loops_pre_tick() +} + +/// Converts hardware ticks to nanoseconds. +#[inline] +pub const fn ticks_to_nanos(ticks: u64) -> u64 { + ticks * NANOS_PER_TICK +} + +/// Converts nanoseconds to hardware ticks. +#[inline] +pub const fn nanos_to_ticks(nanos: u64) -> u64 { + nanos / NANOS_PER_TICK +} + +/// Set a one-shot timer. +/// +/// A timer interrupt will be triggered at the given deadline (in nanoseconds). +#[cfg(feature = "irq")] +pub fn set_oneshot_timer(deadline_ns: u64) { + sbi_rt::set_timer(nanos_to_ticks(deadline_ns) * loops_pre_tick()); +} + +pub(super) fn init_percpu() { + #[cfg(feature = "irq")] + sbi_rt::set_timer(0); +} + +pub fn init_board_info(dtb: usize) { + unsafe { + of::init_fdt_ptr(dtb as *const u8); + } + let of_cpus = of::cpus(); + let freq = { + if let Some(cpu) = of_cpus.expect("Failed to read cpu info").nth(0) { + cpu.timebase_frequency() + } else { + axconfig::TIMER_FREQUENCY + } + }; + init_cpu_freq(freq as u64); +} diff --git a/modules/axhal/src/platform/x86_pc/apic.rs b/modules/axhal/src/platform/x86_pc/apic.rs new file mode 100644 index 0000000..28642b5 --- /dev/null +++ b/modules/axhal/src/platform/x86_pc/apic.rs @@ -0,0 +1,124 @@ +#![allow(dead_code)] + +use lazy_init::LazyInit; +use memory_addr::PhysAddr; +use spinlock::SpinNoIrq; +use x2apic::ioapic::IoApic; +use x2apic::lapic::{xapic_base, LocalApic, LocalApicBuilder}; +use x86_64::instructions::port::Port; + +use self::vectors::*; +use crate::mem::phys_to_virt; + +pub(super) mod vectors { + pub const APIC_TIMER_VECTOR: u8 = 0xf0; + pub const APIC_SPURIOUS_VECTOR: u8 = 0xf1; + pub const APIC_ERROR_VECTOR: u8 = 0xf2; +} + +/// The maximum number of IRQs. +pub const MAX_IRQ_COUNT: usize = 256; + +/// The timer IRQ number. +pub const TIMER_IRQ_NUM: usize = APIC_TIMER_VECTOR as usize; + +const IO_APIC_BASE: PhysAddr = PhysAddr::from(0xFEC0_0000); + +static mut LOCAL_APIC: Option = None; +static mut IS_X2APIC: bool = false; +static IO_APIC: LazyInit> = LazyInit::new(); + +/// Enables or disables the given IRQ. +#[cfg(feature = "irq")] +pub fn set_enable(vector: usize, enabled: bool) { + // should not affect LAPIC interrupts + if vector < APIC_TIMER_VECTOR as _ { + unsafe { + if enabled { + IO_APIC.lock().enable_irq(vector as u8); + } else { + IO_APIC.lock().disable_irq(vector as u8); + } + } + } +} + +/// Registers an IRQ handler for the given IRQ. +/// +/// It also enables the IRQ if the registration succeeds. It returns `false` if +/// the registration failed. +#[cfg(feature = "irq")] +pub fn register_handler(vector: usize, handler: crate::irq::IrqHandler) -> bool { + crate::irq::register_handler_common(vector, handler) +} + +/// Dispatches the IRQ. +/// +/// This function is called by the common interrupt handler. It looks +/// up in the IRQ handler table and calls the corresponding handler. If +/// necessary, it also acknowledges the interrupt controller after handling. +#[cfg(feature = "irq")] +pub fn dispatch_irq(vector: usize) { + crate::irq::dispatch_irq_common(vector); + unsafe { local_apic().end_of_interrupt() }; +} + +pub fn local_apic<'a>() -> &'a mut LocalApic { + // It's safe as LAPIC is per-cpu. + unsafe { LOCAL_APIC.as_mut().unwrap() } +} + +pub fn raw_apic_id(id_u8: u8) -> u32 { + if unsafe { IS_X2APIC } { + id_u8 as u32 + } else { + (id_u8 as u32) << 24 + } +} + +fn cpu_has_x2apic() -> bool { + match raw_cpuid::CpuId::new().get_feature_info() { + Some(finfo) => finfo.has_x2apic(), + None => false, + } +} + +pub(super) fn init_primary() { + info!("Initialize Local APIC..."); + + unsafe { + // Disable 8259A interrupt controllers + Port::::new(0x21).write(0xff); + Port::::new(0xA1).write(0xff); + } + + let mut builder = LocalApicBuilder::new(); + builder + .timer_vector(APIC_TIMER_VECTOR as _) + .error_vector(APIC_ERROR_VECTOR as _) + .spurious_vector(APIC_SPURIOUS_VECTOR as _); + + if cpu_has_x2apic() { + info!("Using x2APIC."); + unsafe { IS_X2APIC = true }; + } else { + info!("Using xAPIC."); + let base_vaddr = phys_to_virt(PhysAddr::from(unsafe { xapic_base() } as usize)); + builder.set_xapic_base(base_vaddr.as_usize() as u64); + } + + let mut lapic = builder.build().unwrap(); + unsafe { + lapic.enable(); + LOCAL_APIC = Some(lapic); + } + + info!("Initialize IO APIC..."); + let io_apic = unsafe { IoApic::new(phys_to_virt(IO_APIC_BASE).as_usize() as u64) }; + IO_APIC.init_by(SpinNoIrq::new(io_apic)); +} + +#[cfg(feature = "smp")] +pub(super) fn init_secondary() { + unsafe { local_apic().enable() }; +} diff --git a/modules/axhal/src/platform/x86_pc/dtables.rs b/modules/axhal/src/platform/x86_pc/dtables.rs new file mode 100644 index 0000000..409f92d --- /dev/null +++ b/modules/axhal/src/platform/x86_pc/dtables.rs @@ -0,0 +1,40 @@ +//! Description tables (per-CPU GDT, per-CPU ISS, IDT) + +use crate::arch::{GdtStruct, TaskStateSegment}; +use lazy_init::LazyInit; + +#[percpu::def_percpu] +static TSS: LazyInit = LazyInit::new(); + +#[percpu::def_percpu] +static GDT: LazyInit = LazyInit::new(); + +fn init_percpu() { + unsafe { + let tss = TSS.current_ref_mut_raw(); + let gdt = GDT.current_ref_mut_raw(); + tss.init_by(TaskStateSegment::new()); + gdt.init_by(GdtStruct::new(tss)); + gdt.load(); + gdt.load_tss(); + } +} + +/// Initializes IDT, GDT on the primary CPU. +pub fn init_primary() { + axlog::ax_println!("\nInitialize IDT & GDT..."); + init_percpu(); +} + +/// Initializes IDT, GDT on secondary CPUs. +#[cfg(feature = "smp")] +pub fn init_secondary() { + init_percpu(); +} + +/// set tss stack top +pub fn set_tss_stack_top(kernel_stack_top: memory_addr::VirtAddr) { + TSS.with_current(|tss| { + tss.privilege_stack_table[0] = x86_64::VirtAddr::new(kernel_stack_top.as_usize() as u64); + }) +} diff --git a/modules/axhal/src/platform/x86_pc/mem.rs b/modules/axhal/src/platform/x86_pc/mem.rs new file mode 100644 index 0000000..625e8ff --- /dev/null +++ b/modules/axhal/src/platform/x86_pc/mem.rs @@ -0,0 +1,15 @@ +// TODO: get memory regions from multiboot info. + +use crate::mem::{MemRegion, MemRegionFlags, PhysAddr}; + +/// Returns platform-specific memory regions. +pub(crate) fn platform_regions() -> impl Iterator { + core::iter::once(MemRegion { + paddr: PhysAddr::from(0x1000), + size: 0x9e000, + flags: MemRegionFlags::RESERVED | MemRegionFlags::READ | MemRegionFlags::WRITE, + name: "low memory", + }) + .chain(crate::mem::default_free_regions()) + .chain(crate::mem::default_mmio_regions()) +} diff --git a/modules/axhal/src/platform/x86_pc/misc.rs b/modules/axhal/src/platform/x86_pc/misc.rs new file mode 100644 index 0000000..1dfbee5 --- /dev/null +++ b/modules/axhal/src/platform/x86_pc/misc.rs @@ -0,0 +1,25 @@ +/// Shutdown the whole system (in QEMU), including all CPUs. +/// +/// See for more information. +pub fn terminate() -> ! { + info!("Shutting down..."); + + #[cfg(platform = "x86_64-pc-oslab")] + { + axlog::ax_println!("System will reboot, press any key to continue ..."); + while super::console::getchar().is_none() {} + axlog::ax_println!("Rebooting ..."); + unsafe { x86_64::instructions::port::PortWriteOnly::new(0x64).write(0xfeu8) }; + } + + #[cfg(platform = "x86_64-qemu-q35")] + unsafe { + x86_64::instructions::port::PortWriteOnly::new(0x604).write(0x2000u16) + }; + + crate::arch::halt(); + warn!("It should shutdown!"); + loop { + crate::arch::halt(); + } +} diff --git a/modules/axhal/src/platform/x86_pc/mod.rs b/modules/axhal/src/platform/x86_pc/mod.rs new file mode 100644 index 0000000..4a44a43 --- /dev/null +++ b/modules/axhal/src/platform/x86_pc/mod.rs @@ -0,0 +1,37 @@ +pub mod apic; + +pub mod dtables; +mod uart16550; + +pub mod mem; +pub mod misc; +pub mod time; + +#[cfg(feature = "irq")] +pub mod irq { + pub use super::apic::*; +} + +pub mod console { + pub use super::uart16550::*; +} + +pub use dtables::set_tss_stack_top; + +/// Initializes the platform devices for the primary CPU. +pub fn platform_init() { + self::apic::init_primary(); + self::time::init_primary(); +} + +/// Initializes the platform devices for secondary CPUs. +#[cfg(feature = "smp")] +pub fn platform_init_secondary() { + self::apic::init_secondary(); + self::time::init_secondary(); +} + +/// Returns the name of the platform. +pub fn platform_name() -> &'static str { + "x86_pc" +} diff --git a/modules/axhal/src/platform/x86_pc/multiboot.S b/modules/axhal/src/platform/x86_pc/multiboot.S new file mode 100644 index 0000000..b18103b --- /dev/null +++ b/modules/axhal/src/platform/x86_pc/multiboot.S @@ -0,0 +1,143 @@ +# Bootstrapping from 32-bit with the Multiboot specification. +# See https://www.gnu.org/software/grub/manual/multiboot/multiboot.html + +.section .text.boot +.code32 +.global _start +_start: + mov edi, eax # arg1: magic: 0x2BADB002 + mov esi, ebx # arg2: multiboot info + jmp bsp_entry32 + +.balign 4 +.type multiboot_header, STT_OBJECT +multiboot_header: + .int {mb_hdr_magic} # magic: 0x1BADB002 + .int {mb_hdr_flags} # flags + .int -({mb_hdr_magic} + {mb_hdr_flags}) # checksum + .int multiboot_header - {offset} # header_addr + .int _skernel - {offset} # load_addr + .int _edata - {offset} # load_end + .int _ebss - {offset} # bss_end_addr + .int _start - {offset} # entry_addr + +# Common code in 32-bit, prepare states to enter 64-bit. +.macro ENTRY32_COMMON + # set data segment selectors + mov ax, 0x18 + mov ss, ax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + # set PAE, PGE bit in CR4 + mov eax, {cr4} + mov cr4, eax + + # load the temporary page table + lea eax, [.Ltmp_pml4 - {offset}] + mov cr3, eax + + # set LME, NXE bit in IA32_EFER + mov ecx, {efer_msr} + mov edx, 0 + mov eax, {efer} + wrmsr + + # set protected mode, write protect, paging bit in CR0 + mov eax, {cr0} + mov cr0, eax +.endm + +# Common code in 64-bit +.macro ENTRY64_COMMON + # clear segment selectors + xor ax, ax + mov ss, ax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax +.endm + +.code32 +bsp_entry32: + lgdt [.Ltmp_gdt_desc - {offset}] # load the temporary GDT + ENTRY32_COMMON + ljmp 0x10, offset bsp_entry64 - {offset} # 0x10 is code64 segment + +.code32 +.global ap_entry32 +ap_entry32: + ENTRY32_COMMON + ljmp 0x10, offset ap_entry64 - {offset} # 0x10 is code64 segment + +.code64 +bsp_entry64: + ENTRY64_COMMON + + # set RSP to boot stack + movabs rsp, offset {boot_stack} + add rsp, {boot_stack_size} + + # call rust_entry(magic, mbi) + movabs rax, offset {entry} + call rax + jmp .Lhlt + +.code64 +ap_entry64: + ENTRY64_COMMON + + # set RSP to high address (already set in ap_start.S) + mov rax, {offset} + add rsp, rax + + # call rust_entry_secondary(magic) + mov rdi, {mb_magic} + movabs rax, offset {entry_secondary} + call rax + jmp .Lhlt + +.Lhlt: + hlt + jmp .Lhlt + +.section .rodata +.balign 8 +.Ltmp_gdt_desc: + .short .Ltmp_gdt_end - .Ltmp_gdt - 1 # limit + .long .Ltmp_gdt - {offset} # base + +.section .data +.balign 16 +.Ltmp_gdt: + .quad 0x0000000000000000 # 0x00: null + .quad 0x00cf9b000000ffff # 0x08: code segment (base=0, limit=0xfffff, type=32bit code exec/read, DPL=0, 4k) + .quad 0x00af9b000000ffff # 0x10: code segment (base=0, limit=0xfffff, type=64bit code exec/read, DPL=0, 4k) + .quad 0x00cf93000000ffff # 0x18: data segment (base=0, limit=0xfffff, type=32bit data read/write, DPL=0, 4k) +.Ltmp_gdt_end: + +.balign 4096 +.Ltmp_pml4: + # 0x0000_0000 ~ 0xffff_ffff + .quad .Ltmp_pdpt_low - {offset} + 0x3 # PRESENT | WRITABLE | paddr(tmp_pdpt) + .zero 8 * 510 + # 0xffff_ff80_0000_0000 ~ 0xffff_ff80_ffff_ffff + .quad .Ltmp_pdpt_high - {offset} + 0x3 # PRESENT | WRITABLE | paddr(tmp_pdpt) + +# FIXME: may not work on macOS using hvf as the CPU does not support 1GB page (pdpe1gb) +.Ltmp_pdpt_low: + .quad 0x0000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x0) + .quad 0x40000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x4000_0000) + .quad 0x80000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x8000_0000) + .quad 0xc0000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0xc000_0000) + .zero 8 * 508 + +.Ltmp_pdpt_high: + .quad 0x0000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x0) + .quad 0x40000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x4000_0000) + .quad 0x80000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x8000_0000) + .quad 0xc0000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0xc000_0000) + .zero 8 * 508 diff --git a/modules/axhal/src/platform/x86_pc/time.rs b/modules/axhal/src/platform/x86_pc/time.rs new file mode 100644 index 0000000..1cd2f1c --- /dev/null +++ b/modules/axhal/src/platform/x86_pc/time.rs @@ -0,0 +1,82 @@ +use raw_cpuid::CpuId; + +#[cfg(feature = "irq")] +const LAPIC_TICKS_PER_SEC: u64 = 100_000_000; // TODO: need to calibrate + +#[cfg(feature = "irq")] +static mut NANOS_TO_LAPIC_TICKS_RATIO: ratio::Ratio = ratio::Ratio::zero(); + +static mut INIT_TICK: u64 = 0; +static mut CPU_FREQ_MHZ: u64 = axconfig::TIMER_FREQUENCY as u64 / 1_000_000; + +/// Returns the current clock time in hardware ticks. +pub fn current_ticks() -> u64 { + unsafe { core::arch::x86_64::_rdtsc() - INIT_TICK } +} + +/// Converts hardware ticks to nanoseconds. +pub fn ticks_to_nanos(ticks: u64) -> u64 { + ticks * 1_000 / unsafe { CPU_FREQ_MHZ } +} + +/// Converts nanoseconds to hardware ticks. +pub fn nanos_to_ticks(nanos: u64) -> u64 { + nanos * unsafe { CPU_FREQ_MHZ } / 1_000 +} + +/// Set a one-shot timer. +/// +/// A timer interrupt will be triggered at the given deadline (in nanoseconds). +#[cfg(feature = "irq")] +pub fn set_oneshot_timer(deadline_ns: u64) { + let lapic = super::apic::local_apic(); + let now_ns = crate::time::current_time_nanos(); + unsafe { + if now_ns < deadline_ns { + let apic_ticks = NANOS_TO_LAPIC_TICKS_RATIO.mul_trunc(deadline_ns - now_ns); + assert!(apic_ticks <= u32::MAX as u64); + lapic.set_timer_initial(apic_ticks.max(1) as u32); + } else { + lapic.set_timer_initial(1); + } + } +} + +pub fn init_early() { + if let Some(freq) = CpuId::new() + .get_processor_frequency_info() + .map(|info| info.processor_base_frequency()) + { + if freq > 0 { + axlog::ax_println!("Got TSC frequency by CPUID: {} MHz", freq); + unsafe { CPU_FREQ_MHZ = freq as u64 } + } + } + + unsafe { INIT_TICK = core::arch::x86_64::_rdtsc() }; +} + +pub(super) fn init_primary() { + #[cfg(feature = "irq")] + unsafe { + use x2apic::lapic::{TimerDivide, TimerMode}; + let lapic = super::apic::local_apic(); + lapic.set_timer_mode(TimerMode::OneShot); + lapic.set_timer_divide(TimerDivide::Div256); // indeed it is Div1, the name is confusing. + lapic.enable_timer(); + + // TODO: calibrate with HPET + NANOS_TO_LAPIC_TICKS_RATIO = ratio::Ratio::new( + LAPIC_TICKS_PER_SEC as u32, + crate::time::NANOS_PER_SEC as u32, + ); + } +} + +#[cfg(feature = "smp")] +pub(super) fn init_secondary() { + #[cfg(feature = "irq")] + unsafe { + super::apic::local_apic().enable_timer(); + } +} diff --git a/modules/axhal/src/platform/x86_pc/uart16550.rs b/modules/axhal/src/platform/x86_pc/uart16550.rs new file mode 100644 index 0000000..f4bb35b --- /dev/null +++ b/modules/axhal/src/platform/x86_pc/uart16550.rs @@ -0,0 +1,105 @@ +//! Uart 16550. + +use spinlock::SpinNoIrq; +use x86_64::instructions::port::{Port, PortReadOnly, PortWriteOnly}; + +const UART_CLOCK_FACTOR: usize = 16; +const OSC_FREQ: usize = 1_843_200; + +static COM1: SpinNoIrq = SpinNoIrq::new(Uart16550::new(0x3f8)); + +bitflags::bitflags! { + /// Line status flags + struct LineStsFlags: u8 { + const INPUT_FULL = 1; + // 1 to 4 unknown + const OUTPUT_EMPTY = 1 << 5; + // 6 and 7 unknown + } +} + +struct Uart16550 { + data: Port, + int_en: PortWriteOnly, + fifo_ctrl: PortWriteOnly, + line_ctrl: PortWriteOnly, + modem_ctrl: PortWriteOnly, + line_sts: PortReadOnly, +} + +impl Uart16550 { + const fn new(port: u16) -> Self { + Self { + data: Port::new(port), + int_en: PortWriteOnly::new(port + 1), + fifo_ctrl: PortWriteOnly::new(port + 2), + line_ctrl: PortWriteOnly::new(port + 3), + modem_ctrl: PortWriteOnly::new(port + 4), + line_sts: PortReadOnly::new(port + 5), + } + } + + fn init(&mut self, baud_rate: usize) { + unsafe { + // Disable interrupts + self.int_en.write(0x00); + + // Enable DLAB + self.line_ctrl.write(0x80); + + // Set maximum speed according the input baud rate by configuring DLL and DLM + let divisor = OSC_FREQ / (baud_rate * UART_CLOCK_FACTOR); + self.data.write((divisor & 0xff) as u8); + self.int_en.write((divisor >> 8) as u8); + + // Disable DLAB and set data word length to 8 bits + self.line_ctrl.write(0x03); + + // Enable FIFO, clear TX/RX queues and + // set interrupt watermark at 14 bytes + self.fifo_ctrl.write(0xC7); + + // Mark data terminal ready, signal request to send + // and enable auxilliary output #2 (used as interrupt line for CPU) + self.modem_ctrl.write(0x0B); + } + } + + fn line_sts(&mut self) -> LineStsFlags { + unsafe { LineStsFlags::from_bits_truncate(self.line_sts.read()) } + } + + fn putchar(&mut self, c: u8) { + while !self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY) {} + unsafe { self.data.write(c) }; + } + + fn getchar(&mut self) -> Option { + if self.line_sts().contains(LineStsFlags::INPUT_FULL) { + unsafe { Some(self.data.read()) } + } else { + None + } + } +} + +/// Writes a byte to the console. +pub fn putchar(c: u8) { + let mut uart = COM1.lock(); + match c { + b'\n' => { + uart.putchar(b'\r'); + uart.putchar(b'\n'); + } + c => uart.putchar(c), + } +} + +/// Reads a byte from the console, or returns [`None`] if no input is available. +pub fn getchar() -> Option { + COM1.lock().getchar() +} + +pub fn init_early() { + COM1.lock().init(115200); +} diff --git a/modules/axhal/src/time.rs b/modules/axhal/src/time.rs new file mode 100644 index 0000000..f5c7fa8 --- /dev/null +++ b/modules/axhal/src/time.rs @@ -0,0 +1,48 @@ +//! Time-related operations. + +pub use core::time::Duration; + +/// A measurement of the system clock. +/// +/// Currently, it reuses the [`core::time::Duration`] type. But it does not +/// represent a duration, but a clock time. +pub type TimeValue = Duration; + +#[cfg(feature = "irq")] +pub use crate::platform::irq::TIMER_IRQ_NUM; +#[cfg(feature = "irq")] +pub use crate::platform::time::set_oneshot_timer; +pub use crate::platform::time::{current_ticks, nanos_to_ticks, ticks_to_nanos}; + +/// Number of milliseconds in a second. +pub const MILLIS_PER_SEC: u64 = 1_000; +/// Number of microseconds in a second. +pub const MICROS_PER_SEC: u64 = 1_000_000; +/// Number of nanoseconds in a second. +pub const NANOS_PER_SEC: u64 = 1_000_000_000; +/// Number of nanoseconds in a millisecond. +pub const NANOS_PER_MILLIS: u64 = 1_000_000; +/// Number of nanoseconds in a microsecond. +pub const NANOS_PER_MICROS: u64 = 1_000; + +/// Returns the current clock time in nanoseconds. +pub fn current_time_nanos() -> u64 { + ticks_to_nanos(current_ticks()) +} + +/// Returns the current clock time in [`TimeValue`]. +pub fn current_time() -> TimeValue { + TimeValue::from_nanos(current_time_nanos()) +} + +/// Busy waiting for the given duration. +pub fn busy_wait(dur: Duration) { + busy_wait_until(current_time() + dur); +} + +/// Busy waiting until reaching the given deadline. +pub fn busy_wait_until(deadline: TimeValue) { + while current_time() < deadline { + core::hint::spin_loop(); + } +} diff --git a/modules/axhal/src/tls.rs b/modules/axhal/src/tls.rs new file mode 100644 index 0000000..bb0ec06 --- /dev/null +++ b/modules/axhal/src/tls.rs @@ -0,0 +1,171 @@ +//! Thread Local Storage (TLS) support. +//! +//! ## TLS layout for x86_64 +//! +//! ```text +//! aligned --> +-------------------------+- static_tls_offset +//! allocation | | \ +//! | .tdata | | +//! | address | | | +//! | grow up + - - - - - - - - - - - - + > Static TLS block +//! v | | | (length: static_tls_size) +//! | .tbss | | +//! | | | +//! +-------------------------+ | +//! | / PADDING / / / / / / / | / +//! +-------------------------+ +//! tls_ptr -+-> self pointer (void *) | \ +//! (tp_offset) | | | +//! | Custom TCB format | > Thread Control Block (TCB) +//! | (might be used | | (length: TCB_SIZE) +//! | by a libC) | | +//! | | / +//! +-------------------------+- (total length: tls_area_size) +//! ``` +//! +//! ## TLS layout for AArch64 and RISC-V +//! +//! ```text +//! +-------------------------+ +//! | | \ +//! | Custom TCB format | | +//! | (might be used | > Thread Control Block (TCB) +//! | by a libC) | | (length: TCB_SIZE) +//! | | / +//! tls_ptr -+-------------------------+ +//! (tp_offset) | GAP_ABOVE_TP | +//! +-------------------------+- static_tls_offset +//! | | \ +//! | .tdata | | +//! | | | +//! + - - - - - - - - - - - - + > Static TLS block +//! | | | (length: static_tls_size) +//! | .tbss | | +//! | | / +//! +-------------------------+- (total length: tls_area_size) +//! ``` +//! +//! Reference: +//! 1. +//! 2. + +extern crate alloc; + +use memory_addr::align_up; + +use core::alloc::Layout; +use core::ptr::NonNull; + +const TLS_ALIGN: usize = 0x10; + +cfg_if::cfg_if! { + if #[cfg(target_arch = "x86_64")] { + const TCB_SIZE: usize = 8; // to store TLS self pointer + const GAP_ABOVE_TP: usize = 0; + } else if #[cfg(target_arch = "aarch64")] { + const TCB_SIZE: usize = 0; + const GAP_ABOVE_TP: usize = 16; + } else if #[cfg(target_arch = "riscv64")] { + const TCB_SIZE: usize = 0; + const GAP_ABOVE_TP: usize = 0; + } +} + +extern "C" { + fn _stdata(); + fn _etdata(); + fn _etbss(); +} + +/// The memory region for thread-local storage. +pub struct TlsArea { + base: NonNull, + layout: Layout, +} + +impl Drop for TlsArea { + fn drop(&mut self) { + unsafe { + alloc::alloc::dealloc(self.base.as_ptr(), self.layout); + } + } +} + +impl TlsArea { + /// Returns the pointer to the TLS static area. + /// + /// One should set the hardware thread pointer register to this value. + pub fn tls_ptr(&self) -> *mut u8 { + unsafe { self.base.as_ptr().add(tp_offset()) } + } + + /// Allocates the memory region for TLS, and initializes it. + pub fn alloc() -> Self { + let layout = Layout::from_size_align(tls_area_size(), TLS_ALIGN).unwrap(); + let area_base = unsafe { alloc::alloc::alloc_zeroed(layout) }; + info!("layout size: {}", layout.size()); + let tls_load_base = _stdata as *mut u8; + let tls_load_size = _etbss as usize - _stdata as usize; + unsafe { + // copy data from .tbdata section + core::ptr::copy_nonoverlapping( + tls_load_base, + area_base.add(static_tls_offset()), + tls_load_size, + ); + // initialize TCB + init_tcb(area_base); + } + + Self { + base: NonNull::new(area_base).unwrap(), + layout, + } + } +} + +fn static_tls_size() -> usize { + let ans = align_up(_etbss as usize - _stdata as usize, TLS_ALIGN); + info!( + "static_tls_size: {} _etbss: {:X} _stdata: {:X} ", + ans, _etbss as usize, _stdata as usize + ); + ans +} + +fn static_tls_offset() -> usize { + if cfg!(target_arch = "x86_64") { + 0 + } else if cfg!(any(target_arch = "aarch64", target_arch = "riscv64")) { + TCB_SIZE + GAP_ABOVE_TP + } else { + unreachable!() + } +} + +fn tp_offset() -> usize { + if cfg!(target_arch = "x86_64") { + static_tls_size() + } else if cfg!(any(target_arch = "aarch64", target_arch = "riscv64")) { + TCB_SIZE + } else { + unreachable!() + } +} + +fn tls_area_size() -> usize { + if cfg!(target_arch = "x86_64") { + static_tls_size() + TCB_SIZE + } else if cfg!(any(target_arch = "aarch64", target_arch = "riscv64")) { + TCB_SIZE + GAP_ABOVE_TP + static_tls_size() + } else { + unreachable!() + } +} + +unsafe fn init_tcb(tls_area: *mut u8) { + if cfg!(target_arch = "x86_64") { + let tp_addr = tls_area.add(tp_offset()).cast::(); + tp_addr.write(tp_addr as usize); // write self pointer + } +} diff --git a/modules/axlog/.gitignore b/modules/axlog/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/modules/axlog/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/modules/axlog/Cargo.toml b/modules/axlog/Cargo.toml new file mode 100644 index 0000000..17e0a72 --- /dev/null +++ b/modules/axlog/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "axlog" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Macros for multi-level formatted logging used by ArceOS" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axlog" +documentation = "https://rcore-os.github.io/arceos/axlog/index.html" +keywords = ["Starry"] + +[features] +std = ["dep:chrono"] +log-level-off = ["log/max_level_off"] +log-level-error = ["log/max_level_error"] +log-level-warn = ["log/max_level_warn"] +log-level-info = ["log/max_level_info"] +log-level-debug = ["log/max_level_debug"] +log-level-trace = ["log/max_level_trace"] +default = [] + +[dependencies] +cfg-if = "1.0" +log = "0.4" +spinlock = { git = "https://github.com/Starry-OS/spinlock.git" } +crate_interface = { git = "https://github.com/Starry-OS/crate_interface.git" } +chrono = { version = "0.4", optional = true } + +[dev-dependencies] +axlog = { workspace = true, features = ["std"] } diff --git a/modules/axlog/src/lib.rs b/modules/axlog/src/lib.rs new file mode 100644 index 0000000..d3ad70b --- /dev/null +++ b/modules/axlog/src/lib.rs @@ -0,0 +1,262 @@ +//! Macros for multi-level formatted logging used by +//! [ArceOS](https://github.com/rcore-os/arceos). +//! +//! The log macros, in descending order of level, are: [`error!`], [`warn!`], +//! [`info!`], [`debug!`], and [`trace!`]. +//! +//! If it is used in `no_std` environment, the users need to implement the +//! [`LogIf`] to provide external functions such as console output. +//! +//! To use in the `std` environment, please enable the `std` feature: +//! +//! ```toml +//! [dependencies] +//! axlog = { version = "0.1", features = ["std"] } +//! ``` +//! +//! # Cargo features: +//! +//! - `std`: Use in the `std` environment. If it is enabled, you can use console +//! output without implementing the [`LogIf`] trait. This is disabled by default. +//! - `log-level-off`: Disable all logging. If it is enabled, all log macros +//! (e.g. [`info!`]) will be optimized out to a no-op in compilation time. +//! - `log-level-error`: Set the maximum log level to `error`. Any macro +//! with a level lower than [`error!`] (e.g, [`warn!`], [`info!`], ...) will be +//! optimized out to a no-op. +//! - `log-level-warn`, `log-level-info`, `log-level-debug`, `log-level-trace`: +//! Similar to `log-level-error`. +//! +//! # Examples +//! +//! ``` +//! use axlog::{debug, error, info, trace, warn}; +//! +//! // Initialize the logger. +//! axlog::init(); +//! // Set the maximum log level to `info`. +//! axlog::set_max_level("info"); +//! +//! // The following logs will be printed. +//! error!("error"); +//! warn!("warn"); +//! info!("info"); +//! +//! // The following logs will not be printed. +//! debug!("debug"); +//! trace!("trace"); +//! ``` + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate log; + +use core::fmt::{self, Write}; +use core::str::FromStr; + +use log::{Level, LevelFilter, Log, Metadata, Record}; + +#[cfg(not(feature = "std"))] +use crate_interface::call_interface; + +pub use log::{debug, error, info, trace, warn}; + +/// Prints to the console. +/// +/// Equivalent to the [`ax_println!`] macro except that a newline is not printed at +/// the end of the message. +#[macro_export] +macro_rules! ax_print { + ($($arg:tt)*) => { + $crate::__print_impl(format_args!($($arg)*)); + } +} + +/// Prints to the console, with a newline. +#[macro_export] +macro_rules! ax_println { + () => { $crate::ax_print!("\n") }; + ($($arg:tt)*) => { + $crate::__print_impl(format_args!("{}\n", format_args!($($arg)*))); + } +} + +macro_rules! with_color { + ($color_code:expr, $($arg:tt)*) => {{ + format_args!("\u{1B}[{}m{}\u{1B}[m", $color_code as u8, format_args!($($arg)*)) + }}; +} + +#[repr(u8)] +#[allow(dead_code)] +enum ColorCode { + Black = 30, + Red = 31, + Green = 32, + Yellow = 33, + Blue = 34, + Magenta = 35, + Cyan = 36, + White = 37, + BrightBlack = 90, + BrightRed = 91, + BrightGreen = 92, + BrightYellow = 93, + BrightBlue = 94, + BrightMagenta = 95, + BrightCyan = 96, + BrightWhite = 97, +} + +/// Extern interfaces that must be implemented in other crates. +#[crate_interface::def_interface] +pub trait LogIf { + /// Writes a string to the console. + fn console_write_str(s: &str); + + /// Gets current clock time. + fn current_time() -> core::time::Duration; + + /// Gets current CPU ID. + /// + /// Returns [`None`] if you don't want to show the CPU ID in the log. + fn current_cpu_id() -> Option; + + /// Gets current task ID. + /// + /// Returns [`None`] if you don't want to show the task ID in the log. + fn current_task_id() -> Option; +} + +struct Logger; + +impl Write for Logger { + fn write_str(&mut self, s: &str) -> fmt::Result { + cfg_if::cfg_if! { + if #[cfg(feature = "std")] { + std::print!("{}", s); + } else { + call_interface!(LogIf::console_write_str, s); + } + } + Ok(()) + } +} + +impl Log for Logger { + #[inline] + fn enabled(&self, _metadata: &Metadata) -> bool { + true + } + + fn log(&self, record: &Record) { + if !self.enabled(record.metadata()) { + return; + } + + let level = record.level(); + let line = record.line().unwrap_or(0); + let path = record.target(); + let args_color = match level { + Level::Error => ColorCode::Red, + Level::Warn => ColorCode::Yellow, + Level::Info => ColorCode::Green, + Level::Debug => ColorCode::Cyan, + Level::Trace => ColorCode::BrightBlack, + }; + + cfg_if::cfg_if! { + if #[cfg(feature = "std")] { + __print_impl(with_color!( + ColorCode::White, + "[{time} {path}:{line}] {args}\n", + time = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.6f"), + path = path, + line = line, + args = with_color!(args_color, "{}", record.args()), + )); + } else { + let cpu_id = call_interface!(LogIf::current_cpu_id); + let tid = call_interface!(LogIf::current_task_id); + let now = call_interface!(LogIf::current_time); + if let Some(cpu_id) = cpu_id { + if let Some(tid) = tid { + // show CPU ID and task ID + __print_impl(with_color!( + ColorCode::White, + "[{:>3}.{:06} {cpu_id}:{tid} {path}:{line}] {args}\n", + now.as_secs(), + now.subsec_micros(), + cpu_id = cpu_id, + tid = tid, + path = path, + line = line, + args = with_color!(args_color, "{}", record.args()), + )); + } else { + // show CPU ID only + __print_impl(with_color!( + ColorCode::White, + "[{:>3}.{:06} {cpu_id} {path}:{line}] {args}\n", + now.as_secs(), + now.subsec_micros(), + cpu_id = cpu_id, + path = path, + line = line, + args = with_color!(args_color, "{}", record.args()), + )); + } + } else { + // neither CPU ID nor task ID is shown + __print_impl(with_color!( + ColorCode::White, + "[{:>3}.{:06} {path}:{line}] {args}\n", + now.as_secs(), + now.subsec_micros(), + path = path, + line = line, + args = with_color!(args_color, "{}", record.args()), + )); + } + } + } + } + + fn flush(&self) {} +} + +/// Prints the formatted string to the console. +pub fn print_fmt(args: fmt::Arguments) -> fmt::Result { + use spinlock::SpinNoIrq; // TODO: more efficient + static LOCK: SpinNoIrq<()> = SpinNoIrq::new(()); + + let _guard = LOCK.lock(); + Logger.write_fmt(args) +} + +#[doc(hidden)] +pub fn __print_impl(args: fmt::Arguments) { + print_fmt(args).unwrap(); +} + +/// Initializes the logger. +/// +/// This function should be called before any log macros are used, otherwise +/// nothing will be printed. +pub fn init() { + log::set_logger(&Logger).unwrap(); + log::set_max_level(LevelFilter::Warn); +} + +/// Set the maximum log level. +/// +/// Unlike the features such as `log-level-error`, setting the logging level in +/// this way incurs runtime overhead. In addition, this function is no effect +/// when those features are enabled. +/// +/// `level` should be one of `off`, `error`, `warn`, `info`, `debug`, `trace`. +pub fn set_max_level(level: &str) { + let lf = LevelFilter::from_str(level) + .ok() + .unwrap_or(LevelFilter::Off); + log::set_max_level(lf); +} diff --git a/modules/axmem/.gitignore b/modules/axmem/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/modules/axmem/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/modules/axmem/Cargo.toml b/modules/axmem/Cargo.toml new file mode 100644 index 0000000..ee17ba3 --- /dev/null +++ b/modules/axmem/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "axmem" +version = "0.1.0" +edition = "2021" +authors = ["Youjie Zheng "] +keywords = ["Starry", "address-space"] +description = "Crates to implement multiple address space abstraction" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] + +monolithic = ["axfs/monolithic"] + +default = [] + +[dependencies] +log = "0.4" +axhal = { workspace = true, features = ["paging"] } +axalloc = { workspace = true } +axconfig = { workspace = true } +axerrno = { git = "https://github.com/Starry-OS/axerrno.git" } +axfs = { workspace = true } +axio = { git = "https://github.com/Starry-OS/axio.git" } +axsync = { workspace = true } +spinlock = { git = "https://github.com/Starry-OS/spinlock.git" } +xmas-elf = "0.9.0" +riscv = "0.10" +page_table_entry = { git = "https://github.com/Starry-OS/page_table_entry.git" } +elf_parser = { git = "https://github.com/Starry-OS/elf_parser.git" } \ No newline at end of file diff --git a/modules/axmem/src/area.rs b/modules/axmem/src/area.rs new file mode 100644 index 0000000..c0e0a86 --- /dev/null +++ b/modules/axmem/src/area.rs @@ -0,0 +1,578 @@ +use alloc::{sync::Arc, vec::Vec}; +use axalloc::PhysPage; +use axerrno::AxResult; +use axhal::{ + mem::{virt_to_phys, VirtAddr, PAGE_SIZE_4K}, + paging::{MappingFlags, PageSize, PageTable}, +}; +use axio::{Seek, SeekFrom}; +use axsync::Mutex; +use core::ptr::copy_nonoverlapping; + +use crate::MemBackend; + +/// A continuous virtual area in user memory. +/// +/// NOTE: Cloning a `MapArea` needs allocating new phys pages and modifying a page table. So +/// `Clone` trait won't implemented. +pub struct MapArea { + /// phys pages of this area + pub pages: Vec>>>, + /// start virtual address + pub vaddr: VirtAddr, + /// shared in child process + shared: bool, + /// mapping flags of this area + pub flags: MappingFlags, + /// whether the area is backed by a file + pub backend: Option, +} + +impl MapArea { + /// Create a lazy-load area and map it in page table (page fault PTE). + pub fn new_lazy( + start: VirtAddr, + num_pages: usize, + flags: MappingFlags, + backend: Option, + page_table: &mut PageTable, + ) -> Self { + let mut pages = Vec::with_capacity(num_pages); + for _ in 0..num_pages { + pages.push(None); + } + + page_table + .map_fault_region(start, num_pages * PAGE_SIZE_4K, flags) + .unwrap(); + + Self { + pages, + vaddr: start, + shared: false, + flags, + backend, + } + } + + /// Allocated an area and map it in page table. + pub fn new_alloc( + start: VirtAddr, + num_pages: usize, + flags: MappingFlags, + data: Option<&[u8]>, + backend: Option, + page_table: &mut PageTable, + ) -> AxResult { + let pages = PhysPage::alloc_contiguous(num_pages, PAGE_SIZE_4K, data)? + .into_iter() + .map(|page| page.map(|page| Arc::new(Mutex::new(page)))) + .collect::>(); + + debug!( + "start: {:X?}, size: {:X}, page start: {:X?} flags: {:?}", + start, + num_pages * PAGE_SIZE_4K, + pages[0].as_ref().unwrap().lock().start_vaddr, + flags + ); + page_table + .map_region( + start, + virt_to_phys(pages[0].as_ref().unwrap().lock().start_vaddr), + num_pages * PAGE_SIZE_4K, + flags, + false, + ) + .unwrap(); + Ok(Self { + pages, + vaddr: start, + shared: false, + flags, + backend, + }) + } + + /// Set the shared flag of the area. + pub(crate) fn set_shared(&mut self, shared: bool) { + self.shared = shared; + } + + /// Return whether the area is shared in child process. + pub(crate) fn is_shared(&self) -> bool { + self.shared + } + + /// Deallocate all phys pages and unmap the area in page table. + pub fn dealloc(&mut self, page_table: &mut PageTable) { + page_table.unmap_region(self.vaddr, self.size()).unwrap(); + self.pages.clear(); + } + + /// 如果处理失败,返回false,此时直接退出当前程序 + pub fn handle_page_fault( + &mut self, + addr: VirtAddr, + flags: MappingFlags, + page_table: &mut PageTable, + ) -> bool { + trace!( + "handling {:?} page fault in area [{:?}, {:?})", + addr, + self.vaddr, + self.end_va() + ); + assert!( + self.vaddr <= addr && addr < self.end_va(), + "Try to handle page fault address out of bound" + ); + if !self.flags.contains(flags) { + error!( + "Try to access {:?} memory addr: {:?} with {:?} flag", + self.flags, addr, flags + ); + return false; + } + + let page_index = (usize::from(addr) - usize::from(self.vaddr)) / PAGE_SIZE_4K; + if page_index >= self.pages.len() { + error!("Phys page index out of bound"); + return false; + } + if self.pages[page_index].is_some() { + debug!("Page fault in page already loaded"); + return true; + } + + debug!("page index {}", page_index); + + // Allocate new page + let mut page = PhysPage::alloc().expect("Error allocating new phys page for page fault"); + + debug!( + "new phys page virtual (offset) address {:?}", + page.start_vaddr + ); + + // Read data from backend to fill with 0. + match &mut self.backend { + Some(backend) => { + if backend + .read_from_seek( + SeekFrom::Current((page_index * PAGE_SIZE_4K) as i64), + page.as_slice_mut(), + ) + .is_err() + { + warn!("Failed to read from backend to memory"); + page.fill(0); + } + } + None => page.fill(0), + }; + + // Map newly allocated page in the page_table + page_table + .map_overwrite( + addr.align_down_4k(), + virt_to_phys(page.start_vaddr), + axhal::paging::PageSize::Size4K, + self.flags, + ) + .expect("Map in page fault handler failed"); + + axhal::arch::flush_tlb(addr.align_down_4k().into()); + self.pages[page_index] = Some(Arc::new(Mutex::new(page))); + true + } + + /// Sync pages in index back to `self.backend` (if there is one). + /// + /// # Panics + /// + /// Panics if index is out of bounds. + pub fn sync_page_with_backend(&mut self, page_index: usize) { + if let Some(page) = &self.pages[page_index] { + if let Some(backend) = &mut self.backend { + if backend.writable() { + let _ = backend + .write_to_seek( + SeekFrom::Start((page_index * PAGE_SIZE_4K) as u64), + page.lock().as_slice(), + ) + .unwrap(); + } + } + } else { + debug!("Tried to sync an unallocated page"); + } + } + + /// Deallocate some pages from the start of the area. + /// This function will unmap them in a page table. You need to flush TLB after this function. + pub fn shrink_left(&mut self, new_start: VirtAddr, page_table: &mut PageTable) { + assert!(new_start.is_aligned_4k()); + + let delete_size = new_start.as_usize() - self.vaddr.as_usize(); + let delete_pages = delete_size / PAGE_SIZE_4K; + + // move backend offset + if let Some(backend) = &mut self.backend { + let _ = backend.seek(SeekFrom::Current(delete_size as i64)).unwrap(); + } + + // remove (dealloc) phys pages + drop(self.pages.drain(0..delete_pages)); + + // unmap deleted pages + page_table.unmap_region(self.vaddr, delete_size).unwrap(); + + self.vaddr = new_start; + } + + /// Deallocate some pages from the end of the area. + /// This function will unmap them in a page table. You need to flush TLB after this function. + pub fn shrink_right(&mut self, new_end: VirtAddr, page_table: &mut PageTable) { + assert!(new_end.is_aligned_4k()); + + let delete_size = self.end_va().as_usize() - new_end.as_usize(); + let delete_pages = delete_size / PAGE_SIZE_4K; + + // remove (dealloc) phys pages + drop( + self.pages + .drain((self.pages.len() - delete_pages)..self.pages.len()), + ); + + // unmap deleted pages + page_table.unmap_region(new_end, delete_size).unwrap(); + } + + /// Split this area into 2. + pub fn split(&mut self, addr: VirtAddr) -> Self { + assert!(addr.is_aligned_4k()); + + let right_page_count = (self.end_va() - addr.as_usize()).as_usize() / PAGE_SIZE_4K; + let right_page_range = self.pages.len() - right_page_count..self.pages.len(); + + let right_pages = self.pages.drain(right_page_range).collect(); + + Self { + pages: right_pages, + vaddr: addr, + flags: self.flags, + shared: self.shared, + backend: self.backend.as_ref().map(|backend| { + let mut backend = backend.clone(); + + let _ = backend + .seek(SeekFrom::Current( + (addr.as_usize() - self.vaddr.as_usize()) as i64, + )) + .unwrap(); + + backend + }), + } + } + + /// Split this area into 3. + pub fn split3(&mut self, start: VirtAddr, end: VirtAddr) -> (Self, Self) { + assert!(start.is_aligned_4k()); + assert!(end.is_aligned_4k()); + assert!(start < end); + assert!(self.vaddr < start); + assert!(end < self.end_va()); + + let right_pages = self + .pages + .drain( + self.pages.len() - (self.end_va().as_usize() - end.as_usize()) / PAGE_SIZE_4K + ..self.pages.len(), + ) + .collect(); + + let mid_pages = self + .pages + .drain( + self.pages.len() - (self.end_va().as_usize() - start.as_usize()) / PAGE_SIZE_4K + ..self.pages.len(), + ) + .collect(); + + let mid = Self { + pages: mid_pages, + vaddr: start, + flags: self.flags, + shared: self.shared, + backend: self.backend.as_ref().map(|backend| { + let mut backend = backend.clone(); + + let _ = backend + .seek(SeekFrom::Current( + (start.as_usize() - self.vaddr.as_usize()) as i64, + )) + .unwrap(); + + backend + }), + }; + + let right = Self { + pages: right_pages, + vaddr: end, + flags: self.flags, + shared: self.shared, + backend: self.backend.as_ref().map(|backend| { + let mut backend = backend.clone(); + + let _ = backend + .seek(SeekFrom::Current( + (end.as_usize() - self.vaddr.as_usize()) as i64, + )) + .unwrap(); + + backend + }), + }; + + (mid, right) + } + + /// Create a second area in the right part of the area, [self.vaddr, left_end) and + /// [right_start, self.end_va()). + /// This function will unmap deleted pages in a page table. You need to flush TLB after calling + /// this. + pub fn remove_mid( + &mut self, + left_end: VirtAddr, + right_start: VirtAddr, + page_table: &mut PageTable, + ) -> Self { + assert!(left_end.is_aligned_4k()); + assert!(right_start.is_aligned_4k()); + // We can have left_end == right_start, although it doesn't do anything other than create + // two areas. + assert!(left_end <= right_start); + + let delete_size = right_start.as_usize() - left_end.as_usize(); + let delete_range = ((left_end.as_usize() - self.vaddr.as_usize()) / PAGE_SIZE_4K) + ..((right_start.as_usize() - self.vaddr.as_usize()) / PAGE_SIZE_4K); + + // create a right area + let pages = self + .pages + .drain(((right_start.as_usize() - self.vaddr.as_usize()) / PAGE_SIZE_4K)..) + .collect(); + + let right_area = Self { + pages, + vaddr: right_start, + flags: self.flags, + shared: self.shared, + backend: self.backend.as_ref().map(|backend| { + let mut backend = backend.clone(); + let _ = backend + .seek(SeekFrom::Current( + (right_start.as_usize() - self.vaddr.as_usize()) as i64, + )) + .unwrap(); + + backend + }), + }; + + // remove pages + let _ = self.pages.drain(delete_range); + + page_table.unmap_region(left_end, delete_size).unwrap(); + + right_area + } +} + +impl MapArea { + /// return the size of the area, which thinks the page size is default 4K. + pub fn size(&self) -> usize { + self.pages.len() * PAGE_SIZE_4K + } + + /// return the end virtual address of the area. + pub fn end_va(&self) -> VirtAddr { + self.vaddr + self.size() + } + + /// return whether all the pages have been allocated. + pub fn allocated(&self) -> bool { + self.pages.iter().all(|page| page.is_some()) + } + /// # Safety + /// This function is unsafe because it dereferences a raw pointer. + /// It will return a slice of the area's memory, whose len is the same as the area's size. + pub unsafe fn as_slice(&self) -> &[u8] { + unsafe { core::slice::from_raw_parts(self.vaddr.as_ptr(), self.size()) } + } + + /// Fill `self` with `byte`. + pub fn fill(&mut self, byte: u8) { + self.pages.iter_mut().for_each(|page| { + if let Some(page) = page { + page.lock().fill(byte); + } + }); + } + + /// If [start, end) overlaps with self. + pub fn overlap_with(&self, start: VirtAddr, end: VirtAddr) -> bool { + self.vaddr <= start && start < self.end_va() || start <= self.vaddr && self.vaddr < end + } + + /// If [start, end] contains self. + pub fn contained_in(&self, start: VirtAddr, end: VirtAddr) -> bool { + start <= self.vaddr && self.end_va() <= end + } + + /// If self contains [start, end]. + pub fn contains(&self, start: VirtAddr, end: VirtAddr) -> bool { + self.vaddr <= start && end <= self.end_va() + } + + /// If self strictly contains [start, end], which stands for the start and end are not equal to self's. + pub fn strict_contain(&self, start: VirtAddr, end: VirtAddr) -> bool { + self.vaddr < start && end < self.end_va() + } + + /// Update area's mapping flags and write it to page table. You need to flush TLB after calling + /// this function. + pub fn update_flags(&mut self, flags: MappingFlags, page_table: &mut PageTable) { + self.flags = flags; + page_table + .update_region(self.vaddr, self.size(), flags) + .unwrap(); + } + /// # Clone the area. + /// + /// If the area is shared, we don't need to allocate new phys pages. + /// + /// If the area is not shared and all the pages have been allocated, + /// we can allocate a contiguous area in phys memory. + /// + /// This function will modify the page table as well. + /// + /// # Arguments + /// + /// * `page_table` - The page table of the new child process. + /// + /// * `parent_page_table` - The page table of the current process. + pub fn clone_alloc( + &mut self, + page_table: &mut PageTable, + parent_page_table: &mut PageTable, + ) -> AxResult { + // If the area is shared, we don't need to allocate new phys pages. + if self.is_shared() { + // Allocated all fault page in the parent page table. + let fault_pages: Vec<_> = self + .pages + .iter() + .enumerate() + .filter_map(|(idx, slot)| { + if slot.is_none() { + Some(self.vaddr + (idx * PAGE_SIZE_4K)) + } else { + None + } + }) + .collect(); + for vaddr in fault_pages { + self.handle_page_fault(vaddr, MappingFlags::empty(), parent_page_table); + } + + // Map the area in the child page table. + let pages: Vec<_> = self + .pages + .iter() + .enumerate() + .map(|(idx, slot)| { + let vaddr = self.vaddr + (idx * PAGE_SIZE_4K); + assert!(slot.is_some()); + let page = slot.as_ref().unwrap().lock(); + page_table + .map( + vaddr, + virt_to_phys(page.start_vaddr), + PageSize::Size4K, + self.flags, + ) + .unwrap(); + drop(page); + Some(Arc::clone(slot.as_ref().unwrap())) + }) + .collect(); + return Ok(Self { + pages, + vaddr: self.vaddr, + flags: self.flags, + shared: self.shared, + backend: self.backend.clone(), + }); + } + // All the pages have been allocated. Allocate a contiguous area in phys memory. + if self.allocated() { + MapArea::new_alloc( + self.vaddr, + self.pages.len(), + self.flags, + Some(unsafe { self.as_slice() }), + self.backend.clone(), + page_table, + ) + } else { + let pages: Vec<_> = self + .pages + .iter() + .enumerate() + .map(|(idx, slot)| { + let vaddr = self.vaddr + (idx * PAGE_SIZE_4K); + match slot.as_ref() { + Some(page) => { + let mut new_page = PhysPage::alloc().unwrap(); + unsafe { + copy_nonoverlapping( + page.lock().as_ptr(), + new_page.as_mut_ptr(), + PAGE_SIZE_4K, + ); + } + + page_table + .map( + vaddr, + virt_to_phys(new_page.start_vaddr), + PageSize::Size4K, + self.flags, + ) + .unwrap(); + + Some(Arc::new(Mutex::new(new_page))) + } + None => { + page_table + .map_fault(vaddr, PageSize::Size4K, self.flags) + .unwrap(); + None + } + } + }) + .collect(); + Ok(Self { + pages, + vaddr: self.vaddr, + flags: self.flags, + shared: self.shared, + backend: self.backend.clone(), + }) + } + } +} diff --git a/modules/axmem/src/backend.rs b/modules/axmem/src/backend.rs new file mode 100644 index 0000000..fa7dad5 --- /dev/null +++ b/modules/axmem/src/backend.rs @@ -0,0 +1,74 @@ +use alloc::boxed::Box; +use axfs::api::{File, FileExt}; +use axio::{Read, Seek, SeekFrom}; + +/// File backend for Lazy load `MapArea`. `file` should be a file holding a offset value. Normally, +/// `MemBackend` won't share a file with other things, so we use a `Box` here. +pub struct MemBackend { + file: Box, +} + +impl MemBackend { + /// Create a new `MemBackend` with a file and the seek offset of this file. + pub fn new(mut file: Box, offset: u64) -> Self { + let _ = file.seek(SeekFrom::Start(offset)).unwrap(); + + Self { file } + } + + /// clone a new `MemBackend` with a delta offset of the file of the original `MemBackend`. + pub fn clone_with_delta(&self, delta: i64) -> Self { + let mut new_backend = self.clone(); + + let _ = new_backend.seek(SeekFrom::Current(delta)).unwrap(); + + new_backend + } + + /// read from the file of the `MemBackend` with a pos offset. + pub fn read_from_seek(&mut self, pos: SeekFrom, buf: &mut [u8]) -> Result { + self.file.read_from_seek(pos, buf) + } + + /// write to the file of the `MemBackend` with a pos offset. + pub fn write_to_seek(&mut self, pos: SeekFrom, buf: &[u8]) -> Result { + self.file.write_to_seek(pos, buf) + } + + /// whether the file of the `MemBackend` is readable. + pub fn readable(&self) -> bool { + self.file.readable() + } + + /// whether the file of the `MemBackend` is writable. + pub fn writable(&self) -> bool { + self.file.writable() + } +} + +impl Clone for MemBackend { + fn clone(&self) -> Self { + let file = self + .file + .as_any() + .downcast_ref::() + .expect("Cloning a MemBackend with a non-file object") + .clone(); + + Self { + file: Box::new(file), + } + } +} + +impl Seek for MemBackend { + fn seek(&mut self, pos: SeekFrom) -> axio::Result { + self.file.seek(pos) + } +} + +impl Read for MemBackend { + fn read(&mut self, buf: &mut [u8]) -> axio::Result { + self.file.read(buf) + } +} diff --git a/modules/axmem/src/lib.rs b/modules/axmem/src/lib.rs new file mode 100644 index 0000000..b2a6303 --- /dev/null +++ b/modules/axmem/src/lib.rs @@ -0,0 +1,710 @@ +//! The memory management module, which implements the memory space management of the process. +#![cfg_attr(not(test), no_std)] +mod area; +mod backend; +mod shared; +pub use area::MapArea; +use axerrno::{AxError, AxResult}; +pub use backend::MemBackend; + +extern crate alloc; +use alloc::{collections::BTreeMap, sync::Arc, vec::Vec}; +use core::sync::atomic::{AtomicI32, Ordering}; +use page_table_entry::GenericPTE; +use shared::SharedMem; +use spinlock::SpinNoIrq; +#[macro_use] +extern crate log; + +use axhal::{ + arch::flush_tlb, + mem::{memory_regions, phys_to_virt, PhysAddr, VirtAddr, PAGE_SIZE_4K}, + paging::{MappingFlags, PageSize, PageTable, PagingError}, +}; + +// TODO: a real allocator +static SHMID: AtomicI32 = AtomicI32::new(1); + +/// This struct only hold SharedMem that are not IPC_PRIVATE. IPC_PRIVATE SharedMem will be stored +/// in MemorySet::detached_mem. +/// +/// This is the only place we can query a SharedMem using its shmid. +/// +/// It holds an Arc to the SharedMem. If the Arc::strong_count() is 1, SharedMem will be dropped. +pub static SHARED_MEMS: SpinNoIrq>> = SpinNoIrq::new(BTreeMap::new()); + +/// The map from key to shmid. It's used to query shmid from key. +pub static KEY_TO_SHMID: SpinNoIrq> = SpinNoIrq::new(BTreeMap::new()); + +/// PageTable + MemoryArea for a process (task) +pub struct MemorySet { + page_table: PageTable, + owned_mem: BTreeMap, + + private_mem: BTreeMap>, + attached_mem: Vec<(VirtAddr, MappingFlags, Arc)>, +} + +impl MemorySet { + /// Get the root page table token. + pub fn page_table_token(&self) -> usize { + self.page_table.root_paddr().as_usize() + } + + /// Create a new empty MemorySet. + pub fn new_empty() -> Self { + Self { + page_table: PageTable::try_new().expect("Error allocating page table."), + owned_mem: BTreeMap::new(), + private_mem: BTreeMap::new(), + attached_mem: Vec::new(), + } + } + + /// Create a new MemorySet + pub fn new_memory_set() -> Self { + if cfg!(target_arch = "aarch64") { + Self::new_empty() + } else { + Self::new_with_kernel_mapped() + } + } + + /// Create a new MemorySet with kernel mapped regions. + fn new_with_kernel_mapped() -> Self { + let mut page_table = PageTable::try_new().expect("Error allocating page table."); + + for r in memory_regions() { + debug!( + "mapping kernel region [0x{:x}, 0x{:x})", + usize::from(phys_to_virt(r.paddr)), + usize::from(phys_to_virt(r.paddr)) + r.size, + ); + page_table + .map_region(phys_to_virt(r.paddr), r.paddr, r.size, r.flags.into(), true) + .expect("Error mapping kernel memory"); + } + + Self { + page_table, + owned_mem: BTreeMap::new(), + private_mem: BTreeMap::new(), + attached_mem: Vec::new(), + } + } + + /// The root page table physical address. + pub fn page_table_root_ppn(&self) -> PhysAddr { + self.page_table.root_paddr() + } + + /// The max virtual address of the areas in this memory set. + pub fn max_va(&self) -> VirtAddr { + self.owned_mem + .last_key_value() + .map(|(_, area)| area.end_va()) + .unwrap_or_default() + } + + /// Allocate contiguous region. If no data, it will create a lazy load region. + pub fn new_region( + &mut self, + vaddr: VirtAddr, + size: usize, + shared: bool, + flags: MappingFlags, + data: Option<&[u8]>, + backend: Option, + ) { + let num_pages = (size + PAGE_SIZE_4K - 1) / PAGE_SIZE_4K; + + let mut area = match data { + Some(data) => MapArea::new_alloc( + vaddr, + num_pages, + flags, + Some(data), + backend, + &mut self.page_table, + ) + .unwrap(), + // None => match backend { + // Some(backend) => { + // MapArea::new_lazy(vaddr, num_pages, flags, Some(backend), &mut self.page_table) + // } + // None => { + // MapArea::new_alloc(vaddr, num_pages, flags, None, None, &mut self.page_table) + // .unwrap() + // } + // }, + None => MapArea::new_lazy(vaddr, num_pages, flags, backend, &mut self.page_table), + }; + + debug!( + "allocating [0x{:x}, 0x{:x}) to [0x{:x}, 0x{:x}) flag: {:?}", + usize::from(vaddr), + usize::from(vaddr) + size, + usize::from(area.vaddr), + usize::from(area.vaddr) + area.size(), + flags + ); + + if shared { + debug!("The area is shared."); + area.set_shared(shared); + } + + // self.owned_mem.insert(area.vaddr.into(), area); + assert!(self.owned_mem.insert(area.vaddr.into(), area).is_none()); + } + + /// Make [start, end) unmapped and dealloced. You need to flush TLB after this. + /// + /// NOTE: modified map area will have the same PhysAddr. + pub fn split_for_area(&mut self, start: VirtAddr, size: usize) { + let end = start + size; + assert!(end.is_aligned_4k()); + + // Note: Some areas will have to shrink its left part, so its key in BTree (start vaddr) have to change. + // We get all the overlapped areas out first. + + // UPDATE: draif_filter is an unstable feature, so we implement it manually. + let mut overlapped_area: Vec<(usize, MapArea)> = Vec::new(); + + let mut prev_area: BTreeMap = BTreeMap::new(); + + for _ in 0..self.owned_mem.len() { + let (idx, area) = self.owned_mem.pop_first().unwrap(); + if area.overlap_with(start, end) { + overlapped_area.push((idx, area)); + } else { + prev_area.insert(idx, area); + } + } + + self.owned_mem = prev_area; + + info!("splitting for [{:?}, {:?})", start, end); + + // Modify areas and insert it back to BTree. + for (_, mut area) in overlapped_area { + if area.contained_in(start, end) { + info!(" drop [{:?}, {:?})", area.vaddr, area.end_va()); + area.dealloc(&mut self.page_table); + // drop area + drop(area); + } else if area.strict_contain(start, end) { + info!( + " split [{:?}, {:?}) into 2 areas", + area.vaddr, + area.end_va() + ); + let new_area = area.remove_mid(start, end, &mut self.page_table); + + assert!(self + .owned_mem + .insert(new_area.vaddr.into(), new_area) + .is_none()); + assert!(self.owned_mem.insert(area.vaddr.into(), area).is_none()); + } else if start <= area.vaddr && area.vaddr < end { + info!( + " shrink_left [{:?}, {:?}) to [{:?}, {:?})", + area.vaddr, + area.end_va(), + end, + area.end_va() + ); + area.shrink_left(end, &mut self.page_table); + + assert!(self.owned_mem.insert(area.vaddr.into(), area).is_none()); + } else { + info!( + " shrink_right [{:?}, {:?}) to [{:?}, {:?})", + area.vaddr, + area.end_va(), + area.vaddr, + start + ); + area.shrink_right(start, &mut self.page_table); + + assert!(self.owned_mem.insert(area.vaddr.into(), area).is_none()); + } + } + } + + /// Find a free area with given start virtual address and size. Return the start address of the area. + pub fn find_free_area(&self, hint: VirtAddr, size: usize) -> Option { + let mut last_end = hint.max(axconfig::USER_MEMORY_START.into()).as_usize(); + + // TODO: performance optimization + let mut segments: Vec<_> = self + .owned_mem + .iter() + .map(|(start, mem)| (*start, *start + mem.size())) + .collect(); + segments.extend( + self.attached_mem + .iter() + .map(|(start, _, mem)| (start.as_usize(), start.as_usize() + mem.size())), + ); + + segments.sort(); + + for (start, end) in segments { + if last_end + size <= start { + return Some(last_end.into()); + } + last_end = end; + } + + None + } + + /// mmap. You need to flush tlb after this. + pub fn mmap( + &mut self, + start: VirtAddr, + size: usize, + flags: MappingFlags, + shared: bool, + fixed: bool, + backend: Option, + ) -> AxResult { + // align up to 4k + let size = (size + PAGE_SIZE_4K - 1) / PAGE_SIZE_4K * PAGE_SIZE_4K; + + info!( + "[mmap] vaddr: [{:?}, {:?}), {:?}, shared: {}, fixed: {}, backend: {}", + start, + start + size, + flags, + shared, + fixed, + backend.is_some() + ); + + if fixed { + self.split_for_area(start, size); + + self.new_region(start, size, shared, flags, None, backend); + + axhal::arch::flush_tlb(None); + + Ok(start.as_usize()) + } else { + info!("find free area"); + let start = self.find_free_area(start, size); + + match start { + Some(start) => { + info!("found area [{:?}, {:?})", start, start + size); + self.new_region(start, size, shared, flags, None, backend); + flush_tlb(None); + Ok(start.as_usize()) + } + None => Err(AxError::NoMemory), + } + } + } + + /// munmap. You need to flush TLB after this. + pub fn munmap(&mut self, start: VirtAddr, size: usize) { + // align up to 4k + let size = (size + PAGE_SIZE_4K - 1) / PAGE_SIZE_4K * PAGE_SIZE_4K; + info!("[munmap] [{:?}, {:?})", start, (start + size).align_up_4k()); + + self.split_for_area(start, size); + } + + /// msync + pub fn msync(&mut self, start: VirtAddr, size: usize) { + let end = start + size; + for area in self.owned_mem.values_mut() { + if area.backend.is_none() { + continue; + } + if area.overlap_with(start, end) { + for page_index in 0..area.pages.len() { + let page_vaddr = area.vaddr + page_index * PAGE_SIZE_4K; + + if page_vaddr >= start && page_vaddr < end { + area.sync_page_with_backend(page_index); + } + } + } + } + } + + /// Edit the page table to update flags in given virt address segment. You need to flush TLB + /// after calling this function. + /// + /// NOTE: It's possible that this function will break map areas into two for different mapping + /// flag settings. + pub fn mprotect(&mut self, start: VirtAddr, size: usize, flags: MappingFlags) { + info!( + "[mprotect] addr: [{:?}, {:?}), flags: {:?}", + start, + start + size, + flags + ); + let end = start + size; + assert!(end.is_aligned_4k()); + + flush_tlb(None); + //self.manual_alloc_range_for_lazy(start, end - 1).unwrap(); + // NOTE: There will be new areas but all old aree's start address won't change. But we + // can't iterating through `value_mut()` while `insert()` to BTree at the same time, so we + // `drain_filter()` out the overlapped areas first. + let mut overlapped_area: Vec<(usize, MapArea)> = Vec::new(); + let mut prev_area: BTreeMap = BTreeMap::new(); + + for _ in 0..self.owned_mem.len() { + let (idx, area) = self.owned_mem.pop_first().unwrap(); + if area.overlap_with(start, end) { + overlapped_area.push((idx, area)); + } else { + prev_area.insert(idx, area); + } + } + + self.owned_mem = prev_area; + + for (_, mut area) in overlapped_area { + if area.contained_in(start, end) { + // update whole area + area.update_flags(flags, &mut self.page_table); + } else if area.strict_contain(start, end) { + // split into 3 areas, update the middle one + let (mut mid, right) = area.split3(start, end); + mid.update_flags(flags, &mut self.page_table); + + assert!(self.owned_mem.insert(mid.vaddr.into(), mid).is_none()); + assert!(self.owned_mem.insert(right.vaddr.into(), right).is_none()); + } else if start <= area.vaddr && area.vaddr < end { + // split into 2 areas, update the left one + let right = area.split(end); + area.update_flags(flags, &mut self.page_table); + + assert!(self.owned_mem.insert(right.vaddr.into(), right).is_none()); + } else { + // split into 2 areas, update the right one + let mut right = area.split(start); + right.update_flags(flags, &mut self.page_table); + + assert!(self.owned_mem.insert(right.vaddr.into(), right).is_none()); + } + + assert!(self.owned_mem.insert(area.vaddr.into(), area).is_none()); + } + axhal::arch::flush_tlb(None); + } + + /// It will map newly allocated page in the page table. You need to flush TLB after this. + pub fn handle_page_fault(&mut self, addr: VirtAddr, flags: MappingFlags) -> AxResult<()> { + match self + .owned_mem + .values_mut() + .find(|area| area.vaddr <= addr && addr < area.end_va()) + { + Some(area) => { + if !area.handle_page_fault(addr, flags, &mut self.page_table) { + return Err(AxError::BadAddress); + } + Ok(()) + } + None => { + error!("Page fault address {:?} not found in memory set ", addr); + Err(AxError::BadAddress) + } + } + } + + /// 将用户分配的页面从页表中直接解映射,内核分配的页面依然保留 + pub fn unmap_user_areas(&mut self) { + for (_, area) in self.owned_mem.iter_mut() { + area.dealloc(&mut self.page_table); + } + self.owned_mem.clear(); + } + + /// Query the page table to get the physical address, flags and page size of the given virtual + pub fn query(&self, vaddr: VirtAddr) -> AxResult<(PhysAddr, MappingFlags, PageSize)> { + if let Ok((paddr, flags, size)) = self.page_table.query(vaddr) { + Ok((paddr, flags, size)) + } else { + Err(AxError::InvalidInput) + } + } + + /// Map a 4K region without allocating physical memory. + pub fn map_page_without_alloc( + &mut self, + vaddr: VirtAddr, + paddr: PhysAddr, + flags: MappingFlags, + ) -> AxResult<()> { + self.page_table + .map_region(vaddr, paddr, PAGE_SIZE_4K, flags, false) + .map_err(|_| AxError::InvalidInput) + } + + /// Create a new SharedMem with given key. + /// You need to add the returned SharedMem to global SHARED_MEMS or process's private_mem. + /// + /// Panics: SharedMem with the key already exist. + pub fn create_shared_mem( + key: i32, + size: usize, + pid: u64, + uid: u32, + gid: u32, + mode: u16, + ) -> AxResult<(i32, SharedMem)> { + let mut key_map = KEY_TO_SHMID.lock(); + + let shmid = SHMID.fetch_add(1, Ordering::Release); + key_map.insert(key, shmid); + + let mem = SharedMem::try_new(key, size, pid, uid, gid, mode)?; + + Ok((shmid, mem)) + } + + /// Panics: shmid is already taken. + pub fn add_shared_mem(shmid: i32, mem: SharedMem) { + let mut mem_map = SHARED_MEMS.lock(); + + assert!(mem_map.insert(shmid, Arc::new(mem)).is_none()); + } + + /// Panics: shmid is already taken in the process. + pub fn add_private_shared_mem(&mut self, shmid: i32, mem: SharedMem) { + assert!(self.private_mem.insert(shmid, Arc::new(mem)).is_none()); + } + + /// Get a SharedMem by shmid. + pub fn get_shared_mem(shmid: i32) -> Option> { + SHARED_MEMS.lock().get(&shmid).cloned() + } + + /// Get a private SharedMem by shmid. + pub fn get_private_shared_mem(&self, shmid: i32) -> Option> { + self.private_mem.get(&shmid).cloned() + } + + /// Attach a SharedMem to the memory set. + pub fn attach_shared_mem(&mut self, mem: Arc, addr: VirtAddr, flags: MappingFlags) { + self.page_table + .map_region(addr, mem.paddr(), mem.size(), flags, false) + .unwrap(); + + self.attached_mem.push((addr, flags, mem)); + } + + /// Detach a SharedMem from the memory set. + /// + /// TODO: implement this + pub fn detach_shared_mem(&mut self, _shmid: i32) { + todo!() + } + + /// mremap: change the size of a mapping, potentially moving it at the same time. + pub fn mremap(&mut self, old_start: VirtAddr, old_size: usize, new_size: usize) -> isize { + info!( + "[mremap] old_start: {:?}, old_size: {:?}), new_size: {:?}", + old_start, old_size, new_size + ); + + // Todo: check flags + let start = self.find_free_area(old_start, new_size); + if start.is_none() { + return -1; + } + + let old_page_start_addr = old_start.align_down_4k(); + let old_page_end_addr = old_start + old_size - 1; + let old_page_end = old_page_end_addr.align_down_4k().into(); + let old_page_start: usize = old_page_start_addr.into(); + + let addr: isize = match start { + Some(start) => { + info!("found area [{:?}, {:?})", start, start + new_size); + + self.new_region( + start, + new_size, + false, + MappingFlags::USER | MappingFlags::READ | MappingFlags::WRITE, + None, + None, + ); + flush_tlb(None); + + let end = start + new_size; + assert!(end.is_aligned_4k()); + + for addr in (old_page_start..=old_page_end).step_by(PAGE_SIZE_4K) { + let vaddr = VirtAddr::from(addr); + match check_page_table_entry_validity(vaddr, &self.page_table) { + Ok(_) => { + // 如果旧地址已经分配内存,进行页copy;否则不做处理 + let page_start = start + addr - old_page_start; + // let page_end = page_start + PAGE_SIZE_4K - 1; + if self.manual_alloc_for_lazy(page_start).is_ok() { + let old_data = unsafe { + core::slice::from_raw_parts(vaddr.as_ptr(), PAGE_SIZE_4K) + }; + let new_data = unsafe { + core::slice::from_raw_parts_mut( + page_start.as_mut_ptr(), + PAGE_SIZE_4K, + ) + }; + new_data[..PAGE_SIZE_4K].copy_from_slice(old_data); + } + } + Err(PagingError::NotMapped) => { + error!("NotMapped addr: {:x}", vaddr); + continue; + } + _ => return -1, + }; + } + self.munmap(old_start, old_size); + flush_tlb(None); + start.as_usize() as isize + } + None => -1, + }; + + debug!("[mremap] return addr: 0x{:x}", addr); + addr + } +} + +impl MemorySet { + /// 判断某一个虚拟地址是否在内存集中。 + /// 若当前虚拟地址在内存集中,且对应的是lazy分配,暂未分配物理页的情况下, + /// 则为其分配物理页面。 + /// + /// 若不在内存集中,则返回None。 + /// + /// 若在内存集中,且已经分配了物理页面,则不做处理。 + pub fn manual_alloc_for_lazy(&mut self, addr: VirtAddr) -> AxResult<()> { + if let Some((_, area)) = self + .owned_mem + .iter_mut() + .find(|(_, area)| area.vaddr <= addr && addr < area.end_va()) + { + match check_page_table_entry_validity(addr, &self.page_table) { + Err(PagingError::NoMemory) => Err(AxError::InvalidInput), + Err(PagingError::NotMapped) => { + // 若未分配物理页面,则手动为其分配一个页面,写入到对应页表中 + let entry = self.page_table.get_entry_mut(addr).unwrap().0; + + if !area.handle_page_fault(addr, entry.flags(), &mut self.page_table) { + return Err(AxError::BadAddress); + } + Ok(()) + } + _ => Ok(()), + } + } else { + Err(AxError::InvalidInput) + } + } + /// 暴力实现区间强制分配 + /// 传入区间左闭右闭 + pub fn manual_alloc_range_for_lazy(&mut self, start: VirtAddr, end: VirtAddr) -> AxResult<()> { + if start > end { + return Err(AxError::InvalidInput); + } + let start: usize = start.align_down_4k().into(); + let end: usize = end.align_down_4k().into(); + for addr in (start..=end).step_by(PAGE_SIZE_4K) { + // 逐页访问,主打暴力 + debug!("allocating page at {:x}", addr); + self.manual_alloc_for_lazy(addr.into())?; + } + Ok(()) + } + /// 判断某一个类型的某一个对象是否被分配 + pub fn manual_alloc_type_for_lazy(&mut self, obj: *const T) -> AxResult<()> { + let start = obj as usize; + let end = start + core::mem::size_of::() - 1; + self.manual_alloc_range_for_lazy(start.into(), end.into()) + } +} + +impl MemorySet { + /// Clone the MemorySet. This will create a new page table and map all the regions in the old + /// page table to the new one. + /// + /// If it occurs error, the new MemorySet will be dropped and return the error. + pub fn clone_or_err(&mut self) -> AxResult { + let mut page_table = PageTable::try_new().expect("Error allocating page table."); + + for r in memory_regions() { + debug!( + "mapping kernel region [0x{:x}, 0x{:x})", + usize::from(phys_to_virt(r.paddr)), + usize::from(phys_to_virt(r.paddr)) + r.size, + ); + page_table + .map_region(phys_to_virt(r.paddr), r.paddr, r.size, r.flags.into(), true) + .expect("Error mapping kernel memory"); + } + let mut owned_mem: BTreeMap = BTreeMap::new(); + for (vaddr, area) in self.owned_mem.iter_mut() { + info!("vaddr: {:X?}, new_area: {:X?}", vaddr, area.vaddr); + match area.clone_alloc(&mut page_table, &mut self.page_table) { + Ok(new_area) => { + info!("new area: {:X?}", new_area.vaddr); + owned_mem.insert(*vaddr, new_area); + Ok(()) + } + Err(err) => Err(err), + }?; + } + + let mut new_memory = Self { + page_table, + owned_mem, + + private_mem: self.private_mem.clone(), + attached_mem: Vec::new(), + }; + + for (addr, flags, mem) in &self.attached_mem { + new_memory.attach_shared_mem(mem.clone(), *addr, *flags); + } + + Ok(new_memory) + } +} + +impl Drop for MemorySet { + fn drop(&mut self) { + self.unmap_user_areas(); + } +} + +/// 验证地址是否已分配页面 +pub fn check_page_table_entry_validity( + addr: VirtAddr, + page_table: &PageTable, +) -> Result<(), PagingError> { + let entry = page_table.get_entry_mut(addr); + + if entry.is_err() { + // 地址不合法 + return Err(PagingError::NoMemory); + } + + let entry = entry.unwrap().0; + if !entry.is_present() { + return Err(PagingError::NotMapped); + } + + Ok(()) +} diff --git a/modules/axmem/src/shared.rs b/modules/axmem/src/shared.rs new file mode 100644 index 0000000..584e627 --- /dev/null +++ b/modules/axmem/src/shared.rs @@ -0,0 +1,95 @@ +use axalloc::GlobalPage; +use axerrno::AxResult; +use axhal::{ + mem::{virt_to_phys, PhysAddr, PAGE_SIZE_4K}, + time::current_time, +}; + +#[allow(dead_code)] +pub struct SharedMem { + pages: GlobalPage, + /// The information of the shared memory. + pub info: SharedMemInfo, +} + +impl SharedMem { + /// Allocate a new shared memory. + /// + /// If the allocation fails, return an error. + pub fn try_new( + key: i32, + size: usize, + pid: u64, + uid: u32, + gid: u32, + mode: u16, + ) -> AxResult { + let num_pages = (size + PAGE_SIZE_4K - 1) / PAGE_SIZE_4K; + + let pages = GlobalPage::alloc_contiguous(num_pages, PAGE_SIZE_4K)?; + let size = pages.size(); + + Ok(Self { + pages, + info: SharedMemInfo::new(key, size, pid, uid, gid, mode), + }) + } + + /// Return the size of the shared memory. + pub fn size(&self) -> usize { + self.pages.size() + } + + /// Return the start physical address of the shared memory. + pub fn paddr(&self) -> PhysAddr { + self.pages.start_paddr(virt_to_phys) + } +} + +#[allow(dead_code)] +pub struct SharedMemInfo { + perm: SharedMemPermInfo, + size: usize, + + a_time: usize, + d_time: usize, + c_time: usize, + + c_pid: u64, + l_pid: u64, +} + +#[allow(dead_code)] +pub struct SharedMemPermInfo { + key: i32, + uid: u32, + gid: u32, + cuid: u32, + cgid: u32, + mode: u16, +} + +impl SharedMemInfo { + /// Allocate a new SharedMem. + /// + /// This function should be called by SharedMem::try_new(). + fn new(key: i32, size: usize, pid: u64, uid: u32, gid: u32, mode: u16) -> Self { + Self { + perm: SharedMemPermInfo { + key, + uid, + gid, + cuid: uid, + cgid: gid, + mode, + }, + size, + a_time: 0, + d_time: 0, + c_time: current_time().as_secs() as usize, + + c_pid: pid, + l_pid: 0, + } + } +} diff --git a/modules/axnet/.gitignore b/modules/axnet/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/modules/axnet/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/modules/axnet/Cargo.toml b/modules/axnet/Cargo.toml new file mode 100644 index 0000000..d3ccb1e --- /dev/null +++ b/modules/axnet/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "axnet" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia ", "ChengXiang Qi "] +description = "ArceOS network module" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axnet" +documentation = "https://rcore-os.github.io/arceos/axnet/index.html" +keywords = ["Starry"] + +[features] +monolithic = ["dep:axprocess"] + +smoltcp = [] + +# 启用ip协议与否 +ip = [] + +default = ["smoltcp"] + +[dependencies] +log = "0.4" +cfg-if = "1.0" +spin = "0.9" +driver_net = { git = "https://github.com/Starry-OS/driver_net.git" } +lazy_init = { git = "https://github.com/Starry-OS/lazy_init.git" } +axerrno = { git = "https://github.com/Starry-OS/axerrno.git" } +axhal = { workspace = true } +axsync = { workspace = true } +axtask = { workspace = true } +axdriver = { workspace = true, features = ["net"] } +axio = { git = "https://github.com/Starry-OS/axio.git" } +axprocess = { workspace = true, optional = true } + +[dependencies.smoltcp] +git = "https://github.com/rcore-os/smoltcp.git" +rev = "8bf9a9a" +default-features = false +features = [ + "alloc", "log", # no std + "medium-ethernet", + "medium-ip", + "proto-ipv4", + "proto-ipv6", + "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", "socket-dns", "proto-igmp", + # "fragmentation-buffer-size-65536", "proto-ipv4-fragmentation", + # "reassembly-buffer-size-65536", "reassembly-buffer-count-32", + # "assembler-max-segment-count-32", +] diff --git a/modules/axnet/src/lib.rs b/modules/axnet/src/lib.rs new file mode 100644 index 0000000..b027459 --- /dev/null +++ b/modules/axnet/src/lib.rs @@ -0,0 +1,52 @@ +//! [ArceOS](https://github.com/rcore-os/arceos) network module. +//! +//! It provides unified networking primitives for TCP/UDP communication +//! using various underlying network stacks. Currently, only [smoltcp] is +//! supported. +//! +//! # Organization +//! +//! - [`TcpSocket`]: A TCP socket that provides POSIX-like APIs. +//! - [`UdpSocket`]: A UDP socket that provides POSIX-like APIs. +//! - [`dns_query`]: Function for DNS query. +//! +//! # Cargo Features +//! +//! - `smoltcp`: Use [smoltcp] as the underlying network stack. This is enabled +//! by default. +//! +//! [smoltcp]: https://github.com/smoltcp-rs/smoltcp + +#![no_std] +#![feature(new_uninit)] + +#[macro_use] +extern crate log; +extern crate alloc; + +cfg_if::cfg_if! { + if #[cfg(feature = "smoltcp")] { + mod smoltcp_impl; + use smoltcp_impl as net_impl; + } +} + +pub use self::net_impl::TcpSocket; +pub use self::net_impl::UdpSocket; +pub use self::net_impl::{ + add_membership, dns_query, from_core_sockaddr, into_core_sockaddr, poll_interfaces, +}; +pub use self::net_impl::{bench_receive, bench_transmit}; +pub use smoltcp::time::Duration; +pub use smoltcp::wire::{IpAddress as IpAddr, IpEndpoint as SocketAddr, Ipv4Address as Ipv4Addr, Ipv6Address as Ipv6Addr}; + +use axdriver::{prelude::*, AxDeviceContainer}; + +/// Initializes the network subsystem by NIC devices. +pub fn init_network(mut net_devs: AxDeviceContainer) { + info!("Initialize network subsystem..."); + + let dev = net_devs.take_one().expect("No NIC device found!"); + info!(" use NIC 0: {:?}", dev.device_name()); + net_impl::init(dev); +} diff --git a/modules/axnet/src/smoltcp_impl/addr.rs b/modules/axnet/src/smoltcp_impl/addr.rs new file mode 100644 index 0000000..2cd0b30 --- /dev/null +++ b/modules/axnet/src/smoltcp_impl/addr.rs @@ -0,0 +1,36 @@ +use core::net::{IpAddr, SocketAddr}; +use smoltcp::wire::{IpAddress, IpEndpoint, Ipv4Address, Ipv6Address}; + +pub const fn from_core_ipaddr(ip: IpAddr) -> IpAddress { + match ip { + IpAddr::V4(ipv4) => IpAddress::Ipv4(Ipv4Address(ipv4.octets())), + IpAddr::V6(ipv6) => IpAddress::Ipv6(Ipv6Address(ipv6.octets())), + } +} + +pub const fn into_core_ipaddr(ip: IpAddress) -> IpAddr { + match ip { + IpAddress::Ipv4(ipv4) => IpAddr::V4(unsafe { core::mem::transmute(ipv4.0) }), + IpAddress::Ipv6(ipv6) => IpAddr::V6(unsafe { core::mem::transmute(ipv6.0) }), + } +} + +/// Convert from `std::net::SocketAddr` to `smoltcp::wire::IpEndpoint`. +pub const fn from_core_sockaddr(addr: SocketAddr) -> IpEndpoint { + IpEndpoint { + addr: from_core_ipaddr(addr.ip()), + port: addr.port(), + } +} + +/// Convert from `smoltcp::wire::IpEndpoint` to `std::net::SocketAddr`. +pub const fn into_core_sockaddr(addr: IpEndpoint) -> SocketAddr { + SocketAddr::new(into_core_ipaddr(addr.addr), addr.port) +} + +pub fn is_unspecified(ip: IpAddress) -> bool { + ip.as_bytes() == [0, 0, 0, 0] +} + +pub const UNSPECIFIED_IP: IpAddress = IpAddress::v4(0, 0, 0, 0); +pub const UNSPECIFIED_ENDPOINT: IpEndpoint = IpEndpoint::new(UNSPECIFIED_IP, 0); diff --git a/modules/axnet/src/smoltcp_impl/bench.rs b/modules/axnet/src/smoltcp_impl/bench.rs new file mode 100644 index 0000000..8e9749d --- /dev/null +++ b/modules/axnet/src/smoltcp_impl/bench.rs @@ -0,0 +1,75 @@ +use super::{AxNetRxToken, AxNetTxToken, STANDARD_MTU}; +use super::{DeviceWrapper, InterfaceWrapper}; +use smoltcp::phy::{Device, RxToken, TxToken}; + +const GB: usize = 1000 * MB; +const MB: usize = 1000 * KB; +const KB: usize = 1000; + +#[allow(unused)] +impl DeviceWrapper { + pub fn bench_transmit_bandwidth(&mut self) { + // 10 Gb + const MAX_SEND_BYTES: usize = 10 * GB; + let mut send_bytes: usize = 0; + let mut past_send_bytes: usize = 0; + let mut past_time = InterfaceWrapper::current_time(); + + // Send bytes + while send_bytes < MAX_SEND_BYTES { + if let Some(tx_token) = self.transmit(InterfaceWrapper::current_time()) { + AxNetTxToken::consume(tx_token, STANDARD_MTU, |tx_buf| { + tx_buf[0..12].fill(1); + // ether type: IPv4 + tx_buf[12..14].copy_from_slice(&[0x08, 0x00]); + tx_buf[14..STANDARD_MTU].fill(1); + }); + send_bytes += STANDARD_MTU; + } + + let current_time = InterfaceWrapper::current_time(); + if (current_time - past_time).secs() == 1 { + let gb = ((send_bytes - past_send_bytes) * 8) / GB; + let mb = (((send_bytes - past_send_bytes) * 8) % GB) / MB; + let gib = (send_bytes - past_send_bytes) / GB; + let mib = ((send_bytes - past_send_bytes) % GB) / MB; + info!( + "Transmit: {}.{:03}GBytes, Bandwidth: {}.{:03}Gbits/sec.", + gib, mib, gb, mb + ); + past_time = current_time; + past_send_bytes = send_bytes; + } + } + } + + pub fn bench_receive_bandwidth(&mut self) { + // 10 Gb + const MAX_RECEIVE_BYTES: usize = 10 * GB; + let mut receive_bytes: usize = 0; + let mut past_receive_bytes: usize = 0; + let mut past_time = InterfaceWrapper::current_time(); + // Receive bytes + while receive_bytes < MAX_RECEIVE_BYTES { + if let Some(rx_token) = self.receive(InterfaceWrapper::current_time()) { + AxNetRxToken::consume(rx_token.0, |rx_buf| { + receive_bytes += rx_buf.len(); + }); + } + + let current_time = InterfaceWrapper::current_time(); + if (current_time - past_time).secs() == 1 { + let gb = ((receive_bytes - past_receive_bytes) * 8) / GB; + let mb = (((receive_bytes - past_receive_bytes) * 8) % GB) / MB; + let gib = (receive_bytes - past_receive_bytes) / GB; + let mib = ((receive_bytes - past_receive_bytes) % GB) / MB; + info!( + "Receive: {}.{:03}GBytes, Bandwidth: {}.{:03}Gbits/sec.", + gib, mib, gb, mb + ); + past_time = current_time; + past_receive_bytes = receive_bytes; + } + } + } +} diff --git a/modules/axnet/src/smoltcp_impl/dns.rs b/modules/axnet/src/smoltcp_impl/dns.rs new file mode 100644 index 0000000..f4321f7 --- /dev/null +++ b/modules/axnet/src/smoltcp_impl/dns.rs @@ -0,0 +1,91 @@ +use alloc::vec::Vec; +use axerrno::{ax_err_type, AxError, AxResult}; +use core::net::IpAddr; + +use smoltcp::iface::SocketHandle; +use smoltcp::socket::dns::{self, GetQueryResultError, StartQueryError}; +use smoltcp::wire::DnsQueryType; + +use super::addr::into_core_ipaddr; +use super::{SocketSetWrapper, SOCKET_SET}; + +/// A DNS socket. +struct DnsSocket { + handle: Option, +} + +impl DnsSocket { + #[allow(clippy::new_without_default)] + /// Creates a new DNS socket. + pub fn new() -> Self { + let socket = SocketSetWrapper::new_dns_socket(); + let handle = Some(SOCKET_SET.add(socket)); + Self { handle } + } + + #[allow(dead_code)] + /// Update the list of DNS servers, will replace all existing servers. + pub fn update_servers(self, servers: &[smoltcp::wire::IpAddress]) { + SOCKET_SET.with_socket_mut::(self.handle.unwrap(), |socket| { + socket.update_servers(servers) + }); + } + + /// Query a address with given DNS query type. + pub fn query(&self, name: &str, query_type: DnsQueryType) -> AxResult> { + // let local_addr = self.local_addr.unwrap_or_else(f); + let handle = self.handle.ok_or_else(|| ax_err_type!(InvalidInput))?; + + let iface = &super::ETH0.iface; + let query_handle = SOCKET_SET + .with_socket_mut::(handle, |socket| { + socket.start_query(iface.lock().context(), name, query_type) + }) + .map_err(|e| match e { + StartQueryError::NoFreeSlot => { + ax_err_type!(ResourceBusy, "socket query() failed: no free slot") + } + StartQueryError::InvalidName => { + ax_err_type!(InvalidInput, "socket query() failed: invalid name") + } + StartQueryError::NameTooLong => { + ax_err_type!(InvalidInput, "socket query() failed: too long name") + } + })?; + loop { + SOCKET_SET.poll_interfaces(); + match SOCKET_SET.with_socket_mut::(handle, |socket| { + socket.get_query_result(query_handle).map_err(|e| match e { + GetQueryResultError::Pending => AxError::WouldBlock, + GetQueryResultError::Failed => { + ax_err_type!(ConnectionRefused, "socket query() failed") + } + }) + }) { + Ok(n) => { + let mut res = Vec::with_capacity(n.capacity()); + for ip in n { + res.push(into_core_ipaddr(ip)) + } + return Ok(res); + } + Err(AxError::WouldBlock) => axtask::yield_now(), + Err(e) => return Err(e), + } + } + } +} + +impl Drop for DnsSocket { + fn drop(&mut self) { + if let Some(handle) = self.handle { + SOCKET_SET.remove(handle); + } + } +} + +/// Public function for DNS query. +pub fn dns_query(name: &str) -> AxResult> { + let socket = DnsSocket::new(); + socket.query(name, DnsQueryType::A) +} diff --git a/modules/axnet/src/smoltcp_impl/listen_table.rs b/modules/axnet/src/smoltcp_impl/listen_table.rs new file mode 100644 index 0000000..f1f841a --- /dev/null +++ b/modules/axnet/src/smoltcp_impl/listen_table.rs @@ -0,0 +1,164 @@ +use alloc::{boxed::Box, collections::VecDeque}; +use core::ops::{Deref, DerefMut}; + +use axerrno::{ax_err, AxError, AxResult}; +use axsync::Mutex; +use smoltcp::iface::{SocketHandle, SocketSet}; +use smoltcp::socket::tcp::{self, State}; +use smoltcp::wire::{IpAddress, IpEndpoint, IpListenEndpoint}; + +use super::{SocketSetWrapper, LISTEN_QUEUE_SIZE, SOCKET_SET}; + +const PORT_NUM: usize = 65536; + +struct ListenTableEntry { + listen_endpoint: IpListenEndpoint, + syn_queue: VecDeque, +} + +impl ListenTableEntry { + pub fn new(listen_endpoint: IpListenEndpoint) -> Self { + Self { + listen_endpoint, + syn_queue: VecDeque::with_capacity(LISTEN_QUEUE_SIZE), + } + } + + #[inline] + fn can_accept(&self, dst: IpAddress) -> bool { + match self.listen_endpoint.addr { + Some(addr) => addr == dst, + None => true, + } + } +} + +impl Drop for ListenTableEntry { + fn drop(&mut self) { + for &handle in &self.syn_queue { + SOCKET_SET.remove(handle); + } + } +} + +pub struct ListenTable { + tcp: Box<[Mutex>>]>, +} + +impl ListenTable { + pub fn new() -> Self { + let tcp = unsafe { + let mut buf = Box::new_uninit_slice(PORT_NUM); + for i in 0..PORT_NUM { + buf[i].write(Mutex::new(None)); + } + buf.assume_init() + }; + Self { tcp } + } + + pub fn can_listen(&self, port: u16) -> bool { + self.tcp[port as usize].lock().is_none() + } + + pub fn listen(&self, listen_endpoint: IpListenEndpoint) -> AxResult { + let port = listen_endpoint.port; + assert_ne!(port, 0); + let mut entry = self.tcp[port as usize].lock(); + if entry.is_none() { + *entry = Some(Box::new(ListenTableEntry::new(listen_endpoint))); + Ok(()) + } else { + ax_err!(AddrInUse, "socket listen() failed") + } + } + + pub fn unlisten(&self, port: u16) { + debug!("TCP socket unlisten on {}", port); + *self.tcp[port as usize].lock() = None; + } + + pub fn can_accept(&self, port: u16) -> AxResult { + if let Some(entry) = self.tcp[port as usize].lock().deref() { + Ok(entry.syn_queue.iter().any(|&handle| is_connected(handle))) + } else { + ax_err!(InvalidInput, "socket accept() failed: not listen") + } + } + + pub fn accept(&self, port: u16) -> AxResult<(SocketHandle, (IpEndpoint, IpEndpoint))> { + if let Some(entry) = self.tcp[port as usize].lock().deref_mut() { + let syn_queue: &mut VecDeque = &mut entry.syn_queue; + let idx = syn_queue + .iter() + .enumerate() + .find_map(|(idx, &handle)| is_connected(handle).then(|| idx)) + .ok_or(AxError::WouldBlock)?; // wait for connection + if idx > 0 { + warn!( + "slow SYN queue enumeration: index = {}, len = {}!", + idx, + syn_queue.len() + ); + } + let handle = syn_queue.swap_remove_front(idx).unwrap(); + // If the connection is reset, return ConnectionReset error + // Otherwise, return the handle and the address tuple + if is_closed(handle) { + ax_err!(ConnectionReset, "socket accept() failed: connection reset") + } else { + Ok((handle, get_addr_tuple(handle))) + } + } else { + ax_err!(InvalidInput, "socket accept() failed: not listen") + } + } + + pub fn incoming_tcp_packet( + &self, + src: IpEndpoint, + dst: IpEndpoint, + sockets: &mut SocketSet<'_>, + ) { + if let Some(entry) = self.tcp[dst.port as usize].lock().deref_mut() { + if !entry.can_accept(dst.addr) { + // not listening on this address + return; + } + if entry.syn_queue.len() >= LISTEN_QUEUE_SIZE { + // SYN queue is full, drop the packet + warn!("SYN queue overflow!"); + return; + } + let mut socket = SocketSetWrapper::new_tcp_socket(); + if socket.listen(entry.listen_endpoint).is_ok() { + let handle = sockets.add(socket); + debug!( + "TCP socket {}: prepare for connection {} -> {}", + handle, src, entry.listen_endpoint + ); + entry.syn_queue.push_back(handle); + } + } + } +} + +fn is_connected(handle: SocketHandle) -> bool { + SOCKET_SET.with_socket::(handle, |socket| { + !matches!(socket.state(), State::Listen | State::SynReceived) + }) +} + +fn is_closed(handle: SocketHandle) -> bool { + SOCKET_SET + .with_socket::(handle, |socket| matches!(socket.state(), State::Closed)) +} + +fn get_addr_tuple(handle: SocketHandle) -> (IpEndpoint, IpEndpoint) { + SOCKET_SET.with_socket::(handle, |socket| { + ( + socket.local_endpoint().unwrap(), + socket.remote_endpoint().unwrap(), + ) + }) +} diff --git a/modules/axnet/src/smoltcp_impl/loopback.rs b/modules/axnet/src/smoltcp_impl/loopback.rs new file mode 100644 index 0000000..fea413d --- /dev/null +++ b/modules/axnet/src/smoltcp_impl/loopback.rs @@ -0,0 +1,104 @@ +use alloc::{collections::VecDeque, vec, vec::Vec}; +use smoltcp::{ + iface::SocketSet, + phy::{Device, DeviceCapabilities, Medium}, + time::Instant, +}; + +use crate::net_impl::LISTEN_TABLE; + +pub(crate) struct LoopbackDev { + pub(crate) queue: VecDeque>, + medium: Medium, +} + +impl LoopbackDev { + pub fn new(medium: Medium) -> Self { + Self { + queue: VecDeque::new(), + medium, + } + } +} + +fn snoop_tcp_from_ip(buffer: &[u8], sockets: &mut SocketSet) -> Result<(), smoltcp::wire::Error> { + use crate::SocketAddr; + use smoltcp::wire::{IpProtocol, Ipv4Packet, TcpPacket}; + + let ipv4_packet = Ipv4Packet::new_checked(buffer)?; + + if ipv4_packet.next_header() == IpProtocol::Tcp { + let tcp_packet = TcpPacket::new_checked(ipv4_packet.payload())?; + let src_addr = SocketAddr::new(ipv4_packet.src_addr().into(), tcp_packet.src_port()); + let dst_addr = SocketAddr::new(ipv4_packet.dst_addr().into(), tcp_packet.dst_port()); + let is_first = tcp_packet.syn() && !tcp_packet.ack(); + if is_first { + // create a socket for the first incoming TCP packet, as the later accept() returns. + LISTEN_TABLE.incoming_tcp_packet(src_addr, dst_addr, sockets); + } + } + Ok(()) +} + +pub(crate) struct RxTokenScoop { + buffer: Vec, +} + +pub(crate) struct TxToken<'a> { + queue: &'a mut VecDeque>, +} + +impl smoltcp::phy::RxToken for RxTokenScoop { + fn consume(mut self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + f(&mut self.buffer) + } + + fn preprocess(&self, sockets: &mut SocketSet<'_>) { + snoop_tcp_from_ip(&self.buffer, sockets).ok(); + } +} + +impl<'a> smoltcp::phy::TxToken for TxToken<'a> { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut buffer = vec![0; len]; + let result = f(&mut buffer); + self.queue.push_back(buffer); + result + } +} + +impl Device for LoopbackDev { + type RxToken<'a> = RxTokenScoop; + type TxToken<'a> = TxToken<'a>; + + fn capabilities(&self) -> DeviceCapabilities { + let mut cap = DeviceCapabilities::default(); + + cap.max_transmission_unit = 65535; + cap.medium = self.medium; + + cap + } + + fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + self.queue.pop_front().map(move |buffer| { + let rx = Self::RxToken { buffer }; + let tx = Self::TxToken { + queue: &mut self.queue, + }; + (rx, tx) + }) + } + + fn transmit(&mut self, _timestamp: Instant) -> Option> { + Some(TxToken { + queue: &mut self.queue, + }) + } +} diff --git a/modules/axnet/src/smoltcp_impl/mod.rs b/modules/axnet/src/smoltcp_impl/mod.rs new file mode 100644 index 0000000..2fdf49e --- /dev/null +++ b/modules/axnet/src/smoltcp_impl/mod.rs @@ -0,0 +1,393 @@ +mod addr; +mod bench; +mod dns; +mod listen_table; + +mod tcp; +mod udp; +use alloc::vec; +use axerrno::{AxError, AxResult}; +use core::cell::RefCell; +use core::ops::DerefMut; + +use axdriver::prelude::*; +use axhal::time::{current_time_nanos, NANOS_PER_MICROS}; +use axsync::Mutex; +use driver_net::{DevError, NetBufPtr}; +use lazy_init::LazyInit; +use smoltcp::iface::{Config, Interface, SocketHandle, SocketSet}; +use smoltcp::phy::{Device, DeviceCapabilities, Medium, RxToken, TxToken}; +use smoltcp::socket::{self, AnySocket, Socket}; +use smoltcp::time::Instant; +use smoltcp::wire::{EthernetAddress, HardwareAddress, IpAddress, IpCidr}; + +use self::listen_table::ListenTable; + +pub use self::dns::dns_query; +pub use self::tcp::TcpSocket; +pub use self::udp::UdpSocket; +pub use addr::{from_core_sockaddr, into_core_sockaddr}; +#[allow(unused)] +macro_rules! env_or_default { + ($key:literal) => { + match option_env!($key) { + Some(val) => val, + None => "", + } + }; +} + +const DNS_SEVER: &str = "8.8.8.8"; + +const RANDOM_SEED: u64 = 0xA2CE_05A2_CE05_A2CE; +const STANDARD_MTU: usize = 1500; +const TCP_RX_BUF_LEN: usize = 64 * 1024; +const TCP_TX_BUF_LEN: usize = 64 * 1024; +const UDP_RX_BUF_LEN: usize = 64 * 1024; +const UDP_TX_BUF_LEN: usize = 64 * 1024; +const LISTEN_QUEUE_SIZE: usize = 512; + +static LISTEN_TABLE: LazyInit = LazyInit::new(); +static SOCKET_SET: LazyInit = LazyInit::new(); + +mod loopback; +static LOOPBACK_DEV: LazyInit> = LazyInit::new(); +static LOOPBACK: LazyInit> = LazyInit::new(); +use self::loopback::LoopbackDev; + +const IP: &str = env_or_default!("AX_IP"); +const GATEWAY: &str = env_or_default!("AX_GW"); +const IP_PREFIX: u8 = 24; + +static ETH0: LazyInit = LazyInit::new(); + +struct SocketSetWrapper<'a>(Mutex>); + +struct DeviceWrapper { + inner: RefCell, // use `RefCell` is enough since it's wrapped in `Mutex` in `InterfaceWrapper`. +} + +struct InterfaceWrapper { + name: &'static str, + ether_addr: EthernetAddress, + dev: Mutex, + iface: Mutex, +} + +impl<'a> SocketSetWrapper<'a> { + fn new() -> Self { + Self(Mutex::new(SocketSet::new(vec![]))) + } + + pub fn new_tcp_socket() -> socket::tcp::Socket<'a> { + let tcp_rx_buffer = socket::tcp::SocketBuffer::new(vec![0; TCP_RX_BUF_LEN]); + let tcp_tx_buffer = socket::tcp::SocketBuffer::new(vec![0; TCP_TX_BUF_LEN]); + socket::tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer) + } + + pub fn new_udp_socket() -> socket::udp::Socket<'a> { + let udp_rx_buffer = socket::udp::PacketBuffer::new( + vec![socket::udp::PacketMetadata::EMPTY; 256], + vec![0; UDP_RX_BUF_LEN], + ); + let udp_tx_buffer = socket::udp::PacketBuffer::new( + vec![socket::udp::PacketMetadata::EMPTY; 256], + vec![0; UDP_TX_BUF_LEN], + ); + socket::udp::Socket::new(udp_rx_buffer, udp_tx_buffer) + } + + pub fn new_dns_socket() -> socket::dns::Socket<'a> { + let server_addr = DNS_SEVER.parse().expect("invalid DNS server address"); + socket::dns::Socket::new(&[server_addr], vec![]) + } + + pub fn add>(&self, socket: T) -> SocketHandle { + let handle = self.0.lock().add(socket); + debug!("socket {}: created", handle); + handle + } + + pub fn with_socket, R, F>(&self, handle: SocketHandle, f: F) -> R + where + F: FnOnce(&T) -> R, + { + let set = self.0.lock(); + let socket = set.get(handle); + f(socket) + } + + pub fn with_socket_mut, R, F>(&self, handle: SocketHandle, f: F) -> R + where + F: FnOnce(&mut T) -> R, + { + let mut set = self.0.lock(); + let socket = set.get_mut(handle); + f(socket) + } + + pub fn bind_check(&self, addr: IpAddress, _port: u16) -> AxResult { + let mut sockets = self.0.lock(); + for item in sockets.iter_mut() { + match item.1 { + Socket::Tcp(s) => { + let local_addr = s.get_bound_endpoint(); + if local_addr.addr == Some(addr) { + return Err(AxError::AddrInUse); + } + } + Socket::Udp(s) => { + if s.endpoint().addr == Some(addr) { + return Err(AxError::AddrInUse); + } + } + _ => continue, + }; + } + Ok(()) + } + + pub fn poll_interfaces(&self) { + #[cfg(feature = "monolithic")] + LOOPBACK.lock().poll( + Instant::from_micros_const((current_time_nanos() / NANOS_PER_MICROS) as i64), + LOOPBACK_DEV.lock().deref_mut(), + &mut self.0.lock(), + ); + + ETH0.poll(&self.0); + } + + pub fn remove(&self, handle: SocketHandle) { + self.0.lock().remove(handle); + debug!("socket {}: destroyed", handle); + } +} + +#[allow(unused)] +impl InterfaceWrapper { + fn new(name: &'static str, dev: AxNetDevice, ether_addr: EthernetAddress) -> Self { + let mut config = Config::new(HardwareAddress::Ethernet(ether_addr)); + config.random_seed = RANDOM_SEED; + + let mut dev = DeviceWrapper::new(dev); + let iface = Mutex::new(Interface::new(config, &mut dev, Self::current_time())); + Self { + name, + ether_addr, + dev: Mutex::new(dev), + iface, + } + } + + fn current_time() -> Instant { + Instant::from_micros_const((current_time_nanos() / NANOS_PER_MICROS) as i64) + } + + pub fn name(&self) -> &str { + self.name + } + + pub fn ethernet_address(&self) -> EthernetAddress { + self.ether_addr + } + + pub fn setup_ip_addr(&self, ip: IpAddress, prefix_len: u8) { + let mut iface = self.iface.lock(); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs.push(IpCidr::new(ip, prefix_len)).unwrap(); + }); + } + + pub fn setup_gateway(&self, gateway: IpAddress) { + let mut iface = self.iface.lock(); + match gateway { + IpAddress::Ipv4(v4) => iface.routes_mut().add_default_ipv4_route(v4).unwrap(), + IpAddress::Ipv6(v6) => iface.routes_mut().add_default_ipv6_route(v6).unwrap(), + }; + } + + pub fn poll(&self, sockets: &Mutex) { + let mut dev = self.dev.lock(); + let mut iface = self.iface.lock(); + let mut sockets = sockets.lock(); + let timestamp = Self::current_time(); + iface.poll(timestamp, dev.deref_mut(), &mut sockets); + } +} + +impl DeviceWrapper { + fn new(inner: AxNetDevice) -> Self { + Self { + inner: RefCell::new(inner), + } + } +} + +impl Device for DeviceWrapper { + type RxToken<'a> = AxNetRxToken<'a> where Self: 'a; + type TxToken<'a> = AxNetTxToken<'a> where Self: 'a; + + fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + let mut dev = self.inner.borrow_mut(); + if let Err(e) = dev.recycle_tx_buffers() { + warn!("recycle_tx_buffers failed: {:?}", e); + return None; + } + + if !dev.can_transmit() { + return None; + } + let rx_buf = match dev.receive() { + Ok(buf) => buf, + Err(err) => { + if !matches!(err, DevError::Again) { + warn!("receive failed: {:?}", err); + } + return None; + } + }; + Some((AxNetRxToken(&self.inner, rx_buf), AxNetTxToken(&self.inner))) + } + + fn transmit(&mut self, _timestamp: Instant) -> Option> { + let mut dev = self.inner.borrow_mut(); + if let Err(e) = dev.recycle_tx_buffers() { + warn!("recycle_tx_buffers failed: {:?}", e); + return None; + } + if dev.can_transmit() { + Some(AxNetTxToken(&self.inner)) + } else { + None + } + } + + fn capabilities(&self) -> DeviceCapabilities { + let mut caps = DeviceCapabilities::default(); + caps.max_transmission_unit = 1514; + caps.max_burst_size = None; + caps.medium = Medium::Ethernet; + caps + } +} + +struct AxNetRxToken<'a>(&'a RefCell, NetBufPtr); +struct AxNetTxToken<'a>(&'a RefCell); + +impl<'a> RxToken for AxNetRxToken<'a> { + fn preprocess(&self, sockets: &mut SocketSet<'_>) { + snoop_tcp_packet(self.1.packet(), sockets).ok(); + } + + fn consume(self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut rx_buf = self.1; + trace!( + "RECV {} bytes: {:02X?}", + rx_buf.packet_len(), + rx_buf.packet() + ); + let result = f(rx_buf.packet_mut()); + self.0.borrow_mut().recycle_rx_buffer(rx_buf).unwrap(); + result + } +} + +impl<'a> TxToken for AxNetTxToken<'a> { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut dev = self.0.borrow_mut(); + let mut tx_buf = dev.alloc_tx_buffer(len).unwrap(); + let ret = f(tx_buf.packet_mut()); + trace!("SEND {} bytes: {:02X?}", len, tx_buf.packet()); + dev.transmit(tx_buf).unwrap(); + ret + } +} + +fn snoop_tcp_packet(buf: &[u8], sockets: &mut SocketSet<'_>) -> Result<(), smoltcp::wire::Error> { + use smoltcp::wire::{EthernetFrame, IpProtocol, Ipv4Packet, TcpPacket}; + + let ether_frame = EthernetFrame::new_checked(buf)?; + let ipv4_packet = Ipv4Packet::new_checked(ether_frame.payload())?; + + if ipv4_packet.next_header() == IpProtocol::Tcp { + let tcp_packet = TcpPacket::new_checked(ipv4_packet.payload())?; + let src_addr = (ipv4_packet.src_addr(), tcp_packet.src_port()).into(); + let dst_addr = (ipv4_packet.dst_addr(), tcp_packet.dst_port()).into(); + let is_first = tcp_packet.syn() && !tcp_packet.ack(); + if is_first { + // create a socket for the first incoming TCP packet, as the later accept() returns. + LISTEN_TABLE.incoming_tcp_packet(src_addr, dst_addr, sockets); + } + } + Ok(()) +} + +/// Poll the network stack. +/// +/// It may receive packets from the NIC and process them, and transmit queued +/// packets to the NIC. +pub fn poll_interfaces() { + SOCKET_SET.poll_interfaces(); +} + +/// Benchmark raw socket transmit bandwidth. +pub fn bench_transmit() { + ETH0.dev.lock().bench_transmit_bandwidth(); +} + +/// Benchmark raw socket receive bandwidth. +pub fn bench_receive() { + ETH0.dev.lock().bench_receive_bandwidth(); +} + +/// Add multicast_addr to the loopback device. +pub fn add_membership(multicast_addr: IpAddress, _interface_addr: IpAddress) { + let timestamp = Instant::from_micros_const((current_time_nanos() / NANOS_PER_MICROS) as i64); + let _ = LOOPBACK.lock().join_multicast_group( + LOOPBACK_DEV.lock().deref_mut(), + multicast_addr, + timestamp, + ); +} + +pub(crate) fn init(_net_dev: AxNetDevice) { + let mut device = LoopbackDev::new(Medium::Ip); + let config = Config::new(smoltcp::wire::HardwareAddress::Ip); + + let mut iface = Interface::new( + config, + &mut device, + Instant::from_micros_const((current_time_nanos() / NANOS_PER_MICROS) as i64), + ); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8)) + .unwrap(); + }); + LOOPBACK.init_by(Mutex::new(iface)); + LOOPBACK_DEV.init_by(Mutex::new(device)); + + let ether_addr = EthernetAddress(_net_dev.mac_address().0); + let eth0 = InterfaceWrapper::new("eth0", _net_dev, ether_addr); + + let ip = IP.parse().expect("invalid IP address"); + let gateway = GATEWAY.parse().expect("invalid gateway IP address"); + eth0.setup_ip_addr(ip, IP_PREFIX); + eth0.setup_gateway(gateway); + + ETH0.init_by(eth0); + info!("created net interface {:?}:", ETH0.name()); + info!(" ether: {}", ETH0.ethernet_address()); + info!(" ip: {}/{}", ip, IP_PREFIX); + info!(" gateway: {}", gateway); + + SOCKET_SET.init_by(SocketSetWrapper::new()); + LISTEN_TABLE.init_by(ListenTable::new()); +} diff --git a/modules/axnet/src/smoltcp_impl/tcp.rs b/modules/axnet/src/smoltcp_impl/tcp.rs new file mode 100644 index 0000000..a3caedc --- /dev/null +++ b/modules/axnet/src/smoltcp_impl/tcp.rs @@ -0,0 +1,742 @@ +use core::cell::UnsafeCell; +use core::net::SocketAddr; +use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; + +use axerrno::{ax_err, ax_err_type, AxError, AxResult}; +use axhal::time::current_ticks; +use axio::{PollState, Read, Write}; +use axsync::Mutex; + +use axtask::yield_now; +use smoltcp::iface::SocketHandle; +use smoltcp::socket::tcp::{self, ConnectError, State}; +use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; + +use super::addr::{from_core_sockaddr, into_core_sockaddr, is_unspecified, UNSPECIFIED_ENDPOINT}; +use super::{SocketSetWrapper, LISTEN_TABLE, SOCKET_SET}; + +// State transitions: +// CLOSED -(connect)-> BUSY -> CONNECTING -> CONNECTED -(shutdown)-> BUSY -> CLOSED +// | +// |-(listen)-> BUSY -> LISTENING -(shutdown)-> BUSY -> CLOSED +// | +// -(bind)-> BUSY -> CLOSED +const STATE_CLOSED: u8 = 0; +const STATE_BUSY: u8 = 1; +const STATE_CONNECTING: u8 = 2; +const STATE_CONNECTED: u8 = 3; +const STATE_LISTENING: u8 = 4; + +/// A TCP socket that provides POSIX-like APIs. +/// +/// - [`connect`] is for TCP clients. +/// - [`bind`], [`listen`], and [`accept`] are for TCP servers. +/// - Other methods are for both TCP clients and servers. +/// +/// [`connect`]: TcpSocket::connect +/// [`bind`]: TcpSocket::bind +/// [`listen`]: TcpSocket::listen +/// [`accept`]: TcpSocket::accept +pub struct TcpSocket { + state: AtomicU8, + handle: UnsafeCell>, + local_addr: UnsafeCell, + peer_addr: UnsafeCell, + nonblock: AtomicBool, + reuse_addr: AtomicBool, +} + +unsafe impl Sync for TcpSocket {} + +impl TcpSocket { + /// Creates a new TCP socket. + pub const fn new() -> Self { + Self { + state: AtomicU8::new(STATE_CLOSED), + handle: UnsafeCell::new(None), + local_addr: UnsafeCell::new(UNSPECIFIED_ENDPOINT), + peer_addr: UnsafeCell::new(UNSPECIFIED_ENDPOINT), + nonblock: AtomicBool::new(false), + reuse_addr: AtomicBool::new(false), + } + } + + /// Creates a new TCP socket that is already connected. + const fn new_connected( + handle: SocketHandle, + local_addr: IpEndpoint, + peer_addr: IpEndpoint, + ) -> Self { + Self { + state: AtomicU8::new(STATE_CONNECTED), + handle: UnsafeCell::new(Some(handle)), + local_addr: UnsafeCell::new(local_addr), + peer_addr: UnsafeCell::new(peer_addr), + nonblock: AtomicBool::new(false), + reuse_addr: AtomicBool::new(false), + } + } + + /// Returns the local address and port, or + /// [`Err(NotConnected)`](AxError::NotConnected) if not connected. + #[inline] + pub fn local_addr(&self) -> AxResult { + // 为了通过测例,已经`bind`但未`listen`的socket也可以返回地址 + match self.get_state() { + STATE_CONNECTED | STATE_LISTENING | STATE_CLOSED => { + Ok(into_core_sockaddr(unsafe { self.local_addr.get().read() })) + } + _ => Err(AxError::NotConnected), + } + } + + /// Returns the remote address and port, or + /// [`Err(NotConnected)`](AxError::NotConnected) if not connected. + #[inline] + pub fn peer_addr(&self) -> AxResult { + match self.get_state() { + STATE_CONNECTED | STATE_LISTENING => { + Ok(into_core_sockaddr(unsafe { self.peer_addr.get().read() })) + } + _ => Err(AxError::NotConnected), + } + } + + /// Returns whether this socket is in nonblocking mode. + #[inline] + pub fn is_nonblocking(&self) -> bool { + self.nonblock.load(Ordering::Acquire) + } + + /// Moves this TCP stream into or out of nonblocking mode. + /// + /// This will result in `read`, `write`, `recv` and `send` operations + /// becoming nonblocking, i.e., immediately returning from their calls. + /// If the IO operation is successful, `Ok` is returned and no further + /// action is required. If the IO operation could not be completed and needs + /// to be retried, an error with kind [`Err(WouldBlock)`](AxError::WouldBlock) is + /// returned. + #[inline] + pub fn set_nonblocking(&self, nonblocking: bool) { + self.nonblock.store(nonblocking, Ordering::Release); + } + + ///Returns whether this socket is in reuse address mode. + #[inline] + pub fn is_reuse_addr(&self) -> bool { + self.reuse_addr.load(Ordering::Acquire) + } + + /// Moves this TCP socket into or out of reuse address mode. + /// + /// When a socket is bound, the `SO_REUSEADDR` option allows multiple sockets to be bound to the + /// same address if they are bound to different local addresses. This option must be set before + /// calling `bind`. + #[inline] + pub fn set_reuse_addr(&self, reuse_addr: bool) { + self.reuse_addr.store(reuse_addr, Ordering::Release); + } + + /// To get the address pair of the socket. + /// + /// Returns the local and remote endpoint pair. + // fn get_endpoint_pair( + // &self, + // remote_addr: SocketAddr, + // ) -> Result<(IpListenEndpoint, IpEndpoint), AxError> { + // // TODO: check remote addr unreachable + // #[allow(unused_mut)] + // let mut remote_endpoint = from_core_sockaddr(remote_addr); + // #[allow(unused_mut)] + // let mut bound_endpoint = self.bound_endpoint()?; + // // #[cfg(feature = "ip")] + // if bound_endpoint.addr.is_none() && remote_endpoint.addr.as_bytes()[0] == 127 { + // // If the remote addr is unspecified, we should copy the local addr. + // // If the local addr is unspecified too, we should use the loopback interface. + // if remote_endpoint.addr.is_unspecified() { + // remote_endpoint.addr = + // smoltcp::wire::IpAddress::Ipv4(smoltcp::wire::Ipv4Address::new(127, 0, 0, 1)); + // } + // bound_endpoint.addr = Some(remote_endpoint.addr); + // } + // Ok((bound_endpoint, remote_endpoint)) + // } + + /// Connects to the given address and port. + /// + /// The local port is generated automatically. + pub fn connect(&self, remote_addr: SocketAddr) -> AxResult { + self.update_state(STATE_CLOSED, STATE_CONNECTING, || { + // SAFETY: no other threads can read or write these fields. + let handle = unsafe { self.handle.get().read() } + .unwrap_or_else(|| SOCKET_SET.add(SocketSetWrapper::new_tcp_socket())); + + // // TODO: check remote addr unreachable + // let (bound_endpoint, remote_endpoint) = self.get_endpoint_pair(remote_addr)?; + let remote_endpoint = from_core_sockaddr(remote_addr); + let bound_endpoint = self.bound_endpoint()?; + info!("bound endpoint: {:?}", bound_endpoint); + info!("remote endpoint: {:?}", remote_endpoint); + warn!("Temporarily net bridge used"); + let iface = if remote_endpoint.addr.as_bytes()[0] == 127 { + super::LOOPBACK.try_get().unwrap() + } else { + info!("Use eth net"); + &super::ETH0.iface + }; + + let (local_endpoint, remote_endpoint) = SOCKET_SET + .with_socket_mut::(handle, |socket| { + socket + .connect(iface.lock().context(), remote_endpoint, bound_endpoint) + .or_else(|e| match e { + ConnectError::InvalidState => { + ax_err!(BadState, "socket connect() failed") + } + ConnectError::Unaddressable => { + ax_err!(ConnectionRefused, "socket connect() failed") + } + })?; + Ok::<(IpEndpoint, IpEndpoint), AxError>(( + socket.local_endpoint().unwrap(), + socket.remote_endpoint().unwrap(), + )) + })?; + unsafe { + // SAFETY: no other threads can read or write these fields as we + // have changed the state to `BUSY`. + self.local_addr.get().write(local_endpoint); + self.peer_addr.get().write(remote_endpoint); + self.handle.get().write(Some(handle)); + } + Ok(()) + }) + .unwrap_or_else(|_| ax_err!(AlreadyExists, "socket connect() failed: already connected"))?; // EISCONN + + // HACK: yield() to let server to listen + yield_now(); + + // Here our state must be `CONNECTING`, and only one thread can run here. + if self.is_nonblocking() { + Err(AxError::WouldBlock) + } else { + self.block_on(|| { + let PollState { writable, .. } = self.poll_connect()?; + if !writable { + Err(AxError::WouldBlock) + } else if self.get_state() == STATE_CONNECTED { + Ok(()) + } else { + ax_err!(ConnectionRefused, "socket connect() failed") + } + }) + } + } + + /// Binds an unbound socket to the given address and port. + /// + /// If the given port is 0, it generates one automatically. + /// + /// It's must be called before [`listen`](Self::listen) and + /// [`accept`](Self::accept). + pub fn bind(&self, mut local_addr: SocketAddr) -> AxResult { + self.update_state(STATE_CLOSED, STATE_CLOSED, || { + // TODO: check addr is available + if local_addr.port() == 0 { + local_addr.set_port(get_ephemeral_port()?); + } + // SAFETY: no other threads can read or write `self.local_addr` as we + // have changed the state to `BUSY`. + unsafe { + let old = self.local_addr.get().read(); + if old != UNSPECIFIED_ENDPOINT { + return ax_err!(InvalidInput, "socket bind() failed: already bound"); + } + self.local_addr.get().write(from_core_sockaddr(local_addr)); + } + let local_endpoint = from_core_sockaddr(local_addr); + let bound_endpoint = self.bound_endpoint()?; + let handle = unsafe { self.handle.get().read() } + .unwrap_or_else(|| SOCKET_SET.add(SocketSetWrapper::new_tcp_socket())); + SOCKET_SET.with_socket_mut::(handle, |socket| { + socket.set_bound_endpoint(bound_endpoint); + }); + + if !self.is_reuse_addr() { + SOCKET_SET.bind_check(local_endpoint.addr, local_endpoint.port)?; + } + Ok(()) + }) + .unwrap_or_else(|_| ax_err!(InvalidInput, "socket bind() failed: already bound")) + } + + /// Starts listening on the bound address and port. + /// + /// It's must be called after [`bind`](Self::bind) and before + /// [`accept`](Self::accept). + pub fn listen(&self) -> AxResult { + self.update_state(STATE_CLOSED, STATE_LISTENING, || { + let bound_endpoint = self.bound_endpoint()?; + unsafe { + (*self.local_addr.get()).port = bound_endpoint.port; + } + LISTEN_TABLE.listen(bound_endpoint)?; + debug!("TCP socket listening on {}", bound_endpoint); + Ok(()) + }) + .unwrap_or(Ok(())) // ignore simultaneous `listen`s. + } + + /// Accepts a new connection. + /// + /// This function will block the calling thread until a new TCP connection + /// is established. When established, a new [`TcpSocket`] is returned. + /// + /// It's must be called after [`bind`](Self::bind) and [`listen`](Self::listen). + pub fn accept(&self) -> AxResult { + if !self.is_listening() { + return ax_err!(InvalidInput, "socket accept() failed: not listen"); + } + + // SAFETY: `self.local_addr` should be initialized after `bind()`. + let local_port = unsafe { self.local_addr.get().read().port }; + self.block_on(|| { + let (handle, (local_addr, peer_addr)) = LISTEN_TABLE.accept(local_port)?; + debug!("TCP socket accepted a new connection {}", peer_addr); + Ok(TcpSocket::new_connected(handle, local_addr, peer_addr)) + }) + } + + /// Close the connection. + pub fn shutdown(&self) -> AxResult { + // stream + self.update_state(STATE_CONNECTED, STATE_CLOSED, || { + // SAFETY: `self.handle` should be initialized in a connected socket, and + // no other threads can read or write it. + let handle = unsafe { self.handle.get().read().unwrap() }; + SOCKET_SET.with_socket_mut::(handle, |socket| { + debug!("TCP socket {}: shutting down", handle); + socket.close(); + }); + unsafe { self.local_addr.get().write(UNSPECIFIED_ENDPOINT) }; // clear bound address + SOCKET_SET.poll_interfaces(); + Ok(()) + }) + .unwrap_or(Ok(()))?; + + // listener + self.update_state(STATE_LISTENING, STATE_CLOSED, || { + // SAFETY: `self.local_addr` should be initialized in a listening socket, + // and no other threads can read or write it. + let local_port = unsafe { self.local_addr.get().read().port }; + unsafe { self.local_addr.get().write(UNSPECIFIED_ENDPOINT) }; // clear bound address + LISTEN_TABLE.unlisten(local_port); + SOCKET_SET.poll_interfaces(); + Ok(()) + }) + .unwrap_or(Ok(()))?; + + // ignore for other states + Ok(()) + } + + /// Close the transmit half of the tcp socket. + /// It will call `close()` on smoltcp::socket::tcp::Socket. It should send FIN to remote half. + /// + /// This function is for shutdown(fd, SHUT_WR) syscall. + /// + /// It won't change TCP state. + /// It won't affect unconnected sockets (listener). + pub fn close(&self) { + let handle = match unsafe { self.handle.get().read() } { + Some(h) => h, + None => return, + }; + SOCKET_SET.with_socket_mut::(handle, |socket| socket.close()); + SOCKET_SET.poll_interfaces(); + } + + /// Receives data from the socket, stores it in the given buffer. + pub fn recv(&self, buf: &mut [u8]) -> AxResult { + if self.is_connecting() { + return Err(AxError::WouldBlock); + } else if !self.is_connected() { + return ax_err!(NotConnected, "socket recv() failed"); + } + + // SAFETY: `self.handle` should be initialized in a connected socket. + let handle = unsafe { self.handle.get().read().unwrap() }; + self.block_on(|| { + SOCKET_SET.with_socket_mut::(handle, |socket| { + if socket.recv_queue() > 0 { + // data available + // TODO: use socket.recv(|buf| {...}) + let len = socket + .recv_slice(buf) + .map_err(|_| ax_err_type!(BadState, "socket recv() failed"))?; + Ok(len) + } else if !socket.is_active() { + // not open + ax_err!(ConnectionRefused, "socket recv() failed") + } else if !socket.may_recv() { + // connection closed + Ok(0) + } else { + // no more data + Err(AxError::WouldBlock) + } + }) + }) + } + /// Receives data from the socket, stores it in the given buffer. + /// + /// It will return [`Err(Timeout)`](AxError::Timeout) if expired. + pub fn recv_timeout(&self, buf: &mut [u8], ticks: u64) -> AxResult { + if self.is_connecting() { + return Err(AxError::WouldBlock); + } else if !self.is_connected() { + return ax_err!(NotConnected, "socket recv() failed"); + } + + let expire_at = current_ticks() + ticks; + + // SAFETY: `self.handle` should be initialized in a connected socket. + let handle = unsafe { self.handle.get().read().unwrap() }; + self.block_on(|| { + SOCKET_SET.with_socket_mut::(handle, |socket| { + if socket.recv_queue() > 0 { + // data available + // TODO: use socket.recv(|buf| {...}) + let len = socket + .recv_slice(buf) + .map_err(|_| ax_err_type!(BadState, "socket recv() failed"))?; + Ok(len) + } else if !socket.is_active() { + // not open + ax_err!(ConnectionRefused, "socket recv() failed") + } else if !socket.may_recv() { + // connection closed + Ok(0) + } else { + // no more data + if current_ticks() > expire_at { + Err(AxError::Timeout) + } else { + Err(AxError::WouldBlock) + } + } + }) + }) + } + + /// Transmits data in the given buffer. + pub fn send(&self, buf: &[u8]) -> AxResult { + if self.is_connecting() { + return Err(AxError::WouldBlock); + } else if !self.is_connected() { + return ax_err!(NotConnected, "socket send() failed"); + } + + // SAFETY: `self.handle` should be initialized in a connected socket. + let handle = unsafe { self.handle.get().read().unwrap() }; + self.block_on(|| { + SOCKET_SET.with_socket_mut::(handle, |socket| { + if !socket.is_active() || !socket.may_send() { + // closed by remote + ax_err!(ConnectionReset, "socket send() failed") + } else if socket.can_send() { + // connected, and the tx buffer is not full + // TODO: use socket.send(|buf| {...}) + let len = socket + .send_slice(buf) + .map_err(|_| ax_err_type!(BadState, "socket send() failed"))?; + Ok(len) + } else { + // tx buffer is full + Err(AxError::WouldBlock) + } + }) + }) + } + + /// Whether the socket is readable or writable. + pub fn poll(&self) -> AxResult { + match self.get_state() { + STATE_CONNECTING => self.poll_connect(), + STATE_CONNECTED => self.poll_stream(), + STATE_LISTENING => self.poll_listener(), + _ => Ok(PollState { + readable: false, + writable: false, + }), + } + } + + /// To set the nagle algorithm enabled or not. + pub fn set_nagle_enabled(&self, enabled: bool) -> AxResult { + let handle = unsafe { self.handle.get().read() }; + + let Some(handle) = handle else { + return Err(AxError::NotConnected); + }; + + SOCKET_SET.with_socket_mut::(handle, |socket| { + socket.set_nagle_enabled(enabled) + }); + + Ok(()) + } + + /// To get the nagle algorithm enabled or not. + pub fn nagle_enabled(&self) -> bool { + let handle = unsafe { self.handle.get().read() }; + + match handle { + Some(handle) => { + SOCKET_SET.with_socket::(handle, |socket| socket.nagle_enabled()) + } + // Nagle algorithm will be enabled by default once the socket is created + None => true, + } + } + + /// To get the socket and call the given function. + /// + /// If the socket is not connected, it will return None. + /// + /// Or it will return the result of the given function. + pub fn with_socket(&self, f: impl FnOnce(Option<&tcp::Socket>) -> R) -> R { + let handle = unsafe { self.handle.get().read() }; + + match handle { + Some(handle) => { + SOCKET_SET.with_socket::(handle, |socket| f(Some(socket))) + } + None => f(None), + } + } + + /// To get the mutable socket and call the given function. + /// + /// If the socket is not connected, it will return None. + /// + /// Or it will return the result of the given function. + pub fn with_socket_mut(&self, f: impl FnOnce(Option<&mut tcp::Socket>) -> R) -> R { + let handle = unsafe { self.handle.get().read() }; + + match handle { + Some(handle) => { + SOCKET_SET.with_socket_mut::(handle, |socket| f(Some(socket))) + } + None => f(None), + } + } +} + +/// Private methods +impl TcpSocket { + #[inline] + fn get_state(&self) -> u8 { + self.state.load(Ordering::Acquire) + } + + #[inline] + fn set_state(&self, state: u8) { + self.state.store(state, Ordering::Release); + } + + /// Update the state of the socket atomically. + /// + /// If the current state is `expect`, it first changes the state to `STATE_BUSY`, + /// then calls the given function. If the function returns `Ok`, it changes the + /// state to `new`, otherwise it changes the state back to `expect`. + /// + /// It returns `Ok` if the current state is `expect`, otherwise it returns + /// the current state in `Err`. + fn update_state(&self, expect: u8, new: u8, f: F) -> Result, u8> + where + F: FnOnce() -> AxResult, + { + match self + .state + .compare_exchange(expect, STATE_BUSY, Ordering::Acquire, Ordering::Acquire) + { + Ok(_) => { + let res = f(); + if res.is_ok() { + self.set_state(new); + } else { + self.set_state(expect); + } + Ok(res) + } + Err(old) => Err(old), + } + } + + #[inline] + fn is_connecting(&self) -> bool { + self.get_state() == STATE_CONNECTING + } + + #[inline] + /// Whether the socket is connected. + pub fn is_connected(&self) -> bool { + self.get_state() == STATE_CONNECTED + } + + #[inline] + /// Whether the socket is closed. + pub fn is_closed(&self) -> bool { + self.get_state() == STATE_CLOSED + } + + #[inline] + fn is_listening(&self) -> bool { + self.get_state() == STATE_LISTENING + } + + fn bound_endpoint(&self) -> AxResult { + // SAFETY: no other threads can read or write `self.local_addr`. + let local_addr = unsafe { self.local_addr.get().read() }; + let port = if local_addr.port != 0 { + local_addr.port + } else { + get_ephemeral_port()? + }; + assert_ne!(port, 0); + let addr = if !is_unspecified(local_addr.addr) { + Some(local_addr.addr) + } else { + None + }; + Ok(IpListenEndpoint { addr, port }) + } + + fn poll_connect(&self) -> AxResult { + // SAFETY: `self.handle` should be initialized above. + let handle = unsafe { self.handle.get().read().unwrap() }; + let writable = + SOCKET_SET.with_socket::(handle, |socket| match socket.state() { + State::SynSent => false, // wait for connection + State::Established => { + self.set_state(STATE_CONNECTED); // connected + debug!( + "TCP socket {}: connected to {}", + handle, + socket.remote_endpoint().unwrap(), + ); + true + } + _ => { + unsafe { + self.local_addr.get().write(UNSPECIFIED_ENDPOINT); + self.peer_addr.get().write(UNSPECIFIED_ENDPOINT); + } + self.set_state(STATE_CLOSED); // connection failed + true + } + }); + Ok(PollState { + readable: false, + writable, + }) + } + + fn poll_stream(&self) -> AxResult { + // SAFETY: `self.handle` should be initialized in a connected socket. + let handle = unsafe { self.handle.get().read().unwrap() }; + SOCKET_SET.with_socket::(handle, |socket| { + Ok(PollState { + readable: !socket.may_recv() || socket.can_recv(), + writable: !socket.may_send() || socket.can_send(), + }) + }) + } + + fn poll_listener(&self) -> AxResult { + // SAFETY: `self.local_addr` should be initialized in a listening socket. + let local_addr = unsafe { self.local_addr.get().read() }; + Ok(PollState { + readable: LISTEN_TABLE.can_accept(local_addr.port)?, + writable: false, + }) + } + + /// Block the current thread until the given function completes or fails. + /// + /// If the socket is non-blocking, it calls the function once and returns + /// immediately. Otherwise, it may call the function multiple times if it + /// returns [`Err(WouldBlock)`](AxError::WouldBlock). + fn block_on(&self, mut f: F) -> AxResult + where + F: FnMut() -> AxResult, + { + if self.is_nonblocking() { + f() + } else { + loop { + #[cfg(feature = "monolithic")] + if axprocess::signal::current_have_signals() { + return Err(AxError::Interrupted); + } + + SOCKET_SET.poll_interfaces(); + match f() { + Ok(t) => return Ok(t), + Err(AxError::WouldBlock) => axtask::yield_now(), + Err(e) => return Err(e), + } + } + } + } +} + +impl Read for TcpSocket { + fn read(&mut self, buf: &mut [u8]) -> AxResult { + self.recv(buf) + } +} + +impl Write for TcpSocket { + fn write(&mut self, buf: &[u8]) -> AxResult { + self.send(buf) + } + + fn flush(&mut self) -> AxResult { + Err(AxError::Unsupported) + } +} + +impl Drop for TcpSocket { + fn drop(&mut self) { + self.shutdown().ok(); + // Safe because we have mut reference to `self`. + if let Some(handle) = unsafe { self.handle.get().read() } { + SOCKET_SET.remove(handle); + } + } +} + +fn get_ephemeral_port() -> AxResult { + const PORT_START: u16 = 0xc000; + const PORT_END: u16 = 0xffff; + static CURR: Mutex = Mutex::new(PORT_START); + + let mut curr = CURR.lock(); + let mut tries = 0; + // TODO: more robust + while tries <= PORT_END - PORT_START { + let port = *curr; + if *curr == PORT_END { + *curr = PORT_START; + } else { + *curr += 1; + } + if LISTEN_TABLE.can_listen(port) { + return Ok(port); + } + tries += 1; + } + ax_err!(AddrInUse, "no avaliable ports!") +} diff --git a/modules/axnet/src/smoltcp_impl/udp.rs b/modules/axnet/src/smoltcp_impl/udp.rs new file mode 100644 index 0000000..43dd337 --- /dev/null +++ b/modules/axnet/src/smoltcp_impl/udp.rs @@ -0,0 +1,380 @@ +use core::net::SocketAddr; +use core::sync::atomic::{AtomicBool, Ordering}; + +use axerrno::{ax_err, ax_err_type, AxError, AxResult}; +use axhal::time::current_ticks; +use axio::{PollState, Read, Write}; +use axsync::Mutex; +use spin::RwLock; + +use smoltcp::iface::SocketHandle; +use smoltcp::socket::udp::{self, BindError, SendError}; +use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; + +use super::addr::{from_core_sockaddr, into_core_sockaddr, is_unspecified, UNSPECIFIED_ENDPOINT}; +use super::{SocketSetWrapper, SOCKET_SET}; + +/// A UDP socket that provides POSIX-like APIs. +pub struct UdpSocket { + handle: SocketHandle, + local_addr: RwLock>, + peer_addr: RwLock>, + nonblock: AtomicBool, + reuse_addr: AtomicBool, +} + +impl UdpSocket { + /// Creates a new UDP socket. + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + let socket = SocketSetWrapper::new_udp_socket(); + let handle = SOCKET_SET.add(socket); + Self { + handle, + local_addr: RwLock::new(None), + peer_addr: RwLock::new(None), + nonblock: AtomicBool::new(false), + reuse_addr: AtomicBool::new(false), + } + } + + /// Returns the local address and port, or + /// [`Err(NotConnected)`](AxError::NotConnected) if not connected. + pub fn local_addr(&self) -> AxResult { + match self.local_addr.try_read() { + Some(addr) => addr.map(into_core_sockaddr).ok_or(AxError::NotConnected), + None => Err(AxError::NotConnected), + } + } + + /// Returns the remote address and port, or + /// [`Err(NotConnected)`](AxError::NotConnected) if not connected. + pub fn peer_addr(&self) -> AxResult { + self.remote_endpoint().map(into_core_sockaddr) + } + + /// Returns whether this socket is in nonblocking mode. + #[inline] + pub fn is_nonblocking(&self) -> bool { + self.nonblock.load(Ordering::Acquire) + } + + /// Moves this UDP socket into or out of nonblocking mode. + /// + /// This will result in `recv`, `recv_from`, `send`, and `send_to` + /// operations becoming nonblocking, i.e., immediately returning from their + /// calls. If the IO operation is successful, `Ok` is returned and no + /// further action is required. If the IO operation could not be completed + /// and needs to be retried, an error with kind + /// [`Err(WouldBlock)`](AxError::WouldBlock) is returned. + #[inline] + pub fn set_nonblocking(&self, nonblocking: bool) { + self.nonblock.store(nonblocking, Ordering::Release); + } + + /// Set the TTL (time-to-live) option for this socket. + /// + /// The TTL is the number of hops that a packet is allowed to live. + pub fn set_socket_ttl(&self, ttl: u8) { + SOCKET_SET.with_socket_mut::(self.handle, |socket| { + socket.set_hop_limit(Some(ttl)) + }); + } + + /// Returns whether this socket is in reuse address mode. + #[inline] + pub fn is_reuse_addr(&self) -> bool { + self.reuse_addr.load(Ordering::Acquire) + } + + /// Moves this UDP socket into or out of reuse address mode. + /// + /// When a socket is bound, the `SO_REUSEADDR` option allows multiple sockets to be bound to the + /// same address if they are bound to different local addresses. This option must be set before + /// calling `bind`. + #[inline] + pub fn set_reuse_addr(&self, reuse_addr: bool) { + self.reuse_addr.store(reuse_addr, Ordering::Release); + } + + /// Binds an unbound socket to the given address and port. + /// + /// It's must be called before [`send_to`](Self::send_to) and + /// [`recv_from`](Self::recv_from). + pub fn bind(&self, mut local_addr: SocketAddr) -> AxResult { + let mut self_local_addr = self.local_addr.write(); + + if local_addr.port() == 0 { + local_addr.set_port(get_ephemeral_port()?); + } + if self_local_addr.is_some() { + return ax_err!(InvalidInput, "socket bind() failed: already bound"); + } + + let local_endpoint = from_core_sockaddr(local_addr); + let endpoint = IpListenEndpoint { + addr: (!is_unspecified(local_endpoint.addr)).then_some(local_endpoint.addr), + port: local_endpoint.port, + }; + + if !self.is_reuse_addr() { + // Check if the address is already in use + SOCKET_SET.bind_check(local_endpoint.addr, local_endpoint.port)?; + } + + SOCKET_SET.with_socket_mut::(self.handle, |socket| { + socket.bind(endpoint).or_else(|e| match e { + BindError::InvalidState => ax_err!(AlreadyExists, "socket bind() failed"), + BindError::Unaddressable => ax_err!(InvalidInput, "socket bind() failed"), + }) + })?; + + *self_local_addr = Some(local_endpoint); + debug!("UDP socket {}: bound on {}", self.handle, endpoint); + Ok(()) + } + + /// Sends data on the socket to the given address. On success, returns the + /// number of bytes written. + pub fn send_to(&self, buf: &[u8], remote_addr: SocketAddr) -> AxResult { + if remote_addr.port() == 0 || remote_addr.ip().is_unspecified() { + return ax_err!(InvalidInput, "socket send_to() failed: invalid address"); + } + self.send_impl(buf, from_core_sockaddr(remote_addr)) + } + + /// Receives a single datagram message on the socket. On success, returns + /// the number of bytes read and the origin. + pub fn recv_from(&self, buf: &mut [u8]) -> AxResult<(usize, SocketAddr)> { + self.recv_impl(|socket| match socket.recv_slice(buf) { + Ok((len, meta)) => Ok((len, into_core_sockaddr(meta.endpoint))), + Err(_) => ax_err!(BadState, "socket recv_from() failed"), + }) + } + + /// Receives data from the socket, stores it in the given buffer. + /// + /// It will return [`Err(Timeout)`](AxError::Timeout) if expired. + pub fn recv_from_timeout(&self, buf: &mut [u8], ticks: u64) -> AxResult<(usize, SocketAddr)> { + let expire_at = current_ticks() + ticks; + self.recv_impl(|socket| match socket.recv_slice(buf) { + Ok((len, meta)) => Ok((len, into_core_sockaddr(meta.endpoint))), + Err(_) => { + if current_ticks() > expire_at { + Err(AxError::Timeout) + } else { + Err(AxError::WouldBlock) + } + } + }) + } + + /// Receives a single datagram message on the socket, without removing it from + /// the queue. On success, returns the number of bytes read and the origin. + pub fn peek_from(&self, buf: &mut [u8]) -> AxResult<(usize, SocketAddr)> { + self.recv_impl(|socket| match socket.peek_slice(buf) { + Ok((len, meta)) => Ok((len, into_core_sockaddr(meta.endpoint))), + Err(_) => ax_err!(BadState, "socket recv_from() failed"), + }) + } + + /// Connects this UDP socket to a remote address, allowing the `send` and + /// `recv` to be used to send data and also applies filters to only receive + /// data from the specified address. + /// + /// The local port will be generated automatically if the socket is not bound. + /// It's must be called before [`send`](Self::send) and + /// [`recv`](Self::recv). + pub fn connect(&self, addr: SocketAddr) -> AxResult { + let mut self_peer_addr = self.peer_addr.write(); + + if self.local_addr.read().is_none() { + self.bind(into_core_sockaddr(UNSPECIFIED_ENDPOINT))?; + } + + *self_peer_addr = Some(from_core_sockaddr(addr)); + debug!("UDP socket {}: connected to {}", self.handle, addr); + Ok(()) + } + + /// Sends data on the socket to the remote address to which it is connected. + pub fn send(&self, buf: &[u8]) -> AxResult { + let remote_endpoint = self.remote_endpoint()?; + self.send_impl(buf, remote_endpoint) + } + + /// Receives a single datagram message on the socket from the remote address + /// to which it is connected. On success, returns the number of bytes read. + pub fn recv(&self, buf: &mut [u8]) -> AxResult { + let remote_endpoint = self.remote_endpoint()?; + self.recv_impl(|socket| { + let (len, meta) = socket + .recv_slice(buf) + .map_err(|_| ax_err_type!(BadState, "socket recv() failed"))?; + if !is_unspecified(remote_endpoint.addr) && remote_endpoint.addr != meta.endpoint.addr { + return Err(AxError::WouldBlock); + } + if remote_endpoint.port != 0 && remote_endpoint.port != meta.endpoint.port { + return Err(AxError::WouldBlock); + } + Ok(len) + }) + } + + /// Close the socket. + pub fn shutdown(&self) -> AxResult { + SOCKET_SET.poll_interfaces(); + SOCKET_SET.with_socket_mut::(self.handle, |socket| { + debug!("UDP socket {}: shutting down", self.handle); + socket.close(); + }); + Ok(()) + } + + /// Whether the socket is readable or writable. + pub fn poll(&self) -> AxResult { + if self.local_addr.read().is_none() { + return Ok(PollState { + readable: false, + writable: false, + }); + } + SOCKET_SET.with_socket_mut::(self.handle, |socket| { + Ok(PollState { + readable: socket.can_recv(), + writable: socket.can_send(), + }) + }) + } +} + +/// Private methods +impl UdpSocket { + fn remote_endpoint(&self) -> AxResult { + match self.peer_addr.try_read() { + Some(addr) => addr.ok_or(AxError::NotConnected), + None => Err(AxError::NotConnected), + } + } + + fn send_impl(&self, buf: &[u8], remote_endpoint: IpEndpoint) -> AxResult { + if self.local_addr.read().is_none() { + return ax_err!(NotConnected, "socket send() failed"); + } + // info!("send to addr: {:?}", remote_endpoint); + self.block_on(|| { + SOCKET_SET.with_socket_mut::(self.handle, |socket| { + if !socket.is_open() { + // not connected + ax_err!(NotConnected, "socket send() failed") + } else if socket.can_send() { + socket + .send_slice(buf, remote_endpoint) + .map_err(|e| match e { + SendError::BufferFull => AxError::WouldBlock, + SendError::Unaddressable => { + ax_err_type!(ConnectionRefused, "socket send() failed") + } + })?; + Ok(buf.len()) + } else { + // tx buffer is full + Err(AxError::WouldBlock) + } + }) + }) + } + + fn recv_impl(&self, mut op: F) -> AxResult + where + F: FnMut(&mut udp::Socket) -> AxResult, + { + if self.local_addr.read().is_none() { + return ax_err!(NotConnected, "socket send() failed"); + } + self.block_on(|| { + SOCKET_SET.with_socket_mut::(self.handle, |socket| { + if !socket.is_open() { + // not bound + ax_err!(NotConnected, "socket recv() failed") + } else if socket.can_recv() { + // data available + op(socket) + } else { + // no more data + Err(AxError::WouldBlock) + } + }) + }) + } + + fn block_on(&self, mut f: F) -> AxResult + where + F: FnMut() -> AxResult, + { + if self.is_nonblocking() { + f() + } else { + loop { + #[cfg(feature = "monolithic")] + if axprocess::signal::current_have_signals() { + return Err(AxError::Interrupted); + } + + SOCKET_SET.poll_interfaces(); + match f() { + Ok(t) => return Ok(t), + Err(AxError::WouldBlock) => axtask::yield_now(), + Err(e) => return Err(e), + } + } + } + } + + /// To get the socket and call the given function. + /// + /// If the socket is not connected, it will return None. + /// + /// Or it will return the result of the given function. + pub fn with_socket(&self, f: impl FnOnce(&udp::Socket) -> R) -> R { + SOCKET_SET.with_socket(self.handle, |s| f(s)) + } +} + +impl Read for UdpSocket { + fn read(&mut self, buf: &mut [u8]) -> AxResult { + self.recv(buf) + } +} + +impl Write for UdpSocket { + fn write(&mut self, buf: &[u8]) -> AxResult { + self.send(buf) + } + + fn flush(&mut self) -> AxResult { + Err(AxError::Unsupported) + } +} + +impl Drop for UdpSocket { + fn drop(&mut self) { + self.shutdown().ok(); + SOCKET_SET.remove(self.handle); + } +} + +fn get_ephemeral_port() -> AxResult { + const PORT_START: u16 = 0xc000; + const PORT_END: u16 = 0xffff; + static CURR: Mutex = Mutex::new(PORT_START); + let mut curr = CURR.lock(); + + let port = *curr; + if *curr == PORT_END { + *curr = PORT_START; + } else { + *curr += 1; + } + Ok(port) +} diff --git a/modules/axprocess/.gitignore b/modules/axprocess/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/modules/axprocess/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/modules/axprocess/Cargo.toml b/modules/axprocess/Cargo.toml new file mode 100644 index 0000000..d2bb3eb --- /dev/null +++ b/modules/axprocess/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "axprocess" +version = "0.1.0" +edition = "2021" +keywords = ["Starry", "process"] +description = "A process management library for Starry OS." +authors = ["Youjie Zheng "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] + +fs = ["axfs"] + +monolithic = ["fs", "axfs/monolithic", "axhal/monolithic", "axtask/monolithic"] + +default = ["monolithic"] + +[dependencies] +cfg-if = "1.0" +spinlock = { git = "https://github.com/Starry-OS/spinlock.git" } +axtask = { workspace = true } +axsync = { workspace = true } +axmem = { workspace = true } +axalloc = { workspace = true } +axio = { git = "https://github.com/Starry-OS/axio.git", features = ["alloc"] } +axhal = { workspace = true } +axlog = { workspace = true } +axerrno = { git = "https://github.com/Starry-OS/axerrno.git" } +axconfig = { workspace = true } +axfs = { workspace = true, optional = true } +axsignal = { workspace = true } +axfutex = { workspace = true } +riscv = "0.10" +bitflags = "2.0" +lazy_static = { version = "1.4", features = ["spin_no_std"] } +xmas-elf = "0.9.0" +elf_parser = { git = "https://github.com/Starry-OS/elf_parser.git" } \ No newline at end of file diff --git a/modules/axprocess/src/api.rs b/modules/axprocess/src/api.rs new file mode 100644 index 0000000..f072add --- /dev/null +++ b/modules/axprocess/src/api.rs @@ -0,0 +1,394 @@ +use core::ops::Deref; +use core::ptr::copy_nonoverlapping; +use core::str::from_utf8; +extern crate alloc; +use alloc::sync::Arc; +use alloc::{ + string::{String, ToString}, + vec, + vec::Vec, +}; +use axconfig::{MAX_USER_HEAP_SIZE, MAX_USER_STACK_SIZE, USER_HEAP_BASE, USER_STACK_TOP}; +use axerrno::{AxError, AxResult}; +use axfutex::flags::FutexFlags; +use axhal::mem::VirtAddr; +use axhal::paging::MappingFlags; +use axhal::time::{current_time_nanos, NANOS_PER_MICROS, NANOS_PER_SEC}; +use axhal::KERNEL_PROCESS_ID; +use axlog::{info, warn}; +use axmem::MemorySet; + +use axsignal::signal_no::SignalNo; +use axsync::Mutex; +use axtask::{current, current_processor, yield_now, AxTaskRef, CurrentTask, TaskId, TaskState}; +use core::sync::atomic::AtomicI32; +use elf_parser::{ + get_app_stack_region, get_auxv_vector, get_elf_entry, get_elf_segments, get_relocate_pairs, +}; +use xmas_elf::program::SegmentData; + +use crate::flags::WaitStatus; +use crate::futex::futex_wake; +use crate::link::real_path; +use crate::process::{Process, PID2PC, TID2TASK}; + +use crate::signal::{send_signal_to_process, send_signal_to_thread}; + +/// 初始化内核调度进程 +pub fn init_kernel_process() { + let kernel_process = Arc::new(Process::new( + TaskId::new().as_u64(), + axconfig::TASK_STACK_SIZE as u64, + 0, + Mutex::new(Arc::new(Mutex::new(MemorySet::new_empty()))), + 0, + Arc::new(Mutex::new(String::from("/"))), + Arc::new(AtomicI32::new(0o022)), + Arc::new(Mutex::new(vec![])), + )); + + axtask::init_scheduler(); + kernel_process + .tasks + .lock() + .push(Arc::clone(current_processor().idle_task())); + PID2PC.lock().insert(kernel_process.pid(), kernel_process); +} + +/// return the `Arc` of the current process +pub fn current_process() -> Arc { + let current_task = current(); + + let current_process = Arc::clone(PID2PC.lock().get(¤t_task.get_process_id()).unwrap()); + + current_process +} + +/// 退出当前任务 +pub fn exit_current_task(exit_code: i32) -> ! { + let process = current_process(); + let current_task = current(); + + let curr_id = current_task.id().as_u64(); + + info!("exit task id {} with code _{}_", curr_id, exit_code); + // 检查这个任务是否有sig_child信号 + let exit_signal = process + .signal_modules + .lock() + .get(&curr_id) + .unwrap() + .get_exit_signal(); + + if exit_signal.is_some() || current_task.is_leader() { + let parent = process.get_parent(); + if parent != KERNEL_PROCESS_ID { + // send exit signal + let signal = if let Some(exit_signal) = exit_signal { + exit_signal + } else { + SignalNo::SIGCHLD + }; + + send_signal_to_process(parent as isize, signal as isize, None).unwrap(); + } + } + // clear_child_tid 的值不为 0,则将这个用户地址处的值写为0 + let clear_child_tid = current_task.get_clear_child_tid(); + if clear_child_tid != 0 { + // 先确认是否在用户空间 + if process + .manual_alloc_for_lazy(clear_child_tid.into()) + .is_ok() + { + unsafe { + *(clear_child_tid as *mut i32) = 0; + let _ = futex_wake(clear_child_tid.into(), FutexFlags::empty(), 1); + } + } + } + if current_task.is_leader() { + loop { + let mut all_exited = true; + for task in process.tasks.lock().deref() { + if !task.is_leader() && task.state() != TaskState::Exited { + all_exited = false; + send_signal_to_thread(task.id().as_u64() as isize, SignalNo::SIGKILL as isize) + .unwrap(); + } + } + if !all_exited { + yield_now(); + } else { + break; + } + } + TID2TASK.lock().remove(&curr_id); + process.set_exit_code(exit_code); + + process.set_zombie(true); + + process.tasks.lock().clear(); + process.fd_manager.fd_table.lock().clear(); + + process.signal_modules.lock().clear(); + + let mut pid2pc = PID2PC.lock(); + let kernel_process = pid2pc.get(&KERNEL_PROCESS_ID).unwrap(); + // 将子进程交给idle进程 + // process.memory_set = Arc::clone(&kernel_process.memory_set); + for child in process.children.lock().deref() { + child.set_parent(KERNEL_PROCESS_ID); + kernel_process.children.lock().push(Arc::clone(child)); + } + if let Some(parent_process) = pid2pc.get(&process.get_parent()) { + parent_process.set_vfork_block(false); + } + pid2pc.remove(&process.pid()); + drop(pid2pc); + drop(process); + } else { + TID2TASK.lock().remove(&curr_id); + // 从进程中删除当前线程 + let mut tasks = process.tasks.lock(); + let len = tasks.len(); + for index in 0..len { + if tasks[index].id().as_u64() == curr_id { + tasks.remove(index); + break; + } + } + drop(tasks); + + process.signal_modules.lock().remove(&curr_id); + drop(process); + } + axtask::exit(exit_code); +} + +/// 返回应用程序入口,用户栈底,用户堆底 +pub fn load_app( + name: String, + mut args: Vec, + envs: &Vec, + memory_set: &mut MemorySet, +) -> AxResult<(VirtAddr, VirtAddr, VirtAddr)> { + if name.ends_with(".sh") { + args = [vec![String::from("busybox"), String::from("sh")], args].concat(); + return load_app("busybox".to_string(), args, envs, memory_set); + } + let elf_data = if let Ok(ans) = axfs::api::read(name.as_str()) { + ans + } else { + // exit(0) + info!("App not found: {}", name); + return Err(AxError::NotFound); + }; + let elf = xmas_elf::ElfFile::new(&elf_data).expect("Error parsing app ELF file."); + if let Some(interp) = elf + .program_iter() + .find(|ph| ph.get_type() == Ok(xmas_elf::program::Type::Interp)) + { + let interp = match interp.get_data(&elf) { + Ok(SegmentData::Undefined(data)) => data, + _ => panic!("Invalid data in Interp Elf Program Header"), + }; + + let interp_path = from_utf8(interp).expect("Interpreter path isn't valid UTF-8"); + // remove trailing '\0' + let interp_path = interp_path.trim_matches(char::from(0)).to_string(); + let real_interp_path = real_path(&interp_path); + args = [vec![real_interp_path.clone()], args].concat(); + return load_app(real_interp_path, args, envs, memory_set); + } + info!("load app args: {:?} name: {}", args, name); + let elf_base_addr = Some(0x400_0000); + warn!("The elf base addr may be different in different arch!"); + // let (entry, segments, relocate_pairs) = parse_elf(&elf, elf_base_addr); + let entry = get_elf_entry(&elf, elf_base_addr); + let segments = get_elf_segments(&elf, elf_base_addr); + let relocate_pairs = get_relocate_pairs(&elf, elf_base_addr); + for segment in segments { + memory_set.new_region( + segment.vaddr, + segment.size, + false, + segment.flags, + segment.data.as_deref(), + None, + ); + } + + for relocate_pair in relocate_pairs { + let src: usize = relocate_pair.src.into(); + let dst: usize = relocate_pair.dst.into(); + let count = relocate_pair.count; + unsafe { copy_nonoverlapping(src.to_ne_bytes().as_ptr(), dst as *mut u8, count) } + } + + // Now map the stack and the heap + let heap_start = VirtAddr::from(USER_HEAP_BASE); + let heap_data = [0_u8].repeat(MAX_USER_HEAP_SIZE); + memory_set.new_region( + heap_start, + MAX_USER_HEAP_SIZE, + false, + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::USER, + Some(&heap_data), + None, + ); + info!( + "[new region] user heap: [{:?}, {:?})", + heap_start, + heap_start + MAX_USER_HEAP_SIZE + ); + + let auxv = get_auxv_vector(&elf, elf_base_addr); + + let stack_top = VirtAddr::from(USER_STACK_TOP); + let stack_size = MAX_USER_STACK_SIZE; + + let (stack_data, stack_bottom) = get_app_stack_region(args, envs, auxv, stack_top, stack_size); + memory_set.new_region( + stack_top, + stack_size, + false, + MappingFlags::USER | MappingFlags::READ | MappingFlags::WRITE, + Some(&stack_data), + None, + ); + info!( + "[new region] user stack: [{:?}, {:?})", + stack_top, + stack_top + stack_size + ); + Ok((entry, stack_bottom.into(), heap_start)) +} + +/// 当从内核态到用户态时,统计对应进程的时间信息 +pub fn time_stat_from_kernel_to_user() { + let curr_task = current(); + curr_task.time_stat_from_kernel_to_user(current_time_nanos() as usize); +} + +#[no_mangle] +/// 当从用户态到内核态时,统计对应进程的时间信息 +pub fn time_stat_from_user_to_kernel() { + let curr_task = current(); + curr_task.time_stat_from_user_to_kernel(current_time_nanos() as usize); +} + +/// 统计时间输出 +/// (用户态秒,用户态微秒,内核态秒,内核态微秒) +pub fn time_stat_output() -> (usize, usize, usize, usize) { + let curr_task = current(); + let (utime_ns, stime_ns) = curr_task.time_stat_output(); + ( + utime_ns / NANOS_PER_SEC as usize, + utime_ns / NANOS_PER_MICROS as usize, + stime_ns / NANOS_PER_SEC as usize, + stime_ns / NANOS_PER_MICROS as usize, + ) +} + +/// To deal with the page fault +pub fn handle_page_fault(addr: VirtAddr, flags: MappingFlags) { + let current_process = current_process(); + if current_process + .memory_set + .lock() + .lock() + .handle_page_fault(addr, flags) + .is_ok() + { + axhal::arch::flush_tlb(None); + } else { + let _ = send_signal_to_thread(current().id().as_u64() as isize, SignalNo::SIGSEGV as isize); + } +} + +/// 在当前进程找对应的子进程,并等待子进程结束 +/// 若找到了则返回对应的pid +/// 否则返回一个状态 +/// +/// # Safety +/// +/// 保证传入的 ptr 是有效的 +pub unsafe fn wait_pid(pid: i32, exit_code_ptr: *mut i32) -> Result { + // 获取当前进程 + let curr_process = current_process(); + let mut exit_task_id: usize = 0; + let mut answer_id: u64 = 0; + let mut answer_status = WaitStatus::NotExist; + for (index, child) in curr_process.children.lock().iter().enumerate() { + if pid <= 0 { + if pid == 0 { + axlog::warn!("Don't support for process group."); + } + // 任意一个进程结束都可以的 + answer_status = WaitStatus::Running; + if let Some(exit_code) = child.get_code_if_exit() { + answer_status = WaitStatus::Exited; + info!("wait pid _{}_ with code _{}_", child.pid(), exit_code); + exit_task_id = index; + if !exit_code_ptr.is_null() { + unsafe { + // 因为没有切换页表,所以可以直接填写 + *exit_code_ptr = exit_code << 8; + } + } + answer_id = child.pid(); + break; + } + } else if child.pid() == pid as u64 { + // 找到了对应的进程 + if let Some(exit_code) = child.get_code_if_exit() { + answer_status = WaitStatus::Exited; + info!("wait pid _{}_ with code _{:?}_", child.pid(), exit_code); + exit_task_id = index; + if !exit_code_ptr.is_null() { + unsafe { + *exit_code_ptr = exit_code << 8; + // 用于WEXITSTATUS设置编码 + } + } + answer_id = child.pid(); + } else { + answer_status = WaitStatus::Running; + } + break; + } + } + // 若进程成功结束,需要将其从父进程的children中删除 + if answer_status == WaitStatus::Exited { + curr_process.children.lock().remove(exit_task_id); + return Ok(answer_id); + } + Err(answer_status) +} + +/// 以进程作为中转调用task的yield +pub fn yield_now_task() { + axtask::yield_now(); +} + +/// 以进程作为中转调用task的sleep +pub fn sleep_now_task(dur: core::time::Duration) { + axtask::sleep(dur); +} + +/// current running task +pub fn current_task() -> CurrentTask { + axtask::current() +} + +/// 设置当前任务的clear_child_tid +pub fn set_child_tid(tid: usize) { + let curr = current_task(); + curr.set_clear_child_tid(tid); +} + +/// Get the task reference by tid +pub fn get_task_ref(tid: u64) -> Option { + TID2TASK.lock().get(&tid).cloned() +} diff --git a/modules/axprocess/src/fd_manager.rs b/modules/axprocess/src/fd_manager.rs new file mode 100644 index 0000000..fb9cafb --- /dev/null +++ b/modules/axprocess/src/fd_manager.rs @@ -0,0 +1,86 @@ +//! todo 重构fd_table, fd_allocator +extern crate alloc; +use core::sync::atomic::{AtomicI32, AtomicU64}; + +use alloc::string::String; +use alloc::sync::Arc; +use axfs::api::{FileIO, OpenFlags}; +use axlog::info; + +use alloc::vec::Vec; +use axsync::Mutex; + +use crate::stdio::{Stdin, Stdout}; + +pub type FdTable = Arc>>>>; + +pub struct FdManager { + /// 保存文件描述符的数组 + pub fd_table: FdTable, + /// 保存文件描述符的数组的最大长度 + pub limit: AtomicU64, + /// 创建文件时的mode的掩码 + pub umask: Arc, + pub cwd: Arc>, +} + +impl FdManager { + pub fn new( + fd_table: FdTable, + cwd_src: Arc>, + mask_src: Arc, + limit: usize, + ) -> Self { + Self { + fd_table, + limit: AtomicU64::new(limit as u64), + umask: mask_src, + cwd: cwd_src, + } + } + + pub fn get_limit(&self) -> u64 { + self.limit.load(core::sync::atomic::Ordering::Acquire) + } + + pub fn set_limit(&self, new_limit: u64) { + self.limit + .store(new_limit, core::sync::atomic::Ordering::Release) + } + + #[allow(unused)] + pub fn get_mask(&self) -> i32 { + self.umask.load(core::sync::atomic::Ordering::Acquire) + } + + /// 设置新的 mask,返回旧的 mask + pub fn set_mask(&self, new_mask: i32) -> i32 { + let old_mask = self.umask.load(core::sync::atomic::Ordering::Acquire); + self.umask + .store(new_mask, core::sync::atomic::Ordering::Release); + old_mask + } + + /// 在执行 `exec()` 时关闭标记为 `CLOEXEC` 的文件 + pub fn close_on_exec(&self) { + let mut fd_table = self.fd_table.lock(); + for (index, fd) in fd_table.iter_mut().enumerate() { + if let Some(f) = fd { + if f.get_status().is_close_on_exec() { + info!("close fd: {} on exec", index); + fd.take(); + } + } + } + if fd_table[0].is_none() { + fd_table[0] = Some(Arc::new(Stdin { + flags: Mutex::new(OpenFlags::empty()), + })); + } + if fd_table[1].is_none() { + fd_table[1] = Some(Arc::new(Stdout { + flags: Mutex::new(OpenFlags::empty()), + })); + } + } +} diff --git a/modules/axprocess/src/flags.rs b/modules/axprocess/src/flags.rs new file mode 100644 index 0000000..8374bd5 --- /dev/null +++ b/modules/axprocess/src/flags.rs @@ -0,0 +1,59 @@ +//! clone 任务时指定的参数。 + +use bitflags::*; + +bitflags! { + /// 用于 sys_clone 的选项 + #[derive(Debug, Clone, Copy)] + pub struct CloneFlags: u32 { + /// . + const CLONE_NEWTIME = 1 << 7; + /// 共享地址空间 + const CLONE_VM = 1 << 8; + /// 共享文件系统新信息 + const CLONE_FS = 1 << 9; + /// 共享文件描述符(fd)表 + const CLONE_FILES = 1 << 10; + /// 共享信号处理函数 + const CLONE_SIGHAND = 1 << 11; + /// 创建指向子任务的fd,用于 sys_pidfd_open + const CLONE_PIDFD = 1 << 12; + /// 用于 sys_ptrace + const CLONE_PTRACE = 1 << 13; + /// 指定父任务创建后立即阻塞,直到子任务退出才继续 + const CLONE_VFORK = 1 << 14; + /// 指定子任务的 ppid 为当前任务的 ppid,相当于创建“兄弟”而不是“子女” + const CLONE_PARENT = 1 << 15; + /// 作为一个“线程”被创建。具体来说,它同 CLONE_PARENT 一样设置 ppid,且不可被 wait + const CLONE_THREAD = 1 << 16; + /// 子任务使用新的命名空间。目前还未用到 + const CLONE_NEWNS = 1 << 17; + /// 子任务共享同一组信号量。用于 sys_semop + const CLONE_SYSVSEM = 1 << 18; + /// 要求设置 tls + const CLONE_SETTLS = 1 << 19; + /// 要求在父任务的一个地址写入子任务的 tid + const CLONE_PARENT_SETTID = 1 << 20; + /// 要求将子任务的一个地址清零。这个地址会被记录下来,当子任务退出时会触发此处的 futex + const CLONE_CHILD_CLEARTID = 1 << 21; + /// 历史遗留的 flag,现在按 linux 要求应忽略 + const CLONE_DETACHED = 1 << 22; + /// 与 sys_ptrace 相关,目前未用到 + const CLONE_UNTRACED = 1 << 23; + /// 要求在子任务的一个地址写入子任务的 tid + const CLONE_CHILD_SETTID = 1 << 24; + /// New pid namespace. + const CLONE_NEWPID = 1 << 29; + } +} + +/// sys_wait4 的返回值 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WaitStatus { + /// 子任务正常退出 + Exited, + /// 子任务正在运行 + Running, + /// 找不到对应的子任务 + NotExist, +} diff --git a/modules/axprocess/src/futex.rs b/modules/axprocess/src/futex.rs new file mode 100644 index 0000000..5b5e927 --- /dev/null +++ b/modules/axprocess/src/futex.rs @@ -0,0 +1,255 @@ +//! 实现与futex相关的系统调用 +use crate::{current_process, current_task, signal::current_have_signals, yield_now_task}; +use axerrno::LinuxError; +use axfutex::{flags::FutexFlags, futex_hash, FutexKey, FutexQ, FUTEXQUEUES}; +use axhal::mem::VirtAddr; +use axlog::info; +use core::time::Duration; +//use axtask::WaitQueue; + +extern crate alloc; + +type AxSyscallResult = Result; + +/// waiting queue which stores tasks waiting for futex variable +//pub static WAIT_FOR_FUTEX: WaitQueue = WaitQueue::new(); + +#[derive(Default)] +/// 用于存储 robust list 的结构 +pub struct FutexRobustList { + /// The location of the head of the robust list in user space + pub head: usize, + /// The length of the robust list + pub len: usize, +} + +impl FutexRobustList { + /// Create a new robust list + pub fn new(head: usize, len: usize) -> Self { + Self { head, len } + } +} + +fn futex_get_value_locked(vaddr: VirtAddr) -> AxSyscallResult { + let process = current_process(); + if process.manual_alloc_for_lazy(vaddr).is_ok() { + let real_futex_val = unsafe { (vaddr.as_usize() as *const u32).read_volatile() }; + Ok(real_futex_val as isize) + } else { + Err(LinuxError::EFAULT) + } +} + +fn get_futex_key(uaddr: VirtAddr, flags: &FutexFlags) -> FutexKey { + if flags.contains(FutexFlags::SHARED) { + /* Todo: after implement inode layer + let inode = uaddr.get_inode(); + let page_index = uaddr.get_page_index(); + let offset = uaddr.get_offset(); + FutexKey::new(inode, page_index, offset) + */ + // TODO: Distinguishing between anonymous and file mappings + let pid = 0; + let aligned = uaddr.align_down_4k().as_usize(); + let offset = uaddr.align_offset_4k() as u32; + FutexKey::new(pid, aligned, offset) + } else { + let pid = current_process().pid(); + let aligned = uaddr.align_down_4k().as_usize(); + let offset = uaddr.align_offset_4k() as u32; + FutexKey::new(pid, aligned, offset) + } +} + +/// Wait on a futex variable +/// +/// Details: +pub fn futex_wait( + vaddr: VirtAddr, + flags: FutexFlags, + expected_val: u32, + deadline: Option, + bitset: u32, +) -> AxSyscallResult { + info!( + "[futex_wait] current task: {:?}, vaddr: {:?}, flags: {:?}, val: {:?}, deadline: {:?}", + current_task().id(), + vaddr, + flags, + expected_val, + deadline + ); + let mut is_timeout = false; + + // we may be victim of spurious wakeups, so we need to loop + loop { + let key = get_futex_key(vaddr, &flags); + let real_futex_val = futex_get_value_locked(vaddr)?; + if expected_val != real_futex_val as u32 { + return Err(LinuxError::EAGAIN); + } + // 比较后相等,放入等待队列 + let mut hash_bucket = FUTEXQUEUES.buckets[futex_hash(&key)].lock(); + let cur_futexq = FutexQ::new(key, current_task().as_task_ref().clone(), bitset); + hash_bucket.push_back(cur_futexq); + + // drop lock to avoid deadlock + drop(hash_bucket); + + /* There is something wrong with WaitQueues, tasks are woken up unexpectedly + if let Some(deadline) = deadline { + // There is something wrong with WaitQueues + is_tiemout = WAIT_FOR_FUTEX.wait_timeout(deadline); + } + else { + // If timeout is NULL, the operation can block indefinitely. + yield_now_task(); + } + */ + + if let Some(deadline) = deadline { + let now = axhal::time::current_time(); + is_timeout = deadline < now; + } + if deadline.is_none() || !is_timeout { + yield_now_task(); + } + // If we were woken (and unqueued), we succeeded, whatever. + // We doesn't care about the reason of wakeup if we were unqueued. + let mut hash_bucket = FUTEXQUEUES.buckets[futex_hash(&key)].lock(); + let cur_id = current_task().id().as_u64(); + //if let Some(idx) = hash_bucket.iter().position(|futex_q| futex_q.task.id().as_u64() == cur_id) { + if let Some(idx) = hash_bucket + .iter() + .position(|futex_q| futex_q.task.id().as_u64() == cur_id) + { + hash_bucket.remove(idx); + if is_timeout { + return Err(LinuxError::ETIMEDOUT); + } + if current_have_signals() { + return Err(LinuxError::EINTR); + } + } else { + // the task is woken up anyway + return Ok(0); + } + } +} + +/// Wake up tasks waiting on a futex variable +/// +/// Details: +/// +/// Tips: There is no need to check the bitset, faster than futex_wake_bitset +pub fn futex_wake(vaddr: VirtAddr, flags: FutexFlags, nr_waken: u32) -> AxSyscallResult { + info!( + "[futex_wake] vaddr: {:?}, flags: {:?}, nr_waken: {:?}", + vaddr, flags, nr_waken + ); + let mut ret = 0; + let key = get_futex_key(vaddr, &flags); + let mut hash_bucket = FUTEXQUEUES.buckets[futex_hash(&key)].lock(); + + if hash_bucket.is_empty() { + info!("hash_bucket is empty"); + return Ok(0); + } else { + hash_bucket.retain(|futex_q| { + if ret < nr_waken && futex_q.key == key { + //let wake_up = WAIT_FOR_FUTEX.notify_task(&futex_q.task); + info!("wake up task {:?}", futex_q.task.id()); + ret += 1; + return false; + } + true + }) + } + // drop hash_bucket to avoid deadlock + drop(hash_bucket); + Ok(ret as isize) +} + +/// Wake up tasks specified by a bitset waiting on a futex variable +pub fn futex_wake_bitset( + vaddr: VirtAddr, + flags: FutexFlags, + nr_waken: u32, + bitset: u32, +) -> AxSyscallResult { + info!( + "[futex_wake_bitset] vaddr: {:?}, flags: {:?}, nr_waken: {:?}, bitset: {:x}", + vaddr, flags, nr_waken, bitset + ); + if bitset == 0 { + return Err(LinuxError::EINVAL); + } + let mut ret = 0; + let key = get_futex_key(vaddr, &flags); + let mut hash_bucket = FUTEXQUEUES.buckets[futex_hash(&key)].lock(); + if hash_bucket.is_empty() { + return Ok(0); + } else { + hash_bucket.retain(|futex_q| { + if ret == nr_waken { + return true; + } + if (futex_q.bitset & bitset) != 0 && futex_q.key == key { + //WAIT_FOR_FUTEX.notify_task(&futex_q.task); + ret += 1; + return false; + } + true + }) + } + // drop hash_bucket to avoid deadlock + drop(hash_bucket); + Ok(ret as isize) +} + +/// Requeue tasks waiting on a futex variable +pub fn futex_requeue( + uaddr: VirtAddr, + flags: FutexFlags, + nr_waken: u32, + uaddr2: VirtAddr, + nr_requeue: u32, +) -> AxSyscallResult { + let mut ret = 0; + let mut requeued = 0; + let key = get_futex_key(uaddr, &flags); + let req_key = get_futex_key(uaddr2, &flags); + + if key == req_key { + return futex_wake(uaddr, flags, nr_waken); + } + + let mut hash_bucket = FUTEXQUEUES.buckets[futex_hash(&key)].lock(); + if hash_bucket.is_empty() { + return Ok(0); + } else { + while let Some(futex_q) = hash_bucket.pop_front() { + if futex_q.key == key { + //WAIT_FOR_FUTEX.notify_task(&futex_q.task); + ret += 1; + if ret == nr_waken { + break; + } + } + } + if hash_bucket.is_empty() { + return Ok(ret as isize); + } + // requeue the rest of the waiters + let mut req_bucket = FUTEXQUEUES.buckets[futex_hash(&req_key)].lock(); + while let Some(futex_q) = hash_bucket.pop_front() { + req_bucket.push_back(futex_q); + requeued += 1; + if requeued == nr_requeue { + break; + } + } + } + drop(hash_bucket); + Ok(ret as isize) +} diff --git a/modules/axprocess/src/lib.rs b/modules/axprocess/src/lib.rs new file mode 100644 index 0000000..b6f7dc1 --- /dev/null +++ b/modules/axprocess/src/lib.rs @@ -0,0 +1,16 @@ +//! This module provides the process management API for the operating system. + +#![cfg_attr(not(test), no_std)] +mod api; +pub use api::*; +mod process; +pub use process::{Process, PID2PC, TID2TASK}; + +pub mod flags; +pub mod futex; +pub mod link; +mod stdio; + +mod fd_manager; + +pub mod signal; diff --git a/modules/axprocess/src/link.rs b/modules/axprocess/src/link.rs new file mode 100644 index 0000000..c79bf4c --- /dev/null +++ b/modules/axprocess/src/link.rs @@ -0,0 +1,377 @@ +//! 模拟的链接、挂载模块 +//! fat32本身不支持符号链接和硬链接,两个指向相同文件的目录条目将会被chkdsk报告为交叉链接并修复 +extern crate alloc; +use alloc::collections::BTreeMap; +use alloc::format; +use alloc::string::{String, ToString}; +use axerrno::{AxError, AxResult}; +use axfs::api::{canonicalize, path_exists, remove_file, FileIOType}; +use axlog::{debug, info, trace}; +use axsync::Mutex; + +use crate::current_process; +#[allow(unused)] +/// The file descriptor used to specify the current working directory of a process +pub const AT_FDCWD: usize = -100isize as usize; +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] +/// A struct to represent a file path, which will be canonicalized +pub struct FilePath(String); + +impl FilePath { + /// 创建一个 FilePath, 传入的 path 会被 canonicalize, 故可以是相对路径 + pub fn new(path: &str) -> AxResult { + let new_path = canonicalize(path); + if new_path.is_err() { + return Err(AxError::NotFound); + } + let mut new_path = String::from(new_path.unwrap().trim()); + // canonicalize中没有处理末尾的空格、换行符等 + if path.ends_with('/') && !new_path.ends_with('/') { + // 如果原始路径以 '/' 结尾,那么canonicalize后的路径也应该以 '/' 结尾 + new_path.push('/'); + } + let new_path = real_path(&new_path); + // assert!(!path.ends_with("/"), "path should not end with '/', link only support file"); // 链接只支持文件 + Ok(Self(new_path)) + } + + /// 获取路径 + pub fn path(&self) -> &str { + &self.0 + } + /// 获取所属目录 + #[allow(unused)] + pub fn dir(&self) -> AxResult<&str> { + if self.is_root() { + return Ok("/"); + } + let mut pos = if let Some(pos) = self.0.rfind('/') { + pos + } else { + return Err(AxError::NotADirectory); + }; + if pos == self.0.len() - 1 { + // 如果是以 '/' 结尾,那么再往前找一次 + pos = if let Some(pos) = self.0[..pos].rfind('/') { + pos + } else { + return Err(AxError::NotADirectory); + }; + } + Ok(&self.0[..=pos]) + } + /// 获取文件/目录名 + #[allow(unused)] + pub fn file(&self) -> AxResult<&str> { + if self.is_root() { + return Ok("/"); + } + let mut pos = if let Some(pos) = self.0.rfind('/') { + pos + } else { + return Err(AxError::NotFound); + }; + if pos == self.0.len() - 1 { + pos = if let Some(pos) = self.0[..pos].rfind('/') { + pos + } else { + return Err(AxError::NotFound); + }; + } + Ok(&self.0[pos + 1..]) + } + /// 返回是否是根目录 + #[allow(unused)] + pub fn is_root(&self) -> bool { + self.0 == "/" + } + /// 返回是否是目录 + pub fn is_dir(&self) -> bool { + self.0.ends_with('/') + } + /// 返回是否是文件 + pub fn is_file(&self) -> bool { + !self.0.ends_with('/') + } + /// 判断是否相同 + pub fn equal_to(&self, other: &Self) -> bool { + self.0 == other.0 + } + // /// 判断是否实际存在于文件系统(而不是只有链接) + // pub fn exists(&self) -> bool { + // let path = self.0.clone(); + // path_exists(path.as_str()) + // } + /// 判断是否start_with + pub fn start_with(&self, other: &Self) -> bool { + self.0.starts_with(other.0.as_str()) + } + /// 判断是否end_with + #[allow(unused)] + pub fn end_with(&self, other: &Self) -> bool { + self.0.ends_with(other.0.as_str()) + } +} +#[allow(unused)] +/// # Safety +/// +/// The caller must ensure that the pointer is valid and points to a valid C string. +/// The string must be null-terminated. +pub unsafe fn get_str_len(start: *const u8) -> usize { + let mut ptr = start as usize; + while *(ptr as *const u8) != 0 { + ptr += 1; + } + ptr - start as usize +} + +#[allow(unused)] +/// # Safety +/// +/// The caller must ensure that the pointer is valid and points to a valid C string. +pub unsafe fn raw_ptr_to_ref_str(start: *const u8) -> &'static str { + let len = unsafe { get_str_len(start) }; + // 因为这里直接用用户空间提供的虚拟地址来访问,所以一定能连续访问到字符串,不需要考虑物理地址是否连续 + let slice = unsafe { core::slice::from_raw_parts(start, len) }; + if let Ok(s) = core::str::from_utf8(slice) { + s + } else { + axlog::error!("not utf8 slice"); + for c in slice { + axlog::error!("{c} "); + } + axlog::error!(""); + "" + } +} + +/// 用户看到的文件到实际文件的映射 +static LINK_PATH_MAP: Mutex> = Mutex::new(BTreeMap::new()); +/// 实际文件(而不是用户文件)到链接数的映射 +pub static LINK_COUNT_MAP: Mutex> = Mutex::new(BTreeMap::new()); + +/// 将用户提供的路径转换成实际的路径 +/// +/// 如果在链接列表中找不到,则直接返回自己 +pub fn real_path(src_path: &String) -> String { + trace!("parse_file_name: {}", src_path); + let map = LINK_PATH_MAP.lock(); + // 找到对应的链接 + match map.get(src_path) { + Some(dest_path) => dest_path.clone(), + None => { + // 特判gcc的文件夹链接情况,即将一个文件夹前缀换成另一个文件夹前缀 + static GCC_DIR_SRC: &str = + "/riscv64-linux-musl-native/lib/gcc/riscv64-linux-musl/11.2.1/include"; + static GCC_DIR_DST: &str = "/riscv64-linux-musl-native/include"; + + static MUSL_DIR_SRC: &str = "/riscv64-linux-musl-native/riscv64-linux-musl/include"; + static MUSL_DIR_DST: &str = "/riscv64-linux-musl-native/include"; + if src_path.starts_with(GCC_DIR_SRC) { + // 替换src为dst + GCC_DIR_DST.to_string() + src_path.strip_prefix(GCC_DIR_SRC).unwrap() + } else if src_path.starts_with(MUSL_DIR_SRC) { + // 替换src为dst + MUSL_DIR_DST.to_string() + src_path.strip_prefix(MUSL_DIR_SRC).unwrap() + } else { + src_path.clone() + } + } + } +} + +/// 删除一个链接 +/// +/// 如果在 map 中找不到对应链接,则什么都不做 +/// 返回被删除的链接指向的文件 +/// +/// 现在的一个问题是,如果建立了dir1/A,并将dir2/B链接到dir1/A,那么删除dir1/A时,实际的文件不会被删除(连接数依然大于1),只有当删除dir2/B时,实际的文件才会被删除 +/// 这样的话,如果新建了dir1/A,那么就会报错(create_new)或者覆盖原文件(create),从而影响到dir2/B +pub fn remove_link(src_path: &FilePath) -> Option { + trace!("remove_link: {}", src_path.path()); + let mut map = LINK_PATH_MAP.lock(); + // 找到对应的链接 + match map.remove(&src_path.path().to_string()) { + Some(dest_path) => { + // 更新链接数 + let mut count_map = LINK_COUNT_MAP.lock(); + let count = count_map.entry(dest_path.clone()).or_insert(0); + assert!(*count > 0, "before removing, the link count should > 0"); + *count -= 1; + // 如果链接数为0,那么删除文件 + if *count == 0 { + debug!("link num down to zero, remove file: {}", dest_path); + let _ = remove_file(dest_path.as_str()); + } + Some(dest_path) + } + None => None, + } +} + +/// 获取文件的链接数 +/// +/// 如果文件不存在,那么返回 0 +/// 如果文件存在,但是没有链接,那么返回 1 +/// 如果文件存在,且有链接,那么返回链接数 +pub fn get_link_count(src_path: &String) -> usize { + trace!("get_link_count: {}", src_path); + let map = LINK_PATH_MAP.lock(); + // 找到对应的链接 + match map.get(src_path) { + Some(dest_path) => { + let count_map = LINK_COUNT_MAP.lock(); + let count = count_map.get(dest_path).unwrap(); + *count + } + None => { + // if path_exists(src_path.path()) { + // 1 + // } else { + // 0 + // } + 0 + } + } +} + +/// 创建一个链接 +/// +/// 返回是否创建成功(已存在的链接也会返回 true) +/// 创建新文件时注意调用该函数创建链接 +pub fn create_link(src_path: &FilePath, dest_path: &FilePath) -> bool { + info!("create_link: {} -> {}", src_path.path(), dest_path.path()); + // assert!(src_path.is_file() && dest_path.is_file(), "link only support file"); + // assert_ne!(src_path.path(), dest_path.path(), "link src and dest should not be the same"); // 否则在第一步删除旧链接时可能会删除源文件 + // 检查是否是文件 + if !src_path.is_file() || !dest_path.is_file() { + debug!("link only support file"); + return false; + } + // 检查被链接到的文件是否存在 + if !path_exists(dest_path.path()) { + debug!("link dest file not exists"); + return false; + } + + // 一次性锁定LINK_PATH_MAP,避免重复加锁解锁 + let mut map = LINK_PATH_MAP.lock(); + + // 检查链接是否已存在,并处理旧链接 + if let Some(old_dest_path) = map.get(&src_path.path().to_string()) { + if old_dest_path != &dest_path.path().to_string() { + // 旧链接存在且与新链接不同,移除旧链接 + drop(map); // 释放锁,因为remove_link可能需要锁 + remove_link(src_path); + map = LINK_PATH_MAP.lock(); // 重新获取锁 + } else { + // 链接已存在且相同,无需进一步操作 + debug!("link already exists"); + return true; + } + } + + // 创建新链接 + map.insert( + src_path.path().to_string(), + dest_path.path().to_string().clone(), + ); + + // 更新链接计数 + let mut count_map = LINK_COUNT_MAP.lock(); + let count = count_map.entry(dest_path.path().to_string()).or_insert(0); + *count += 1; + true +} + +/// To deal with the path and return the canonicalized path +/// +/// * `dir_fd` - The file descriptor of the directory, if it is AT_FDCWD, the call operates on the current working directory +/// +/// * `path_addr` - The address of the path, if it is null, the call operates on the file that is specified by `dir_fd` +/// +/// * `force_dir` - If true, the path will be treated as a directory +/// +/// The path will be dealt with links and the path will be canonicalized +pub fn deal_with_path( + dir_fd: usize, + path_addr: Option<*const u8>, + force_dir: bool, +) -> AxResult { + let process = current_process(); + let mut path = "".to_string(); + if let Some(path_addr) = path_addr { + if path_addr.is_null() { + axlog::warn!("path address is null"); + return Err(AxError::BadAddress); + } + process + .manual_alloc_for_lazy((path_addr as usize).into()) + .map(|_| { + path.clone_from(&unsafe { raw_ptr_to_ref_str(path_addr) }.to_string()); + })?; + } + + if path.is_empty() { + // If pathname is an empty string, in this case, dirfd can refer to any type of file, not just a directory + // and the behavior of fstatat() is similar to that of fstat() + // If dirfd is AT_FDCWD, the call operates on the current working directory. + if dir_fd == AT_FDCWD && dir_fd as u32 == AT_FDCWD as u32 { + // return Some(FilePath::new(".").unwrap()); + path = String::from("."); + } else { + let fd_table = process.fd_manager.fd_table.lock(); + if dir_fd >= fd_table.len() { + axlog::warn!("fd index out of range"); + return Err(AxError::InvalidInput); + } + match fd_table[dir_fd].as_ref() { + Some(dir) => { + let dir = dir.clone(); + path = dir.get_path(); + } + None => { + axlog::warn!("fd not exist"); + return Err(AxError::NotFound); + } + } + } + } else if !path.starts_with('/') && dir_fd != AT_FDCWD && dir_fd as u32 != AT_FDCWD as u32 { + // 如果不是绝对路径, 且dir_fd不是AT_FDCWD, 则需要将dir_fd和path拼接起来 + let fd_table = process.fd_manager.fd_table.lock(); + if dir_fd >= fd_table.len() { + axlog::warn!("fd index out of range"); + return Err(AxError::InvalidInput); + } + match fd_table[dir_fd].as_ref() { + Some(dir) => { + if dir.get_type() != FileIOType::DirDesc { + axlog::warn!("selected fd {} is not a dir", dir_fd); + return Err(AxError::NotADirectory); + } + let dir = dir.clone(); + // 有没有可能dir的尾部一定是一个/号,所以不用手工添加/ + path = format!("{}{}", dir.get_path(), path); + axlog::warn!("handled_path: {} dir: {}", path, dir.get_path()); + } + None => { + axlog::warn!("fd not exist"); + return Err(AxError::NotFound); + } + } + } + if !path.starts_with('/') { + // 如果path不是绝对路径, 则加上当前工作目录 + let cwd = process.get_cwd(); + assert!(cwd.ends_with('/')); + path = format!("{}{}", cwd, path); + } + if force_dir && !path.ends_with('/') { + path = format!("{}/", path); + } + if path.ends_with('.') { + // 如果path以.或..结尾, 则加上/告诉FilePath::new它是一个目录 + path = format!("{}/", path); + } + FilePath::new(path.as_str()) +} diff --git a/modules/axprocess/src/process.rs b/modules/axprocess/src/process.rs new file mode 100644 index 0000000..7e4c4e9 --- /dev/null +++ b/modules/axprocess/src/process.rs @@ -0,0 +1,840 @@ +//! 规定进程控制块内容 +extern crate alloc; +use alloc::format; +use alloc::string::ToString; +use alloc::sync::Arc; +use alloc::vec; +use alloc::vec::Vec; +use alloc::{collections::BTreeMap, string::String}; +use axerrno::{AxError, AxResult}; +use axfs::api::{FileIO, OpenFlags}; +use axhal::arch::{ + read_trapframe_from_kstack, write_page_table_root0, write_trapframe_to_kstack, TrapFrame, +}; +use axhal::mem::{phys_to_virt, VirtAddr}; + +use axhal::time::current_time_nanos; +use axhal::KERNEL_PROCESS_ID; +use axlog::{debug, error}; +use axmem::MemorySet; +use axsignal::signal_no::SignalNo; +use axsync::Mutex; +use axtask::{current, new_task, AxTaskRef, Processor, TaskId}; +use core::sync::atomic::{AtomicBool, AtomicI32, AtomicU64, Ordering}; + +use crate::fd_manager::{FdManager, FdTable}; +use crate::flags::CloneFlags; +use crate::futex::FutexRobustList; + +use crate::signal::SignalModule; +use crate::stdio::{Stderr, Stdin, Stdout}; +use crate::{load_app, yield_now_task}; + +/// Map from task id to arc pointer of task +pub static TID2TASK: Mutex> = Mutex::new(BTreeMap::new()); + +/// Map from process id to arc pointer of process +pub static PID2PC: Mutex>> = Mutex::new(BTreeMap::new()); +const FD_LIMIT_ORIGIN: usize = 1025; + +extern "C" { + fn start_signal_trampoline(); +} + +/// The process control block +pub struct Process { + /// 进程号 + pid: u64, + + /// 父进程号 + pub parent: AtomicU64, + + /// 栈大小 + pub stack_size: AtomicU64, + + /// 子进程 + pub children: Mutex>>, + + /// 所管理的线程 + pub tasks: Mutex>, + + /// 文件描述符管理器 + pub fd_manager: FdManager, + + /// 进程状态 + pub is_zombie: AtomicBool, + + /// 退出状态码 + pub exit_code: AtomicI32, + + /// 地址空间 + pub memory_set: Mutex>>, + + /// 用户堆基址,任何时候堆顶都不能比这个值小,理论上讲是一个常量 + pub heap_bottom: AtomicU64, + + /// 当前用户堆的堆顶,不能小于基址,不能大于基址加堆的最大大小 + pub heap_top: AtomicU64, + + /// 信号处理模块 + /// 第一维代表TaskID,第二维代表对应的信号处理模块 + pub signal_modules: Mutex>, + + /// robust list存储模块 + /// 用来存储线程对共享变量的使用地址 + /// 具体使用交给了用户空间 + pub robust_list: Mutex>, + + /// 是否被vfork阻塞 + pub blocked_by_vfork: Mutex, + + /// 该进程可执行文件所在的路径 + pub file_path: Mutex, +} + +impl Process { + /// get the process id + pub fn pid(&self) -> u64 { + self.pid + } + + /// TODO: 改变了新创建的任务栈大小,但未实现当前任务的栈扩展 + /// set stack size + pub fn set_stack_limit(&self, limit: u64) { + self.stack_size.store(limit, Ordering::Release) + } + + /// get stack size + pub fn get_stack_limit(&self) -> u64 { + self.stack_size.load(Ordering::Acquire) + } + + /// get the parent process id + pub fn get_parent(&self) -> u64 { + self.parent.load(Ordering::Acquire) + } + + /// set the parent process id + pub fn set_parent(&self, parent: u64) { + self.parent.store(parent, Ordering::Release) + } + + /// get the exit code of the process + pub fn get_exit_code(&self) -> i32 { + self.exit_code.load(Ordering::Acquire) + } + + /// set the exit code of the process + pub fn set_exit_code(&self, exit_code: i32) { + self.exit_code.store(exit_code, Ordering::Release) + } + + /// whether the process is a zombie process + pub fn get_zombie(&self) -> bool { + self.is_zombie.load(Ordering::Acquire) + } + + /// set the process as a zombie process + pub fn set_zombie(&self, status: bool) { + self.is_zombie.store(status, Ordering::Release) + } + + /// get the heap top of the process + pub fn get_heap_top(&self) -> u64 { + self.heap_top.load(Ordering::Acquire) + } + + /// set the heap top of the process + pub fn set_heap_top(&self, top: u64) { + self.heap_top.store(top, Ordering::Release) + } + + /// get the heap bottom of the process + pub fn get_heap_bottom(&self) -> u64 { + self.heap_bottom.load(Ordering::Acquire) + } + + /// set the heap bottom of the process + pub fn set_heap_bottom(&self, bottom: u64) { + self.heap_bottom.store(bottom, Ordering::Release) + } + + /// set the process as blocked by vfork + pub fn set_vfork_block(&self, value: bool) { + *self.blocked_by_vfork.lock() = value; + } + + /// get the process is blocked by vfork or not + pub fn get_vfork_block(&self) -> bool { + *self.blocked_by_vfork.lock() + } + + /// set the executable file path of the process + pub fn set_file_path(&self, path: String) { + let mut file_path = self.file_path.lock(); + *file_path = path; + } + + /// get the executable file path of the process + pub fn get_file_path(&self) -> String { + (*self.file_path.lock()).clone() + } + + /// 若进程运行完成,则获取其返回码 + /// 若正在运行(可能上锁或没有上锁),则返回None + pub fn get_code_if_exit(&self) -> Option { + if self.get_zombie() { + return Some(self.get_exit_code()); + } + None + } +} + +impl Process { + /// 创建一个新的进程 + pub fn new( + pid: u64, + stack_size: u64, + parent: u64, + memory_set: Mutex>>, + heap_bottom: u64, + cwd: Arc>, + mask: Arc, + fd_table: FdTable, + ) -> Self { + Self { + pid, + stack_size: AtomicU64::new(stack_size), + parent: AtomicU64::new(parent), + children: Mutex::new(Vec::new()), + tasks: Mutex::new(Vec::new()), + is_zombie: AtomicBool::new(false), + exit_code: AtomicI32::new(0), + memory_set, + heap_bottom: AtomicU64::new(heap_bottom), + heap_top: AtomicU64::new(heap_bottom), + fd_manager: FdManager::new(fd_table, cwd, mask, FD_LIMIT_ORIGIN), + + signal_modules: Mutex::new(BTreeMap::new()), + robust_list: Mutex::new(BTreeMap::new()), + blocked_by_vfork: Mutex::new(false), + file_path: Mutex::new(String::new()), + } + } + /// 根据给定参数创建一个新的进程,作为应用程序初始进程 + pub fn init(args: Vec, envs: &Vec) -> AxResult { + let mut path = args[0].clone(); + let mut memory_set = MemorySet::new_memory_set(); + + { + use axhal::mem::virt_to_phys; + use axhal::paging::MappingFlags; + // 生成信号跳板 + let signal_trampoline_vaddr: VirtAddr = (axconfig::SIGNAL_TRAMPOLINE).into(); + let signal_trampoline_paddr = virt_to_phys((start_signal_trampoline as usize).into()); + memory_set.map_page_without_alloc( + signal_trampoline_vaddr, + signal_trampoline_paddr, + MappingFlags::READ + | MappingFlags::EXECUTE + | MappingFlags::USER + | MappingFlags::WRITE, + )?; + } + let page_table_token = memory_set.page_table_token(); + if page_table_token != 0 { + unsafe { + write_page_table_root0(page_table_token.into()); + #[cfg(target_arch = "riscv64")] + riscv::register::sstatus::set_sum(); + }; + } + + let (entry, user_stack_bottom, heap_bottom) = + if let Ok(ans) = load_app(path.clone(), args, envs, &mut memory_set) { + ans + } else { + error!("Failed to load app {}", path); + return Err(AxError::NotFound); + }; + + let new_fd_table: FdTable = Arc::new(Mutex::new(vec![ + // 标准输入 + Some(Arc::new(Stdin { + flags: Mutex::new(OpenFlags::empty()), + })), + // 标准输出 + Some(Arc::new(Stdout { + flags: Mutex::new(OpenFlags::empty()), + })), + // 标准错误 + Some(Arc::new(Stderr { + flags: Mutex::new(OpenFlags::empty()), + })), + ])); + let new_process = Arc::new(Self::new( + TaskId::new().as_u64(), + axconfig::TASK_STACK_SIZE as u64, + KERNEL_PROCESS_ID, + Mutex::new(Arc::new(Mutex::new(memory_set))), + heap_bottom.as_usize() as u64, + Arc::new(Mutex::new(String::from("/"))), + Arc::new(AtomicI32::new(0o022)), + new_fd_table, + )); + if !path.starts_with('/') { + //如果path不是绝对路径, 则加上当前工作目录 + let cwd = new_process.get_cwd(); + assert!(cwd.ends_with('/')); + path = format!("{}{}", cwd, path); + } + new_process.set_file_path(path.clone()); + let new_task = new_task( + || {}, + path, + new_process.get_stack_limit() as usize, + new_process.pid(), + page_table_token, + ); + TID2TASK + .lock() + .insert(new_task.id().as_u64(), Arc::clone(&new_task)); + new_task.set_leader(true); + let new_trap_frame = + TrapFrame::app_init_context(entry.as_usize(), user_stack_bottom.as_usize()); + // // 需要将完整内容写入到内核栈上,first_into_user并不会复制到内核栈上 + write_trapframe_to_kstack(new_task.get_kernel_stack_top().unwrap(), &new_trap_frame); + new_process.tasks.lock().push(Arc::clone(&new_task)); + + new_process + .signal_modules + .lock() + .insert(new_task.id().as_u64(), SignalModule::init_signal(None)); + new_process + .robust_list + .lock() + .insert(new_task.id().as_u64(), FutexRobustList::default()); + PID2PC + .lock() + .insert(new_process.pid(), Arc::clone(&new_process)); + // 将其作为内核进程的子进程 + match PID2PC.lock().get(&KERNEL_PROCESS_ID) { + Some(kernel_process) => { + kernel_process.children.lock().push(new_process); + } + None => { + return Err(AxError::NotFound); + } + } + Processor::first_add_task(Arc::clone(&new_task)); + Ok(new_task) + } +} + +impl Process { + /// 将当前进程替换为指定的用户程序 + /// args为传入的参数 + /// 任务的统计时间会被重置 + pub fn exec(&self, name: String, args: Vec, envs: &Vec) -> AxResult<()> { + // 首先要处理原先进程的资源 + // 处理分配的页帧 + // 之后加入额外的东西之后再处理其他的包括信号等因素 + // 不是直接删除原有地址空间,否则构建成本较高。 + + if Arc::strong_count(&self.memory_set.lock()) == 1 { + self.memory_set.lock().lock().unmap_user_areas(); + } else { + let memory_set = Arc::new(Mutex::new(MemorySet::clone_or_err( + &mut self.memory_set.lock().lock(), + )?)); + *self.memory_set.lock() = memory_set; + self.memory_set.lock().lock().unmap_user_areas(); + let new_page_table = self.memory_set.lock().lock().page_table_token(); + let mut tasks = self.tasks.lock(); + for task in tasks.iter_mut() { + task.inner().set_page_table_token(new_page_table); + } + // 切换到新的页表上 + unsafe { + axhal::arch::write_page_table_root0(new_page_table.into()); + } + } + // 清空用户堆,重置堆顶 + axhal::arch::flush_tlb(None); + + // 关闭 `CLOEXEC` 的文件 + self.fd_manager.close_on_exec(); + let current_task = current(); + // 再考虑手动结束其他所有的task + let mut tasks = self.tasks.lock(); + for _ in 0..tasks.len() { + let task = tasks.pop().unwrap(); + if task.id() == current_task.id() { + // FIXME: This will reset tls forcefully + #[cfg(target_arch = "x86_64")] + unsafe { + task.set_tls_force(0); + axhal::arch::write_thread_pointer(0); + } + tasks.push(task); + } else { + TID2TASK.lock().remove(&task.id().as_u64()); + panic!("currently not support exec when has another task "); + } + } + // 当前任务被设置为主线程 + current_task.set_leader(true); + // 重置统计时间 + current_task.reset_time_stat(current_time_nanos() as usize); + current_task.set_name(name.split('/').last().unwrap()); + assert!(tasks.len() == 1); + drop(tasks); + let args = if args.is_empty() { + vec![name.clone()] + } else { + args + }; + let (entry, user_stack_bottom, heap_bottom) = if let Ok(ans) = + load_app(name.clone(), args, envs, &mut self.memory_set.lock().lock()) + { + ans + } else { + error!("Failed to load app {}", name); + return Err(AxError::NotFound); + }; + // 切换了地址空间, 需要切换token + let page_table_token = if self.pid == KERNEL_PROCESS_ID { + 0 + } else { + self.memory_set.lock().lock().page_table_token() + }; + if page_table_token != 0 { + unsafe { + write_page_table_root0(page_table_token.into()); + }; + // 清空用户堆,重置堆顶 + } + // 重置用户堆 + self.set_heap_bottom(heap_bottom.as_usize() as u64); + self.set_heap_top(heap_bottom.as_usize() as u64); + // // 重置robust list + self.robust_list.lock().clear(); + self.robust_list + .lock() + .insert(current_task.id().as_u64(), FutexRobustList::default()); + + { + use axhal::mem::virt_to_phys; + use axhal::paging::MappingFlags; + // 重置信号处理模块 + // 此时只会留下一个线程 + self.signal_modules.lock().clear(); + self.signal_modules + .lock() + .insert(current_task.id().as_u64(), SignalModule::init_signal(None)); + + // 生成信号跳板 + let signal_trampoline_vaddr: VirtAddr = (axconfig::SIGNAL_TRAMPOLINE).into(); + let signal_trampoline_paddr = virt_to_phys((start_signal_trampoline as usize).into()); + let memory_set_wrapper = self.memory_set.lock(); + let mut memory_set = memory_set_wrapper.lock(); + if memory_set.query(signal_trampoline_vaddr).is_err() { + let _ = memory_set.map_page_without_alloc( + signal_trampoline_vaddr, + signal_trampoline_paddr, + MappingFlags::READ + | MappingFlags::EXECUTE + | MappingFlags::USER + | MappingFlags::WRITE, + ); + } + drop(memory_set); + } + + // user_stack_top = user_stack_top / PAGE_SIZE_4K * PAGE_SIZE_4K; + let new_trap_frame = + TrapFrame::app_init_context(entry.as_usize(), user_stack_bottom.as_usize()); + write_trapframe_to_kstack( + current_task.get_kernel_stack_top().unwrap(), + &new_trap_frame, + ); + + // release vfork for parent process + { + let pid2pc = PID2PC.lock(); + let parent_process = pid2pc.get(&self.get_parent()).unwrap(); + parent_process.set_vfork_block(false); + drop(pid2pc); + } + Ok(()) + } + + /// 实现简易的clone系统调用 + /// 返回值为新产生的任务的id + pub fn clone_task( + &self, + flags: usize, + stack: Option, + ptid: usize, + tls: usize, + ctid: usize, + exit_signal: Option, + ) -> AxResult { + let clone_flags = CloneFlags::from_bits((flags & !0x3f) as u32).unwrap(); + // 是否共享虚拟地址空间 + let new_memory_set = if clone_flags.contains(CloneFlags::CLONE_VM) { + Mutex::new(Arc::clone(&self.memory_set.lock())) + } else { + let memory_set = Arc::new(Mutex::new(MemorySet::clone_or_err( + &mut self.memory_set.lock().lock(), + )?)); + + { + use axhal::mem::virt_to_phys; + use axhal::paging::MappingFlags; + // 生成信号跳板 + let signal_trampoline_vaddr: VirtAddr = (axconfig::SIGNAL_TRAMPOLINE).into(); + let signal_trampoline_paddr = + virt_to_phys((start_signal_trampoline as usize).into()); + memory_set.lock().map_page_without_alloc( + signal_trampoline_vaddr, + signal_trampoline_paddr, + MappingFlags::READ + | MappingFlags::EXECUTE + | MappingFlags::USER + | MappingFlags::WRITE, + )?; + } + Mutex::new(memory_set) + }; + + // 在生成新的进程前,需要决定其所属进程是谁 + let process_id = if clone_flags.contains(CloneFlags::CLONE_THREAD) { + // 当前clone生成的是线程,那么以self作为进程 + self.pid + } else { + // 新建一个进程,并且设计进程之间的父子关系 + TaskId::new().as_u64() + }; + // 决定父进程是谁 + let parent_id = if clone_flags.contains(CloneFlags::CLONE_PARENT) { + // 创建兄弟关系,此时以self的父进程作为自己的父进程 + // 理论上不应该创建内核进程的兄弟进程,所以可以直接unwrap + self.get_parent() + } else { + // 创建父子关系,此时以self作为父进程 + self.pid + }; + let new_task = new_task( + || {}, + String::from(self.tasks.lock()[0].name().split('/').last().unwrap()), + self.get_stack_limit() as usize, + process_id, + new_memory_set.lock().lock().page_table_token(), + ); + + // When clone a new task, the new task should have the same fs_base as the original task. + // + // It should be saved in trap frame, but to compatible with Unikernel, we save it in TaskContext. + #[cfg(target_arch = "x86_64")] + unsafe { + new_task.set_tls_force(axhal::arch::read_thread_pointer()); + } + + debug!("new task:{}", new_task.id().as_u64()); + TID2TASK + .lock() + .insert(new_task.id().as_u64(), Arc::clone(&new_task)); + let new_handler = if clone_flags.contains(CloneFlags::CLONE_SIGHAND) { + // let curr_id = current().id().as_u64(); + self.signal_modules + .lock() + .get_mut(¤t().id().as_u64()) + .unwrap() + .signal_handler + .clone() + } else { + Arc::new(Mutex::new( + self.signal_modules + .lock() + .get_mut(¤t().id().as_u64()) + .unwrap() + .signal_handler + .lock() + .clone(), + )) + // info!("curr_id: {:X}", (&curr_id as *const _ as usize)); + }; + // 检查是否在父任务中写入当前新任务的tid + if clone_flags.contains(CloneFlags::CLONE_PARENT_SETTID) + & self.manual_alloc_for_lazy(ptid.into()).is_ok() + { + unsafe { + *(ptid as *mut i32) = new_task.id().as_u64() as i32; + } + } + // 若包含CLONE_CHILD_SETTID或者CLONE_CHILD_CLEARTID + // 则需要把线程号写入到子线程地址空间中tid对应的地址中 + if clone_flags.contains(CloneFlags::CLONE_CHILD_SETTID) + || clone_flags.contains(CloneFlags::CLONE_CHILD_CLEARTID) + { + if clone_flags.contains(CloneFlags::CLONE_CHILD_SETTID) { + new_task.set_child_tid(ctid); + } + + if clone_flags.contains(CloneFlags::CLONE_CHILD_CLEARTID) { + new_task.set_clear_child_tid(ctid); + } + + if clone_flags.contains(CloneFlags::CLONE_VM) { + // 此时地址空间不会发生改变 + // 在当前地址空间下进行分配 + if self.manual_alloc_for_lazy(ctid.into()).is_ok() { + // 正常分配了地址 + unsafe { + *(ctid as *mut i32) = + if clone_flags.contains(CloneFlags::CLONE_CHILD_SETTID) { + new_task.id().as_u64() as i32 + } else { + 0 + } + } + } else { + return Err(AxError::BadAddress); + } + } else { + // 否则需要在新的地址空间中进行分配 + let memory_set_wrapper = new_memory_set.lock(); + let mut vm = memory_set_wrapper.lock(); + if vm.manual_alloc_for_lazy(ctid.into()).is_ok() { + // 此时token没有发生改变,所以不能直接解引用访问,需要手动查页表 + if let Ok((phyaddr, _, _)) = vm.query(ctid.into()) { + let vaddr: usize = phys_to_virt(phyaddr).into(); + // 注意:任何地址都是从free memory分配来的,那么在页表中,free memory一直在页表中,他们的虚拟地址和物理地址一直有偏移的映射关系 + unsafe { + *(vaddr as *mut i32) = + if clone_flags.contains(CloneFlags::CLONE_CHILD_SETTID) { + new_task.id().as_u64() as i32 + } else { + 0 + } + } + drop(vm); + } else { + drop(vm); + return Err(AxError::BadAddress); + } + } else { + drop(vm); + return Err(AxError::BadAddress); + } + } + } + // 返回的值 + // 若创建的是进程,则返回进程的id + // 若创建的是线程,则返回线程的id + let return_id: u64; + // 决定是创建线程还是进程 + if clone_flags.contains(CloneFlags::CLONE_THREAD) { + // // 若创建的是线程,那么不用新建进程 + // info!("task len: {}", inner.tasks.len()); + // info!("task address :{:X}", (&new_task as *const _ as usize)); + // info!( + // "task address: {:X}", + // (&Arc::clone(&new_task)) as *const _ as usize + // ); + self.tasks.lock().push(Arc::clone(&new_task)); + + let mut signal_module = SignalModule::init_signal(Some(new_handler)); + // exit signal, default to be SIGCHLD + if let Some(exit_signal) = exit_signal { + signal_module.set_exit_signal(exit_signal); + } + self.signal_modules + .lock() + .insert(new_task.id().as_u64(), signal_module); + + self.robust_list + .lock() + .insert(new_task.id().as_u64(), FutexRobustList::default()); + return_id = new_task.id().as_u64(); + } else { + let mut cwd_src = Arc::new(Mutex::new(String::from("/"))); + let mut mask_src = Arc::new(AtomicI32::new(0o022)); + if clone_flags.contains(CloneFlags::CLONE_FS) { + cwd_src = Arc::clone(&self.fd_manager.cwd); + mask_src = Arc::clone(&self.fd_manager.umask); + } + // 若创建的是进程,那么需要新建进程 + // 由于地址空间是复制的,所以堆底的地址也一定相同 + let fd_table = if clone_flags.contains(CloneFlags::CLONE_FILES) { + Arc::clone(&self.fd_manager.fd_table) + } else { + Arc::new(Mutex::new(self.fd_manager.fd_table.lock().clone())) + }; + let new_process = Arc::new(Process::new( + process_id, + self.get_stack_limit(), + parent_id, + new_memory_set, + self.get_heap_bottom(), + cwd_src, + mask_src, + fd_table, + )); + // 复制当前工作文件夹 + new_process.set_cwd(self.get_cwd()); + // 记录该进程,防止被回收 + PID2PC.lock().insert(process_id, Arc::clone(&new_process)); + new_process.tasks.lock().push(Arc::clone(&new_task)); + // 若是新建了进程,那么需要把进程的父子关系进行记录 + let mut signal_module = SignalModule::init_signal(Some(new_handler)); + // exit signal, default to be SIGCHLD + if let Some(exit_signal) = exit_signal { + signal_module.set_exit_signal(exit_signal); + } + new_process + .signal_modules + .lock() + .insert(new_task.id().as_u64(), signal_module); + + new_process + .robust_list + .lock() + .insert(new_task.id().as_u64(), FutexRobustList::default()); + return_id = new_process.pid; + PID2PC + .lock() + .get_mut(&parent_id) + .unwrap() + .children + .lock() + .push(Arc::clone(&new_process)); + }; + + if !clone_flags.contains(CloneFlags::CLONE_THREAD) { + new_task.set_leader(true); + } + let current_task = current(); + // 复制原有的trap上下文 + // let mut trap_frame = unsafe { *(current_task.get_first_trap_frame()) }; + let mut trap_frame = + read_trapframe_from_kstack(current_task.get_kernel_stack_top().unwrap()); + // drop(current_task); + // 新开的进程/线程返回值为0 + trap_frame.set_ret_code(0); + if clone_flags.contains(CloneFlags::CLONE_SETTLS) { + #[cfg(not(target_arch = "x86_64"))] + trap_frame.set_tls(tls); + #[cfg(target_arch = "x86_64")] + unsafe { + new_task.set_tls_force(tls); + } + } + + // 设置用户栈 + // 若给定了用户栈,则使用给定的用户栈 + // 若没有给定用户栈,则使用当前用户栈 + // 没有给定用户栈的时候,只能是共享了地址空间,且原先调用clone的有用户栈,此时已经在之前的trap clone时复制了 + if let Some(stack) = stack { + trap_frame.set_user_sp(stack); + // info!( + // "New user stack: sepc:{:X}, stack:{:X}", + // trap_frame.sepc, trap_frame.regs.sp + // ); + } + write_trapframe_to_kstack(new_task.get_kernel_stack_top().unwrap(), &trap_frame); + Processor::first_add_task(new_task); + // 判断是否为VFORK + if clone_flags.contains(CloneFlags::CLONE_VFORK) { + self.set_vfork_block(true); + // VFORK: TODO: judge when schedule + while self.get_vfork_block() { + yield_now_task(); + } + } + Ok(return_id) + } +} + +/// 与地址空间相关的进程方法 +impl Process { + /// alloc physical memory for lazy allocation manually + pub fn manual_alloc_for_lazy(&self, addr: VirtAddr) -> AxResult<()> { + self.memory_set.lock().lock().manual_alloc_for_lazy(addr) + } + + /// alloc range physical memory for lazy allocation manually + pub fn manual_alloc_range_for_lazy(&self, start: VirtAddr, end: VirtAddr) -> AxResult<()> { + self.memory_set + .lock() + .lock() + .manual_alloc_range_for_lazy(start, end) + } + + /// alloc physical memory with the given type size for lazy allocation manually + pub fn manual_alloc_type_for_lazy(&self, obj: *const T) -> AxResult<()> { + self.memory_set + .lock() + .lock() + .manual_alloc_type_for_lazy(obj) + } +} + +/// 与文件相关的进程方法 +impl Process { + /// 为进程分配一个文件描述符 + pub fn alloc_fd(&self, fd_table: &mut Vec>>) -> AxResult { + for (i, fd) in fd_table.iter().enumerate() { + if fd.is_none() { + return Ok(i); + } + } + if fd_table.len() >= self.fd_manager.get_limit() as usize { + debug!("fd table is full"); + return Err(AxError::StorageFull); + } + fd_table.push(None); + Ok(fd_table.len() - 1) + } + + /// 获取当前进程的工作目录 + pub fn get_cwd(&self) -> String { + self.fd_manager.cwd.lock().clone().to_string() + } + + /// Set the current working directory of the process + pub fn set_cwd(&self, cwd: String) { + *self.fd_manager.cwd.lock() = cwd; + } +} + +/// 与信号相关的方法 +impl Process { + /// 查询当前任务是否存在未决信号 + pub fn have_signals(&self) -> Option { + let current_task = current(); + self.signal_modules + .lock() + .get(¤t_task.id().as_u64()) + .unwrap() + .signal_set + .find_signal() + .map_or_else(|| current_task.check_pending_signal(), Some) + } + + /// Judge whether the signal request the interrupted syscall to restart + /// + /// # Return + /// - None: There is no siganl need to be delivered + /// - Some(true): The interrupted syscall should be restarted + /// - Some(false): The interrupted syscall should not be restarted + pub fn have_restart_signals(&self) -> Option { + let current_task = current(); + self.signal_modules + .lock() + .get(¤t_task.id().as_u64()) + .unwrap() + .have_restart_signal() + } +} diff --git a/modules/axprocess/src/signal.rs b/modules/axprocess/src/signal.rs new file mode 100644 index 0000000..1b5afb9 --- /dev/null +++ b/modules/axprocess/src/signal.rs @@ -0,0 +1,410 @@ +//! 负责处理进程中与信号相关的内容 +extern crate alloc; +use alloc::sync::Arc; +use axerrno::{AxError, AxResult}; +use axhal::{ + arch::{read_trapframe_from_kstack, write_trapframe_to_kstack, TrapFrame}, + cpu::this_cpu_id, + KERNEL_PROCESS_ID, +}; +use axlog::{info, warn}; +use axsignal::{ + action::{SigActionFlags, SignalDefault, SIG_DFL, SIG_IGN}, + info::SigInfo, + signal_no::SignalNo, + ucontext::{SignalStack, SignalUserContext}, + SignalHandler, SignalSet, +}; +use axsync::Mutex; + +/// 信号处理模块,进程间不共享 +pub struct SignalModule { + /// 是否存在siginfo + pub sig_info: bool, + /// 保存的trap上下文 + pub last_trap_frame_for_signal: Option, + /// 信号处理函数集 + pub signal_handler: Arc>, + /// 未决信号集 + pub signal_set: SignalSet, + /// exit signal + exit_signal: Option, + /// Alternative signal stack + pub alternate_stack: SignalStack, +} + +impl SignalModule { + /// 初始化信号模块 + pub fn init_signal(signal_handler: Option>>) -> Self { + let signal_handler = + signal_handler.unwrap_or_else(|| Arc::new(Mutex::new(SignalHandler::new()))); + let signal_set = SignalSet::new(); + let last_trap_frame_for_signal = None; + let sig_info = false; + Self { + sig_info, + last_trap_frame_for_signal, + signal_handler, + signal_set, + exit_signal: None, + alternate_stack: SignalStack::default(), + } + } + + /// Judge whether the signal request the interrupted syscall to restart + /// + /// # Return + /// - None: There is no siganl need to be delivered + /// - Some(true): The interrupted syscall should be restarted + /// - Some(false): The interrupted syscall should not be restarted + pub fn have_restart_signal(&self) -> Option { + self.signal_set.find_signal().map(|sig_num| { + self.signal_handler + .lock() + .get_action(sig_num) + .need_restart() + }) + } + + /// Set the exit signal + pub fn set_exit_signal(&mut self, signal: SignalNo) { + self.exit_signal = Some(signal); + } + + /// Get the exit signal + pub fn get_exit_signal(&self) -> Option { + self.exit_signal + } +} + +const USER_SIGNAL_PROTECT: usize = 512; + +use crate::{ + current_process, current_task, exit_current_task, + process::{PID2PC, TID2TASK}, +}; + +/// 将保存的trap上下文填入内核栈中 +/// +/// 若使用了SIG_INFO,此时会对原有trap上下文作一定修改。 +/// +/// 若确实存在可以被恢复的trap上下文,则返回true +#[no_mangle] +pub fn load_trap_for_signal() -> bool { + let current_process = current_process(); + let current_task = current_task(); + + let mut signal_modules = current_process.signal_modules.lock(); + let signal_module = signal_modules.get_mut(¤t_task.id().as_u64()).unwrap(); + if let Some(old_trap_frame) = signal_module.last_trap_frame_for_signal.take() { + unsafe { + // let now_trap_frame: *mut TrapFrame = current_task.get_first_trap_frame(); + let mut now_trap_frame = + read_trapframe_from_kstack(current_task.get_kernel_stack_top().unwrap()); + // 考虑当时调用信号处理函数时,sp对应的地址上的内容即是SignalUserContext + // 此时认为一定通过sig_return调用这个函数 + // 所以此时sp的位置应该是SignalUserContext的位置 + let sp = now_trap_frame.get_sp(); + now_trap_frame = old_trap_frame; + if signal_module.sig_info { + let pc = (*(sp as *const SignalUserContext)).get_pc(); + now_trap_frame.set_pc(pc); + } + write_trapframe_to_kstack( + current_task.get_kernel_stack_top().unwrap(), + &now_trap_frame, + ); + } + true + } else { + false + } +} + +/// 处理 Terminate 类型的信号 +fn terminate_process(signal: SignalNo, info: Option) { + let current_task = current_task(); + warn!("Terminate process: {}", current_task.get_process_id()); + if current_task.is_leader() { + exit_current_task(signal as i32); + } else { + // 此时应当关闭当前进程 + // 选择向主线程发送信号内部来关闭 + send_signal_to_process( + current_task.get_process_id() as isize, + signal as isize, + info, + ) + .unwrap(); + exit_current_task(-1); + } +} + +/// 处理当前进程的信号 +/// +/// 若返回值为真,代表需要进入处理信号,因此需要执行trap的返回 +pub fn handle_signals() { + let process = current_process(); + let current_task = current_task(); + if let Some(signal_no) = current_task.check_pending_signal() { + send_signal_to_thread(current_task.id().as_u64() as isize, signal_no as isize) + .unwrap_or_else(|err| { + warn!("send signal failed: {:?}", err); + }); + } + if process.get_zombie() { + if current_task.is_leader() { + return; + } + // 进程退出了,在测试环境下非主线程应该立即退出 + exit_current_task(0); + } + + if process.pid() == KERNEL_PROCESS_ID { + // 内核进程不处理信号 + return; + } + let mut signal_modules = process.signal_modules.lock(); + + let signal_module = signal_modules.get_mut(¤t_task.id().as_u64()).unwrap(); + let signal_set = &mut signal_module.signal_set; + let sig_num = if let Some(sig_num) = signal_set.get_one_signal() { + sig_num + } else { + return; + }; + info!( + "cpu: {}, task: {}, handler signal: {}", + this_cpu_id(), + current_task.id().as_u64(), + sig_num + ); + let signal = SignalNo::from(sig_num); + let mask = signal_set.mask; + // 存在未被处理的信号 + if signal_module.last_trap_frame_for_signal.is_some() { + // 之前的trap frame还未被处理 + // 说明之前的信号处理函数还未返回,即出现了信号嵌套。 + if signal == SignalNo::SIGSEGV || signal == SignalNo::SIGBUS { + // 在处理信号的过程中又触发 SIGSEGV 或 SIGBUS,此时会导致死循环,所以直接结束当前进程 + drop(signal_modules); + exit_current_task(-1); + } + return; + } + // 之前的trap frame已经被处理 + // 说明之前的信号处理函数已经返回,即没有信号嵌套。 + // 此时可以将当前的trap frame保存起来 + signal_module.last_trap_frame_for_signal = Some(read_trapframe_from_kstack( + current_task.get_kernel_stack_top().unwrap(), + )); + // current_task.set_siginfo(false); + signal_module.sig_info = false; + // 调取处理函数 + let signal_handler = signal_module.signal_handler.lock(); + let action = signal_handler.get_action(sig_num); + if action.sa_handler == SIG_DFL { + drop(signal_handler); + drop(signal_modules); + // 未显式指定处理函数,使用默认处理函数 + match SignalDefault::get_action(signal) { + SignalDefault::Ignore => { + // 忽略,此时相当于已经完成了处理,所以要把trap上下文清空 + load_trap_for_signal(); + } + SignalDefault::Terminate => { + terminate_process(signal, None); + } + SignalDefault::Stop => { + unimplemented!(); + } + SignalDefault::Cont => { + unimplemented!(); + } + SignalDefault::Core => { + terminate_process(signal, None); + } + } + return; + } + if action.sa_handler == SIG_IGN { + // 忽略处理 + return; + } + // 此时需要调用信号处理函数,注意调用的方式是: + // 通过修改trap上下文的pc指针,使得trap返回之后,直接到达信号处理函数 + // 因此需要处理一系列的trap上下文,使得正确传参与返回。 + // 具体来说需要考虑两个方面: + // 1. 传参 + // 2. 返回值ra地址的设定,与是否设置了SA_RESTORER有关 + + // 读取当前的trap上下文 + let mut trap_frame = read_trapframe_from_kstack(current_task.get_kernel_stack_top().unwrap()); + + // // 新的trap上下文的sp指针位置,由于SIGINFO会存放内容,所以需要开个保护区域 + let mut sp = if action.sa_flags.contains(SigActionFlags::SA_ONSTACK) + && signal_module.alternate_stack.flags != axsignal::ucontext::SS_DISABLE + { + axlog::debug!("Use alternate stack"); + // Use alternate stack + (signal_module.alternate_stack.sp + signal_module.alternate_stack.size - 1) & !0xf + } else { + trap_frame.get_sp() - USER_SIGNAL_PROTECT + }; + + info!("use stack: {:#x}", sp); + let restorer = if let Some(addr) = action.get_storer() { + addr + } else { + axconfig::SIGNAL_TRAMPOLINE + }; + + info!( + "restorer :{:#x}, handler: {:#x}", + restorer, action.sa_handler + ); + #[cfg(not(target_arch = "x86_64"))] + trap_frame.set_ra(restorer); + + let old_pc = trap_frame.get_pc(); + + trap_frame.set_pc(action.sa_handler); + // 传参 + trap_frame.set_arg0(sig_num); + // 若带有SIG_INFO参数,则函数原型为fn(sig: SignalNo, info: &SigInfo, ucontext: &mut UContext) + if action.sa_flags.contains(SigActionFlags::SA_SIGINFO) { + // current_task.set_siginfo(true); + signal_module.sig_info = true; + let sp_base = (((sp - core::mem::size_of::()) & !0xf) + - core::mem::size_of::()) + & !0xf; + + // TODO: 统一为访问用户空间的操作封装函数 + process + .manual_alloc_range_for_lazy(sp_base.into(), sp.into()) + .expect("Failed to alloc memory for signal user stack"); + + // 注意16字节对齐 + sp = (sp - core::mem::size_of::()) & !0xf; + let info = if let Some(info) = signal_set.info.get(&(sig_num - 1)) { + info!("test SigInfo: {:?}", info.0.si_val_int); + info.0 + } else { + SigInfo { + si_signo: sig_num as i32, + ..Default::default() + } + }; + unsafe { + *(sp as *mut SigInfo) = info; + } + trap_frame.set_arg1(sp); + + // 接下来存储ucontext + sp = (sp - core::mem::size_of::()) & !0xf; + + let ucontext = SignalUserContext::init(old_pc, mask); + unsafe { + *(sp as *mut SignalUserContext) = ucontext; + } + trap_frame.set_arg2(sp); + } + + #[cfg(target_arch = "x86_64")] + unsafe { + // set return rip + sp -= core::mem::size_of::(); + *(sp as *mut usize) = restorer; + } + + trap_frame.set_user_sp(sp); + // 将修改后的trap上下文写回内核栈 + write_trapframe_to_kstack(current_task.get_kernel_stack_top().unwrap(), &trap_frame); + drop(signal_handler); + drop(signal_modules); +} + +/// 从信号处理函数返回 +/// +/// 返回的值与原先syscall应当返回的值相同,即返回原先保存的trap上下文的a0的值 +pub fn signal_return() -> isize { + if load_trap_for_signal() { + // 说明确实存在着信号处理函数的trap上下文 + // 此时内核栈上存储的是调用信号处理前的trap上下文 + read_trapframe_from_kstack(current_task().get_kernel_stack_top().unwrap()).get_ret_code() + as isize + } else { + // 没有进行信号处理,但是调用了sig_return + // 此时直接返回-1 + -1 + } +} + +/// 发送信号到指定的进程 +/// +/// 默认发送到该进程下的主线程 +pub fn send_signal_to_process(pid: isize, signum: isize, info: Option) -> AxResult<()> { + let mut pid2pc = PID2PC.lock(); + if !pid2pc.contains_key(&(pid as u64)) { + return Err(axerrno::AxError::NotFound); + } + let process = pid2pc.get_mut(&(pid as u64)).unwrap(); + let mut now_id: Option = None; + for task in process.tasks.lock().iter_mut() { + if task.is_leader() { + now_id = Some(task.id().as_u64()); + break; + } + } + if now_id.is_some() { + let mut signal_modules = process.signal_modules.lock(); + let signal_module = signal_modules.get_mut(&now_id.unwrap()).unwrap(); + signal_module + .signal_set + .try_add_signal(signum as usize, info); + let tid2task = TID2TASK.lock(); + let main_task = Arc::clone(tid2task.get(&now_id.unwrap()).unwrap()); + // 如果这个时候对应的线程是处于休眠状态的,则唤醒之,进入信号处理阶段 + if main_task.is_blocked() { + axtask::wakeup_task(main_task); + } + } + Ok(()) +} + +/// 发送信号到指定的线程 +pub fn send_signal_to_thread(tid: isize, signum: isize) -> AxResult<()> { + let tid2task = TID2TASK.lock(); + let task = if let Some(task) = tid2task.get(&(tid as u64)) { + Arc::clone(task) + } else { + return Err(AxError::NotFound); + }; + drop(tid2task); + let pid = task.get_process_id(); + let pid2pc = PID2PC.lock(); + let process = if let Some(process) = pid2pc.get(&pid) { + Arc::clone(process) + } else { + return Err(AxError::NotFound); + }; + drop(pid2pc); + let mut signal_modules = process.signal_modules.lock(); + if !signal_modules.contains_key(&(tid as u64)) { + return Err(axerrno::AxError::NotFound); + } + let signal_module = signal_modules.get_mut(&(tid as u64)).unwrap(); + signal_module + .signal_set + .try_add_signal(signum as usize, None); + // 如果这个时候对应的线程是处于休眠状态的,则唤醒之,进入信号处理阶段 + if task.is_blocked() { + axtask::wakeup_task(task); + } + Ok(()) +} + +/// Whether the current process has signals pending +pub fn current_have_signals() -> bool { + current_process().have_signals().is_some() +} diff --git a/modules/axprocess/src/stdio.rs b/modules/axprocess/src/stdio.rs new file mode 100644 index 0000000..e5f9cc3 --- /dev/null +++ b/modules/axprocess/src/stdio.rs @@ -0,0 +1,430 @@ +use axerrno::{AxError, AxResult}; +use axfs::api::port::{ + ConsoleWinSize, FileExt, FileIO, FileIOType, OpenFlags, FIOCLEX, TCGETS, TIOCGPGRP, TIOCGWINSZ, + TIOCSPGRP, +}; +use axhal::console::{getchar, putchar, write_bytes}; +use axio::{Read, Seek, SeekFrom, Write}; +use axlog::warn; +use axsync::Mutex; +use axtask::yield_now; + +extern crate alloc; +use alloc::string::String; +/// stdin file for getting chars from console +pub struct Stdin { + pub flags: Mutex, +} + +/// stdout file for putting chars to console +pub struct Stdout { + pub flags: Mutex, +} + +/// stderr file for putting chars to console +pub struct Stderr { + #[allow(unused)] + pub flags: Mutex, +} + +pub const LF: u8 = 0x0au8; +pub const CR: u8 = 0x0du8; +pub const DL: u8 = 0x7fu8; +pub const BS: u8 = 0x08u8; + +pub const SPACE: u8 = 0x20u8; + +pub const BACKSPACE: [u8; 3] = [BS, SPACE, BS]; + +fn stdin_read(buf: &mut [u8]) -> AxResult { + let ch: u8; + // busybox + if buf.len() == 1 { + loop { + match getchar() { + Some(c) => { + ch = c; + break; + } + None => { + yield_now(); + continue; + } + } + } + unsafe { + buf.as_mut_ptr().write_volatile(ch); + } + Ok(1) + } + else { + // user appilcation + let mut line = String::new(); + loop { + let c = getchar(); + if let Some(c) = c { + match c { + LF | CR => { + // convert '\r' to '\n' + line.push('\n'); + putchar(b'\n'); + break; + } + BS | DL => { + if !line.is_empty() { + write_bytes(&BACKSPACE); + line.pop(); + } + } + _ => { + // echo + putchar(c); + line.push(c as char); + } + } + } else { + yield_now(); + continue; + } + } + let len = line.len(); + buf[..len].copy_from_slice(line.as_bytes()); + Ok(len) + } +} + +fn stdout_write(buf: &[u8]) -> AxResult { + write_bytes(buf); + Ok(buf.len()) +} + +impl Read for Stdin { + fn read(&mut self, buf: &mut [u8]) -> AxResult { + stdin_read(buf) + } +} + +impl Write for Stdin { + fn write(&mut self, _: &[u8]) -> AxResult { + panic!("Cannot write to stdin!"); + } + fn flush(&mut self) -> axio::Result { + panic!("Flushing stdin") + } +} + +impl Seek for Stdin { + fn seek(&mut self, _pos: SeekFrom) -> AxResult { + Err(AxError::Unsupported) // 如果没有实现seek, 则返回Unsupported + } +} + +impl FileExt for Stdin { + fn executable(&self) -> bool { + false + } + fn readable(&self) -> bool { + true + } + fn writable(&self) -> bool { + false + } +} + +impl FileIO for Stdin { + fn read(&self, buf: &mut [u8]) -> AxResult { + stdin_read(buf) + } + + fn get_type(&self) -> FileIOType { + FileIOType::Stdin + } + + fn ready_to_read(&self) -> bool { + true + } + + fn ready_to_write(&self) -> bool { + false + } + + fn readable(&self) -> bool { + true + } + + fn writable(&self) -> bool { + false + } + + fn executable(&self) -> bool { + false + } + + fn ioctl(&self, request: usize, data: usize) -> AxResult { + match request { + TIOCGWINSZ => { + let winsize = data as *mut ConsoleWinSize; + unsafe { + *winsize = ConsoleWinSize::default(); + } + Ok(0) + } + TCGETS | TIOCSPGRP => { + warn!("stdin TCGETS | TIOCSPGRP, pretend to be tty."); + // pretend to be tty + Ok(0) + } + + TIOCGPGRP => { + warn!("stdin TIOCGPGRP, pretend to be have a tty process group."); + unsafe { + *(data as *mut u32) = 0; + } + Ok(0) + } + FIOCLEX => Ok(0), + _ => Err(AxError::Unsupported), + } + } + + fn set_status(&self, flags: OpenFlags) -> bool { + if flags.contains(OpenFlags::CLOEXEC) { + *self.flags.lock() = OpenFlags::CLOEXEC; + true + } else { + false + } + } + + fn get_status(&self) -> OpenFlags { + *self.flags.lock() + } + + fn set_close_on_exec(&self, is_set: bool) -> bool { + if is_set { + // 设置close_on_exec位置 + *self.flags.lock() |= OpenFlags::CLOEXEC; + } else { + *self.flags.lock() &= !OpenFlags::CLOEXEC; + } + true + } +} + +impl Read for Stdout { + fn read(&mut self, _: &mut [u8]) -> AxResult { + panic!("Cannot read from stdin!"); + } +} + +impl Write for Stdout { + fn write(&mut self, buf: &[u8]) -> AxResult { + write_bytes(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> AxResult { + // stdout is always flushed + Ok(()) + } +} + +impl Seek for Stdout { + fn seek(&mut self, _pos: SeekFrom) -> AxResult { + Err(AxError::Unsupported) // 如果没有实现seek, 则返回Unsupported + } +} + +impl FileExt for Stdout { + fn readable(&self) -> bool { + false + } + fn writable(&self) -> bool { + true + } + fn executable(&self) -> bool { + false + } +} +impl FileIO for Stdout { + fn write(&self, buf: &[u8]) -> AxResult { + stdout_write(buf) + } + + fn flush(&self) -> AxResult { + // stdout is always flushed + Ok(()) + } + + fn readable(&self) -> bool { + false + } + + fn writable(&self) -> bool { + true + } + + fn executable(&self) -> bool { + false + } + + fn get_type(&self) -> FileIOType { + FileIOType::Stdout + } + + fn ready_to_read(&self) -> bool { + false + } + + fn ready_to_write(&self) -> bool { + true + } + + fn set_status(&self, flags: OpenFlags) -> bool { + if flags.contains(OpenFlags::CLOEXEC) { + *self.flags.lock() = flags; + true + } else { + false + } + } + + fn get_status(&self) -> OpenFlags { + *self.flags.lock() + } + + fn set_close_on_exec(&self, is_set: bool) -> bool { + if is_set { + // 设置close_on_exec位置 + *self.flags.lock() |= OpenFlags::CLOEXEC; + } else { + *self.flags.lock() &= !OpenFlags::CLOEXEC; + } + true + } + + fn ioctl(&self, request: usize, data: usize) -> AxResult { + match request { + TIOCGWINSZ => { + let winsize = data as *mut ConsoleWinSize; + unsafe { + *winsize = ConsoleWinSize::default(); + } + Ok(0) + } + TCGETS | TIOCSPGRP => { + warn!("stdout TCGETS | TIOCSPGRP, pretend to be tty."); + // pretend to be tty + Ok(0) + } + + TIOCGPGRP => { + warn!("stdout TIOCGPGRP, pretend to be have a tty process group."); + unsafe { + *(data as *mut u32) = 0; + } + Ok(0) + } + FIOCLEX => Ok(0), + _ => Err(AxError::Unsupported), + } + } +} + +impl Read for Stderr { + fn read(&mut self, _: &mut [u8]) -> AxResult { + panic!("Cannot read from stdout!"); + } +} + +impl Write for Stderr { + fn write(&mut self, buf: &[u8]) -> AxResult { + write_bytes(buf); + Ok(buf.len()) + } + + /// Stderr is always flushed + fn flush(&mut self) -> axio::Result { + Ok(()) + } +} + +impl Seek for Stderr { + fn seek(&mut self, _pos: SeekFrom) -> AxResult { + Err(AxError::Unsupported) // 如果没有实现seek, 则返回Unsupported + } +} + +impl FileExt for Stderr { + fn readable(&self) -> bool { + false + } + fn writable(&self) -> bool { + true + } + fn executable(&self) -> bool { + false + } +} + +impl FileIO for Stderr { + fn write(&self, buf: &[u8]) -> AxResult { + write_bytes(buf); + Ok(buf.len()) + } + + /// Stderr is always flushed + fn flush(&self) -> axio::Result { + Ok(()) + } + + fn readable(&self) -> bool { + false + } + + fn writable(&self) -> bool { + true + } + + fn executable(&self) -> bool { + false + } + + fn get_type(&self) -> FileIOType { + FileIOType::Stderr + } + + fn ready_to_read(&self) -> bool { + false + } + + fn ready_to_write(&self) -> bool { + true + } + + fn ioctl(&self, request: usize, data: usize) -> AxResult { + match request { + TIOCGWINSZ => { + let winsize = data as *mut ConsoleWinSize; + unsafe { + *winsize = ConsoleWinSize::default(); + } + Ok(0) + } + TCGETS | TIOCSPGRP => { + warn!("stderr TCGETS | TIOCSPGRP, pretend to be tty."); + // pretend to be tty + Ok(0) + } + + TIOCGPGRP => { + warn!("stderr TIOCGPGRP, pretend to be have a tty process group."); + unsafe { + *(data as *mut u32) = 0; + } + Ok(0) + } + FIOCLEX => Ok(0), + _ => Err(AxError::Unsupported), + } + } +} diff --git a/modules/axruntime/.gitignore b/modules/axruntime/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/modules/axruntime/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/modules/axruntime/Cargo.toml b/modules/axruntime/Cargo.toml new file mode 100644 index 0000000..af52d7a --- /dev/null +++ b/modules/axruntime/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "axruntime" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Runtime library of ArceOS" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axruntime" +documentation = "https://rcore-os.github.io/arceos/axruntime/index.html" +keywords = ["Starry"] + +[features] +default = [] + +smp = ["axhal/smp"] +irq = ["axhal/irq", "axtask/irq", "percpu", "kernel_guard"] +tls = ["axhal/tls", "axtask/tls"] +alloc = ["axalloc"] +paging = ["axhal/paging", "lazy_init"] + +multitask = ["axtask/multitask"] +fs = ["axdriver", "axfs"] +net = ["axdriver", "axnet"] +display = ["axdriver", "axdisplay"] +img = ["axdriver/img", "paging"] +monolithic = ["axprocess/monolithic", "axhal/monolithic", "axtask/monolithic", "axmem/monolithic"] + +[dependencies] +cfg-if = "1.0" +axhal = { workspace = true } +axlog = { workspace = true } +axconfig = { workspace = true } +axalloc = { workspace = true, optional = true } +axdriver = { workspace = true, optional = true } +axfs = { workspace = true, optional = true } +axnet = { workspace = true, optional = true } +axdisplay = { workspace = true, optional = true } +axtask = { workspace = true} +axprocess = { workspace = true, optional = true } +axmem = { workspace = true, optional = true } +crate_interface = { git = "https://github.com/Starry-OS/crate_interface.git" } +percpu = { git = "https://github.com/Starry-OS/percpu.git", optional = true } +kernel_guard = { git = "https://github.com/Starry-OS/kernel_guard.git", optional = true } +lazy_init = { git = "https://github.com/Starry-OS/lazy_init.git", optional = true } diff --git a/modules/axruntime/src/lang_items.rs b/modules/axruntime/src/lang_items.rs new file mode 100644 index 0000000..6da79a4 --- /dev/null +++ b/modules/axruntime/src/lang_items.rs @@ -0,0 +1,8 @@ +use core::panic::PanicInfo; + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + error!("{}", info); + axtask::dump_curr_backtrace(); + axhal::misc::terminate() +} diff --git a/modules/axruntime/src/lib.rs b/modules/axruntime/src/lib.rs new file mode 100644 index 0000000..50f9dd0 --- /dev/null +++ b/modules/axruntime/src/lib.rs @@ -0,0 +1,312 @@ +//! Runtime library of [ArceOS](https://github.com/rcore-os/arceos). +//! +//! Any application uses ArceOS should link this library. It does some +//! initialization work before entering the application's `main` function. +//! +//! # Cargo Features +//! +//! - `alloc`: Enable global memory allocator. +//! - `paging`: Enable page table manipulation support. +//! - `irq`: Enable interrupt handling support. +//! - `multitask`: Enable multi-threading support. +//! - `smp`: Enable SMP (symmetric multiprocessing) support. +//! - `fs`: Enable filesystem support. +//! - `net`: Enable networking support. +//! - `display`: Enable graphics support. +//! +//! All the features are optional and disabled by default. + +#![cfg_attr(not(test), no_std)] +#![feature(doc_auto_cfg)] + +#[macro_use] +extern crate axlog; + +#[cfg(all(target_os = "none", not(test)))] +mod lang_items; + +#[cfg(feature = "smp")] +mod mp; + +#[cfg(feature = "smp")] +pub use self::mp::{entered_cpus_num, rust_main_secondary}; + +const LOGO: &str = r#" + d8888 .d88888b. .d8888b. + d88888 d88P" "Y88b d88P Y88b + d88P888 888 888 Y88b. + d88P 888 888d888 .d8888b .d88b. 888 888 "Y888b. + d88P 888 888P" d88P" d8P Y8b 888 888 "Y88b. + d88P 888 888 888 88888888 888 888 "888 + d8888888888 888 Y88b. Y8b. Y88b. .d88P Y88b d88P +d88P 888 888 "Y8888P "Y8888 "Y88888P" "Y8888P" +"#; + +struct LogIfImpl; + +#[crate_interface::impl_interface] +impl axlog::LogIf for LogIfImpl { + fn console_write_str(s: &str) { + axhal::console::write_bytes(s.as_bytes()); + } + + fn current_time() -> core::time::Duration { + axhal::time::current_time() + } + + fn current_cpu_id() -> Option { + #[cfg(feature = "smp")] + if is_init_ok() { + Some(axhal::cpu::this_cpu_id()) + } else { + None + } + #[cfg(not(feature = "smp"))] + Some(0) + } + + fn current_task_id() -> Option { + if is_init_ok() { + #[cfg(feature = "multitask")] + { + axtask::current_may_uninit().map(|curr| curr.id().as_u64()) + } + #[cfg(not(feature = "multitask"))] + None + } else { + None + } + } +} + +use core::sync::atomic::{AtomicUsize, Ordering}; + +static INITED_CPUS: AtomicUsize = AtomicUsize::new(0); + +/// Whether all CPUs has been initialized. +pub fn is_init_ok() -> bool { + INITED_CPUS.load(Ordering::Acquire) == axconfig::SMP +} + +/// The main entry point of the ArceOS runtime. +/// +/// It is called from the bootstrapping code in [axhal]. `cpu_id` is the ID of +/// the current CPU, and `dtb` is the address of the device tree blob. It +/// finally calls the application's `main` function after all initialization +/// work is done. +/// +/// In multi-core environment, this function is called on the primary CPU, +/// and the secondary CPUs call [`rust_main_secondary`]. +#[cfg_attr(not(test), no_mangle)] +pub extern "C" fn rust_main(cpu_id: usize, dtb: usize) { + ax_println!("{}", LOGO); + ax_println!( + "\ + arch = {}\n\ + platform = {}\n\ + target = {}\n\ + smp = {}\n\ + build_mode = {}\n\ + log_level = {}\n\ + ", + option_env!("AX_ARCH").unwrap_or(""), + option_env!("AX_PLATFORM").unwrap_or(""), + option_env!("AX_TARGET").unwrap_or(""), + option_env!("AX_SMP").unwrap_or(""), + option_env!("AX_MODE").unwrap_or(""), + option_env!("AX_LOG").unwrap_or(""), + ); + + info!("Logging is enabled."); + info!("Primary CPU {} started, dtb = {:#x}.", cpu_id, dtb); + info!("Platform name {}.", axhal::platform_name()); + + info!("Found physcial memory regions:"); + for r in axhal::mem::memory_regions() { + info!( + " [{:x?}, {:x?}) {} ({:?})", + r.paddr, + r.paddr + r.size, + r.name, + r.flags + ); + } + #[cfg(feature = "alloc")] + init_allocator(); + + #[cfg(feature = "paging")] + { + info!("Initialize kernel page table..."); + remap_kernel_memory().expect("remap kernel memoy failed"); + } + + info!("Initialize platform devices..."); + axhal::platform_init(); + + cfg_if::cfg_if! { + if #[cfg(feature = "monolithic")] { + axprocess::init_kernel_process(); + } + else { + #[cfg(feature = "multitask")] + axtask::init_scheduler(); + } + } + #[cfg(any(feature = "fs", feature = "net", feature = "display"))] + { + #[allow(unused_variables)] + let all_devices = axdriver::init_drivers(); + + #[cfg(feature = "fs")] + axfs::init_filesystems(all_devices.block); + + #[cfg(feature = "net")] + axnet::init_network(all_devices.net); + + #[cfg(feature = "display")] + axdisplay::init_display(all_devices.display); + } + + #[cfg(feature = "irq")] + { + info!("Initialize interrupt handlers..."); + init_interrupt(); + } + + #[cfg(all(feature = "tls", not(feature = "multitask")))] + { + info!("Initialize thread local storage..."); + init_tls(); + } + + info!("Primary CPU {} init OK.", cpu_id); + INITED_CPUS.fetch_add(1, Ordering::Relaxed); +} + +/// exit the main task +pub fn exit_main() { + #[cfg(feature = "multitask")] + axtask::exit(0); + #[cfg(not(feature = "multitask"))] + { + debug!("main task exited: exit_code={}", 0); + axhal::misc::terminate(); + } +} + +#[cfg(feature = "alloc")] +#[allow(dead_code)] +fn init_allocator() { + use axhal::mem::{memory_regions, phys_to_virt, MemRegionFlags}; + + info!("Initialize global memory allocator..."); + info!(" use {} allocator.", axalloc::global_allocator().name()); + + let mut max_region_size = 0; + let mut max_region_paddr = 0.into(); + for r in memory_regions() { + if r.flags.contains(MemRegionFlags::FREE) && r.size > max_region_size { + max_region_size = r.size; + max_region_paddr = r.paddr; + } + } + for r in memory_regions() { + if r.flags.contains(MemRegionFlags::FREE) && r.paddr == max_region_paddr { + axalloc::global_init(phys_to_virt(r.paddr).as_usize(), r.size); + break; + } + } + for r in memory_regions() { + if r.flags.contains(MemRegionFlags::FREE) && r.paddr != max_region_paddr { + axalloc::global_add_memory(phys_to_virt(r.paddr).as_usize(), r.size) + .expect("add heap memory region failed"); + } + } +} + +cfg_if::cfg_if! { + if #[cfg(feature = "paging")] { + use axhal::paging::PageTable; + use lazy_init::LazyInit; + /// The kernel page table. + pub static KERNEL_PAGE_TABLE: LazyInit = LazyInit::new(); + + fn remap_kernel_memory() -> Result<(), axhal::paging::PagingError> { + use axhal::mem::{memory_regions, phys_to_virt}; + if axhal::cpu::this_cpu_is_bsp() { + let mut kernel_page_table = PageTable::try_new()?; + for r in memory_regions() { + kernel_page_table.map_region( + phys_to_virt(r.paddr), + r.paddr, + r.size, + r.flags.into(), + true, + )?; + } + + #[cfg(feature = "img")] + { + // 此时将测例加载到内存中,通过ramdisk和页表定向映射的方式来读取测例 + use axconfig::{TESTCASE_MEMORY_START,TESTCASE_MEMORY_SIZE}; + use axhal::mem::PhysAddr; + use axhal::mem::MemRegionFlags; + extern "C" { + fn img_start(); + } + // 此时qemu运行,文件镜像的位置需要由汇编确定 + let img_start_addr:PhysAddr = axhal::mem::virt_to_phys((img_start as usize).into()); + kernel_page_table.map_region( + phys_to_virt(TESTCASE_MEMORY_START.into()), + img_start_addr, + TESTCASE_MEMORY_SIZE, + MemRegionFlags::from_bits(1 << 0 | 1 << 1 | 1 << 4).unwrap().into(), + true, + ).unwrap(); + } + KERNEL_PAGE_TABLE.init_by(kernel_page_table); + } + + unsafe { axhal::arch::write_page_table_root(KERNEL_PAGE_TABLE.root_paddr()) }; + Ok(()) + } + } +} +#[cfg(feature = "irq")] +fn init_interrupt() { + use axhal::time::TIMER_IRQ_NUM; + + // Setup timer interrupt handler + const PERIODIC_INTERVAL_NANOS: u64 = + axhal::time::NANOS_PER_SEC / axconfig::TICKS_PER_SEC as u64; + + #[percpu::def_percpu] + static NEXT_DEADLINE: u64 = 0; + + fn update_timer() { + let now_ns = axhal::time::current_time_nanos(); + // Safety: we have disabled preemption in IRQ handler. + let mut deadline = unsafe { NEXT_DEADLINE.read_current_raw() }; + if now_ns >= deadline { + deadline = now_ns + PERIODIC_INTERVAL_NANOS; + } + unsafe { NEXT_DEADLINE.write_current_raw(deadline + PERIODIC_INTERVAL_NANOS) }; + axhal::time::set_oneshot_timer(deadline); + } + + axhal::irq::register_handler(TIMER_IRQ_NUM, || { + update_timer(); + #[cfg(feature = "multitask")] + axtask::on_timer_tick(); + }); + + // Enable IRQs before starting app + axhal::arch::enable_irqs(); +} + +#[cfg(all(feature = "tls", not(feature = "multitask")))] +fn init_tls() { + let main_tls = axhal::tls::TlsArea::alloc(); + unsafe { axhal::arch::write_thread_pointer(main_tls.tls_ptr() as usize) }; + core::mem::forget(main_tls); +} diff --git a/modules/axruntime/src/mp.rs b/modules/axruntime/src/mp.rs new file mode 100644 index 0000000..c0e97c8 --- /dev/null +++ b/modules/axruntime/src/mp.rs @@ -0,0 +1,47 @@ +//! Runtime main function for secondary CPUs. +use core::sync::atomic::{AtomicUsize, Ordering}; + +static ENTERED_CPUS: AtomicUsize = AtomicUsize::new(1); + +/// The main entry point of the ArceOS runtime for secondary CPUs. +/// +/// It is called from the bootstrapping code in [axhal]. +#[no_mangle] +pub fn rust_main_secondary(cpu_id: usize) -> ! { + ENTERED_CPUS.fetch_add(1, Ordering::Relaxed); + + info!("Secondary CPU {:x} started.", cpu_id); + + #[cfg(feature = "paging")] + super::remap_kernel_memory().unwrap(); + + axhal::platform_init_secondary(); + + #[cfg(feature = "multitask")] + axtask::init_scheduler_secondary(); + + info!("Secondary CPU {:x} init OK.", cpu_id); + super::INITED_CPUS.fetch_add(1, Ordering::Relaxed); + + while !super::is_init_ok() { + core::hint::spin_loop(); + } + + #[cfg(feature = "irq")] + axhal::arch::enable_irqs(); + + #[cfg(all(feature = "tls", not(feature = "multitask")))] + super::init_tls(); + + #[cfg(feature = "multitask")] + axtask::run_idle(); + #[cfg(not(feature = "multitask"))] + loop { + axhal::arch::wait_for_irqs(); + } +} + +/// The number of CPUs that have entered the runtime. +pub fn entered_cpus_num() -> usize { + ENTERED_CPUS.load(Ordering::Acquire) +} diff --git a/modules/axsignal/.gitignore b/modules/axsignal/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/modules/axsignal/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/modules/axsignal/Cargo.toml b/modules/axsignal/Cargo.toml new file mode 100644 index 0000000..d435ccb --- /dev/null +++ b/modules/axsignal/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "axsignal" +version = "0.1.0" +edition = "2021" +keywords = ["Starry", "signal"] +authors = ["Youjie Zheng "] +description = "Module with linux signal definitions" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] + +default = [] + +[dependencies] +numeric-enum-macro = { git = "https://github.com/mexus/numeric-enum-macro" } +bitflags = "2.6" +axhal = { workspace = true } +cfg-if = "1.0" \ No newline at end of file diff --git a/modules/axsignal/src/action.rs b/modules/axsignal/src/action.rs new file mode 100644 index 0000000..026f75e --- /dev/null +++ b/modules/axsignal/src/action.rs @@ -0,0 +1,126 @@ +//! To define the signal action and its flags + +use crate::signal_no::SignalNo::{self, *}; + +/// 特殊取值,代表默认处理函数 +pub const SIG_DFL: usize = 0; + +/// 特殊取值,代表忽略这个信号 +pub const SIG_IGN: usize = 1; + +bitflags::bitflags! { + #[allow(missing_docs)] + #[derive(Default,Clone, Copy, Debug)] + /// The flags of the signal action + pub struct SigActionFlags: u32 { + /// do not receive notification when child processes stop + const SA_NOCLDSTOP = 1; + /// do not create zombie on child process exit + const SA_NOCLDWAIT = 2; + /// use signal handler with 3 arguments, and sa_sigaction should be set instead of sa_handler. + const SA_SIGINFO = 4; + /// call the signal handler on an alternate signal stack provided by `sigaltstack(2)` + const SA_ONSTACK = 0x08000000; + /// restart system calls if possible + const SA_RESTART = 0x10000000; + /// do not automatically block the signal when its handler is being executed + const SA_NODEFER = 0x40000000; + /// restore the signal action to the default upon entry to the signal handler + const SA_RESETHAND = 0x80000000; + /// use the restorer field as the signal trampoline + const SA_RESTORER = 0x4000000; + } +} + +/// 没有显式指定处理函数时的默认行为 +pub enum SignalDefault { + /// 终止进程 + Terminate, + /// 忽略信号 + Ignore, + /// 终止进程并转储核心,即程序当时的内存状态记录下来,保存在一个文件中,但当前未实现保存,直接退出进程 + Core, + /// 暂停进程执行 + Stop, + /// 恢复进程执行 + Cont, +} + +impl SignalDefault { + /// Get the default action of a signal + pub fn get_action(signal: SignalNo) -> Self { + match signal { + SIGABRT => Self::Core, + SIGALRM => Self::Terminate, + SIGBUS => Self::Core, + SIGCHLD => Self::Ignore, + SIGCONT => Self::Cont, + SIGFPE => Self::Core, + SIGHUP => Self::Terminate, + SIGILL => Self::Core, + SIGINT => Self::Terminate, + SIGKILL => Self::Terminate, + SIGPIPE => Self::Terminate, + SIGQUIT => Self::Core, + SIGSEGV => Self::Core, + SIGSTOP => Self::Stop, + SIGTERM => Self::Terminate, + SIGTSTP => Self::Stop, + SIGTTIN => Self::Stop, + SIGTTOU => Self::Stop, + SIGUSR1 => Self::Terminate, + SIGUSR2 => Self::Terminate, + SIGXCPU => Self::Core, + SIGXFSZ => Self::Core, + SIGVTALRM => Self::Terminate, + SIGPROF => Self::Terminate, + SIGWINCH => Self::Ignore, + SIGIO => Self::Terminate, + SIGPWR => Self::Terminate, + SIGSYS => Self::Core, + _ => Self::Terminate, + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +/// The structure of the signal action +pub struct SigAction { + /// 信号处理函数的地址 + /// 1. 如果是上述特殊值 SIG_DFL 或 SIG_IGN,则按描述处理 + /// 2. 若flags没有指定SA_SIGINFO,则函数原型为 fn(sig: SignalNo) -> (),对应C语言原型为 void (*sa_handler)(int) + /// 3. 若flags指定了SA_SIGINFO,则函数原型为 fn(sig: SignalNo, info: &SigInfo, ucontext: &mut UContext) -> (), + /// 对应C语言原型为 void (*sa_sigaction)(int, siginfo_t *, void *)。 + /// + /// 其中,SigInfo和SignalNo的定义见siginfo.rs和signal_no.rs。 + /// UContext即是处理信号时内核保存的用户态上下文,它存储在用户地址空间,会在调用sig_return时被恢复,定义见ucontext.rs。 + pub sa_handler: usize, + /// 信号处理的flags + pub sa_flags: SigActionFlags, + /// 信号处理的跳板页地址,存储了sig_return的函数处理地址 + /// 仅在SA_RESTORER标志被设置时有效 + pub restorer: usize, + /// 该信号处理函数的信号掩码 + pub sa_mask: usize, +} + +impl SigAction { + /// get the restorer address of the signal action + /// + /// When the SA_RESTORER flag is set, the restorer address is valid + /// + /// or it will return None, and the core will set the restore address as the signal trampoline + pub fn get_storer(&self) -> Option { + if self.sa_flags.contains(SigActionFlags::SA_RESTORER) { + Some(self.restorer) + } else { + None + } + } + + /// Whether the syscall should be restarted after the signal handler returns + pub fn need_restart(&self) -> bool { + self.sa_flags.contains(SigActionFlags::SA_RESTART) + } +} diff --git a/modules/axsignal/src/info.rs b/modules/axsignal/src/info.rs new file mode 100644 index 0000000..eaed43e --- /dev/null +++ b/modules/axsignal/src/info.rs @@ -0,0 +1,44 @@ +//! 触发信号时的信息,当SigAction指定需要信息时,将其返回给用户 +//! +//! 错误信息:详细定义见 `https://man7.org/linux/man-pages/man2/rt_sigaction.2.html` + +/// The information of the signal +/// +/// When the `SigAction` specifies that it needs information, it will return it to the user +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SigInfo { + /// The signal number + pub si_signo: i32, + /// An errno value + pub si_errno: i32, + /// The code of the signal + pub si_code: i32, + + /// Padding + #[allow(unused)] + pub pad: u32, + /// The process ID of the sender + pub pid: i32, + /// The real user ID of the sender + pub uid: u32, + /// The value sent with the signal + pub si_val_int: i32, + /// The value pointer of the signal + pub si_val_ptr: usize, +} + +impl Default for SigInfo { + fn default() -> Self { + Self { + si_signo: 0, + si_errno: 0, + si_code: -6, // SI_TKILL + pad: 0, + pid: 0, + uid: 0, + si_val_int: 0, + si_val_ptr: 0, + } + } +} diff --git a/modules/axsignal/src/lib.rs b/modules/axsignal/src/lib.rs new file mode 100644 index 0000000..3777680 --- /dev/null +++ b/modules/axsignal/src/lib.rs @@ -0,0 +1,150 @@ +//! 信号处理模块 +//! +//! 当前模型中,进程与线程分离,认为信号模块是进程层面的内容,同一进程下不同线程共享信号处理模块。 +//! +//! 当前采用在trap return时进行信号的处理。因此为了防止信号处理延时过长,需要开启时钟中断,使得OS每隔一段时间触发 +//! 一次trap,从而检查是否有需要处理的信号。 +#![cfg_attr(not(test), no_std)] +extern crate alloc; +use alloc::collections::BTreeMap; + +use action::SigAction; +use info::SigInfo; +use signal_no::{SignalNo, MAX_SIG_NUM}; +use ucontext::SignalUserContext; + +pub mod action; +pub mod info; +pub mod signal_no; +pub mod ucontext; + +/// 处理所有信号的结构 +#[derive(Clone)] +pub struct SignalHandler { + /// handlers`[i]` stands for the handler of signal `i+1` + pub handlers: [SigAction; MAX_SIG_NUM], +} + +impl Default for SignalHandler { + fn default() -> Self { + Self::new() + } +} + +impl SignalHandler { + /// 新建一个信号处理函数 + pub fn new() -> Self { + Self { + // 默认所有信号都是默认处理 + handlers: [SigAction::default(); MAX_SIG_NUM], + } + } + + /// 清空信号处理模块 + /// + /// 会在exec时清空 + pub fn clear(&mut self) { + for action in self.handlers.iter_mut() { + *action = SigAction::default(); + } + } + + /// To get the action of a signal with the signal number + pub fn get_action(&self, sig_num: usize) -> &SigAction { + &self.handlers[sig_num - 1] + } + /// 设置信号处理函数 + /// + /// # Safety + /// + /// 传入的action必须是合法的指针 + pub unsafe fn set_action(&mut self, sig_num: usize, action: *const SigAction) { + self.handlers[sig_num - 1] = unsafe { *action }; + } +} + +/// 接受信号的结构,每一个进程都有一个 +#[derive(Clone)] +pub struct SignalSet { + /// 信号掩码 + pub mask: usize, + /// 未决信号集 + pub pending: usize, + /// 附加信息 + pub info: BTreeMap, +} + +impl Default for SignalSet { + fn default() -> Self { + Self::new() + } +} + +impl SignalSet { + /// 新建处理模块 + pub fn new() -> Self { + Self { + mask: 0, + pending: 0, + info: BTreeMap::new(), + } + } + + /// 清空信号处理模块 + pub fn clear(&mut self) { + self.mask = 0; + self.pending = 0; + } + + /// 查询是否有未决信号,若有则返回对应编号 + /// + /// 但是不会修改原有信号集 + pub fn find_signal(&self) -> Option { + let mut temp_pending = self.pending; + loop { + let pos: u32 = temp_pending.trailing_zeros(); + // 若全为0,则返回64,代表没有未决信号 + if pos == MAX_SIG_NUM as u32 { + return None; + } else { + temp_pending &= !(1 << pos); + + if (self.mask & (1 << pos) == 0) + || pos == SignalNo::SIGKILL as u32 - 1 + || pos == SignalNo::SIGSTOP as u32 - 1 + { + break Some(pos as usize + 1); + } + } + } + } + + /// 查询当前是否有未决信号 + /// + /// 若有则返回信号编号最低的一个,,并且修改原有信号集 + pub fn get_one_signal(&mut self) -> Option { + match self.find_signal() { + Some(pos) => { + // 修改原有信号集 + self.pending &= !(1 << (pos - 1)); + Some(pos) + } + None => None, + } + } + + /// 尝试添加一个bit作为信号 + /// + /// 若当前信号已经加入到未决信号集中,则不作处理 + /// + /// 若信号在掩码中,则仍然加入,但是可能不会触发 + pub fn try_add_signal(&mut self, sig_num: usize, info: Option) { + let now_mask = 1 << (sig_num - 1); + self.pending |= now_mask; + // self.info[sig_num - 1] = info.map(|info| (info, SignalUserContext::default())); + if let Some(info) = info { + self.info + .insert(sig_num, (info, SignalUserContext::default())); + } + } +} diff --git a/modules/axsignal/src/signal_no.rs b/modules/axsignal/src/signal_no.rs new file mode 100644 index 0000000..0c79ded --- /dev/null +++ b/modules/axsignal/src/signal_no.rs @@ -0,0 +1,84 @@ +//! Define signal numbers. + +/// The maximum number of signals. +pub const MAX_SIG_NUM: usize = 64; +numeric_enum_macro::numeric_enum! { +#[repr(u8)] +#[allow(missing_docs)] +#[derive(Eq, PartialEq, Debug, Copy, Clone)] +/// 信号编号。 +/// +/// 从 32 开始的部分为 SIGRT,其中 RT 表示 real time。 +/// 但目前实现时没有通过 ipi 等手段即时处理,而是像其他信号一样等到 trap 再处理 +pub enum SignalNo { + ERR = 0, + SIGHUP = 1, + SIGINT = 2, + SIGQUIT = 3, + SIGILL = 4, + SIGTRAP = 5, + SIGABRT = 6, + SIGBUS = 7, + SIGFPE = 8, + SIGKILL = 9, + SIGUSR1 = 10, + SIGSEGV = 11, + SIGUSR2 = 12, + SIGPIPE = 13, + SIGALRM = 14, + SIGTERM = 15, + SIGSTKFLT = 16, + SIGCHLD = 17, + SIGCONT = 18, + SIGSTOP = 19, + SIGTSTP = 20, + SIGTTIN = 21, + SIGTTOU = 22, + SIGURG = 23, + SIGXCPU = 24, + SIGXFSZ = 25, + SIGVTALRM = 26, + SIGPROF = 27, + SIGWINCH = 28, + SIGIO = 29, + SIGPWR = 30, + SIGSYS = 31, + SIGRTMIN = 32, + SIGRT1 = 33, + SIGRT2 = 34, + SIGRT3 = 35, + SIGRT4 = 36, + SIGRT5 = 37, + SIGRT6 = 38, + SIGRT7 = 39, + SIGRT8 = 40, + SIGRT9 = 41, + SIGRT10 = 42, + SIGRT11 = 43, + SIGRT12 = 44, + SIGRT13 = 45, + SIGRT14 = 46, + SIGRT15 = 47, + SIGRT16 = 48, + SIGRT17 = 49, + SIGRT18 = 50, + SIGRT19 = 51, + SIGRT20 = 52, + SIGRT21 = 53, + SIGRT22 = 54, + SIGRT23 = 55, + SIGRT24 = 56, + SIGRT25 = 57, + SIGRT26 = 58, + SIGRT27 = 59, + SIGRT28 = 60, + SIGRT29 = 61, + SIGRT30 = 62, + SIGRT31 = 63, +}} + +impl From for SignalNo { + fn from(num: usize) -> Self { + Self::try_from(num as u8).unwrap_or(Self::ERR) + } +} diff --git a/modules/axsignal/src/ucontext/aarch64.rs b/modules/axsignal/src/ucontext/aarch64.rs new file mode 100644 index 0000000..c5f2f38 --- /dev/null +++ b/modules/axsignal/src/ucontext/aarch64.rs @@ -0,0 +1,105 @@ +//! 信号处理时保存的用户上下文。 + +/// 处理信号时使用的栈 +/// +/// 详细信息见`https://man7.org/linux/man-pages/man2/sigaltstack.2.html` +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct SignalStack { + /// Base address of the stack + pub sp: usize, + /// Flags for the stack + pub flags: u32, + /// Size of the stack + pub size: usize, +} + +impl Default for SignalStack { + fn default() -> Self { + Self { + sp: 0, + // 代表SS_DISABLE,即不使用栈 + flags: super::SS_DISABLE, + size: 0, + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +/// The `mcontext` struct for the signal action +pub struct MContext { + fault_address: usize, + regs: [usize; 31], + sp: usize, + pc: usize, + pstate: usize, + reserved: [usize; 256 * 2], +} + +impl Default for MContext { + fn default() -> Self { + Self { + fault_address: 0, + regs: [0; 31], + sp: 0, + pc: 0, + pstate: 0, + reserved: [0; 512], + } + } +} + +impl MContext { + fn init_by_pc(pc: usize) -> Self { + Self { + fault_address: 0, + regs: [0; 31], + sp: 0, + pc, + pstate: 0, + reserved: [0; 512], + } + } +} + +#[repr(C)] +#[derive(Clone, Copy)] +/// The user context saved for the signal action, which can be accessed by the signal handler +pub struct SignalUserContext { + flags: usize, + link: usize, + stack: SignalStack, + sigmask: [u64; 17], + mcontext: MContext, +} + +impl Default for SignalUserContext { + fn default() -> Self { + Self { + flags: 0, + link: 0, + stack: SignalStack::default(), + mcontext: MContext::default(), + sigmask: [0; 17], + } + } +} + +impl SignalUserContext { + /// init the user context by the pc and the mask + pub fn init(pc: usize, _mask: usize) -> Self { + Self { + flags: 0, + link: 0, + stack: SignalStack::default(), + mcontext: MContext::init_by_pc(pc), + sigmask: [0; 17], + } + } + + /// get the pc from the user context + pub fn get_pc(&self) -> usize { + self.mcontext.pc + } +} diff --git a/modules/axsignal/src/ucontext/mod.rs b/modules/axsignal/src/ucontext/mod.rs new file mode 100644 index 0000000..ac3fe88 --- /dev/null +++ b/modules/axsignal/src/ucontext/mod.rs @@ -0,0 +1,21 @@ +//! Signal ucontext types and operations. + +/// The thread is currently executing on the alternate signal stack. +pub const SS_ONSTACK: u32 = 1; +/// The alternate signal stack is disabled. +pub const SS_DISABLE: u32 = 2; +/// The alternate signal stack has been marked to be autodisarmed as described above. +pub const SS_AUTODISARM: u32 = 1_u32 << 31; + +cfg_if::cfg_if! { + if #[cfg(target_arch = "x86_64")] { + mod x86_64; + pub use self::x86_64::*; + } else if #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] { + mod riscv; + pub use self::riscv::*; + } else if #[cfg(target_arch = "aarch64")]{ + mod aarch64; + pub use self::aarch64::*; + } +} diff --git a/modules/axsignal/src/ucontext/riscv.rs b/modules/axsignal/src/ucontext/riscv.rs new file mode 100644 index 0000000..9834c47 --- /dev/null +++ b/modules/axsignal/src/ucontext/riscv.rs @@ -0,0 +1,103 @@ +//! 信号处理时保存的用户上下文。 + +/// 处理信号时使用的栈 +/// +/// 详细信息见`https://man7.org/linux/man-pages/man2/sigaltstack.2.html` +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct SignalStack { + /// Base address of the stack + pub sp: usize, + /// Flags for the stack + pub flags: u32, + /// Size of the stack + pub size: usize, +} + +impl Default for SignalStack { + fn default() -> Self { + Self { + sp: 0, + // 代表SS_DISABLE,即不使用栈 + flags: super::SS_DISABLE, + size: 0, + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +/// The `mcontext` struct for the signal action +pub struct MContext { + reserved1: [usize; 16], + pc: usize, + reserved2: [usize; 17], + fpstate: [usize; 66], +} + +impl Default for MContext { + fn default() -> Self { + Self { + reserved1: [0; 16], + pc: 0, + reserved2: [0; 17], + fpstate: [0; 66], + } + } +} + +impl MContext { + fn init_by_pc(pc: usize) -> Self { + Self { + reserved1: [0; 16], + pc, + reserved2: [0; 17], + fpstate: [0; 66], + } + } + + fn get_pc(&self) -> usize { + self.pc + } +} + +#[repr(C)] +#[derive(Clone, Copy)] +/// The user context saved for the signal action, which can be accessed by the signal handler +pub struct SignalUserContext { + flags: usize, + link: usize, + stack: SignalStack, + sigmask: u64, + mcontext: MContext, +} + +impl Default for SignalUserContext { + fn default() -> Self { + Self { + flags: 0, + link: 0, + stack: SignalStack::default(), + mcontext: MContext::default(), + sigmask: 0, + } + } +} + +impl SignalUserContext { + /// init the user context by the pc and the mask + pub fn init(pc: usize, mask: usize) -> Self { + Self { + flags: 0, + link: 0, + stack: SignalStack::default(), + mcontext: MContext::init_by_pc(pc), + sigmask: mask as u64, + } + } + + /// get the pc from the user context + pub fn get_pc(&self) -> usize { + self.mcontext.get_pc() + } +} diff --git a/modules/axsignal/src/ucontext/x86_64.rs b/modules/axsignal/src/ucontext/x86_64.rs new file mode 100644 index 0000000..52dff8d --- /dev/null +++ b/modules/axsignal/src/ucontext/x86_64.rs @@ -0,0 +1,120 @@ +//! 信号处理时保存的用户上下文。 + +/// 处理信号时使用的栈 +/// +/// 详细信息见`https://man7.org/linux/man-pages/man2/sigaltstack.2.html` +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct SignalStack { + /// Base address of the stack + pub sp: usize, + /// Flags for the stack + pub flags: u32, + /// Size of the stack + pub size: usize, +} + +impl Default for SignalStack { + fn default() -> Self { + Self { + sp: 0, + // 代表SS_DISABLE,即不使用栈 + flags: super::SS_DISABLE, + size: 0, + } + } +} +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +/// The `mcontext` struct for the signal action +pub struct MContext { + // gregs + r8: usize, + r9: usize, + r10: usize, + r11: usize, + r12: usize, + r13: usize, + r14: usize, + r15: usize, + rdi: usize, + rsi: usize, + rbp: usize, + rbx: usize, + rdx: usize, + rax: usize, + rcx: usize, + rsp: usize, + rip: usize, + eflags: usize, + cs: u16, + gs: u16, + fs: u16, + _pad: u16, + err: usize, + trapno: usize, + oldmask: usize, + cr2: usize, + // fpregs + // TODO + fpstate: usize, + // reserved + _reserved1: [usize; 8], +} + +impl MContext { + fn init_by_pc(pc: usize) -> Self { + Self { + rip: pc, + ..Default::default() + } + } + + fn get_pc(&self) -> usize { + self.rip + } +} + +#[repr(C)] +#[derive(Clone, Copy)] +/// The user context saved for the signal action, which can be accessed by the signal handler +pub struct SignalUserContext { + flags: usize, + link: usize, + stack: SignalStack, + mcontext: MContext, + sigmask: u64, + _fpregs: [usize; 64], +} + +impl Default for SignalUserContext { + fn default() -> Self { + Self { + _fpregs: [0; 64], + flags: 0, + link: 0, + stack: SignalStack::default(), + mcontext: MContext::default(), + sigmask: 0, + } + } +} + +impl SignalUserContext { + /// init the user context by the pc and the mask + pub fn init(pc: usize, mask: usize) -> Self { + Self { + flags: 0, + link: 0, + stack: SignalStack::default(), + mcontext: MContext::init_by_pc(pc), + sigmask: mask as u64, + _fpregs: [0; 64], + } + } + + /// get the pc from the user context + pub fn get_pc(&self) -> usize { + self.mcontext.get_pc() + } +} diff --git a/modules/axsync/.gitignore b/modules/axsync/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/modules/axsync/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/modules/axsync/Cargo.toml b/modules/axsync/Cargo.toml new file mode 100644 index 0000000..873fd22 --- /dev/null +++ b/modules/axsync/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "axsync" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "ArceOS synchronization primitives" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axsync" +documentation = "https://rcore-os.github.io/arceos/axsync/index.html" +keywords = ["Starry"] + +[features] +multitask = ["axtask/multitask"] +irq = ["axtask/irq"] +default = [] + +[dependencies] +cfg-if = "1.0" +spinlock = { git = "https://github.com/Starry-OS/spinlock.git" } +axtask = { workspace = true } +axhal = {workspace = true} + +[dev-dependencies] +rand = "0.8" +axsync = { workspace = true, features = ["multitask"] } +axtask = { workspace = true, features = ["test"] } diff --git a/modules/axsync/src/completion.rs b/modules/axsync/src/completion.rs new file mode 100644 index 0000000..eb4d522 --- /dev/null +++ b/modules/axsync/src/completion.rs @@ -0,0 +1,121 @@ +//! completion +//! If you have one or more threads that must wait for some kernel activity +//! to have reached a point or a specific state, completions can provide a +//! race-free solution to this problem. Semantically they are somewhat like a +//! pthread_barrier() and have similar use-cases. +//! +//! Completions are a code synchronization mechanism which is preferable to any +//! misuse of locks/semaphores and busy-loops. Any time you think of using +//! yield() or some quirky msleep(1) loop to allow something else to proceed, +//! you probably want to look into using one of the wait_for_completion*() +//! calls and complete() instead. +//! +//! Completions are built on top of the waitqueue and wakeup infrastructure of +//! scheduler(axtask). The event the threads on the waitqueue are waiting for +//! is reduced to a simple flag in 'struct completion', appropriately called "done". +//! +use alloc::sync::Arc; +use axtask::{WaitTaskList, WaitTaskNode}; +use spinlock::SpinNoIrq; +use axtask::schedule; +#[cfg(feature = "irq")] +use axtask::schedule_timeout; +#[cfg(feature = "irq")] +use core::time::Duration; + +use axtask::declare_wait; + +struct Inner { + queue: WaitTaskList, + done: i32, +} + +impl Inner { + /// Creates a new completion + #[inline(always)] + pub const fn new() -> Self { + Self { + queue: WaitTaskList::new(), + done: 0, + } + } +} + +/// Cpmpletion struct, it protect by done +pub struct Completion { + inner: SpinNoIrq +} + +// SAFETY: have it's own SpinNoIrq protect +unsafe impl Sync for Completion {} +unsafe impl Send for Completion {} + +impl Completion { + /// Creates a new completion + #[inline(always)] + pub const fn new() -> Self { + Self { + inner: SpinNoIrq::new(Inner::new()), + } + } + + /// reinit completion calller make sure no thread in waitQ + #[inline(always)] + pub fn reinit(&self) { + self.inner.lock().done = 0; + } + + /// waits for completion of a task + pub fn wait_for_completion(&self) { + declare_wait!(waiter); + loop { + let mut inner = self.inner.lock(); + assert!(inner.done >=0); + if inner.done == 0 { + inner.queue.prepare_to_wait(waiter.clone()); + drop(inner); + schedule(); + } else { + inner.done -= 1; + break; + } + } + } + + #[cfg(feature = "irq")] + /// waits for completion of a task (w/timeout secs) + pub fn wait_for_completion_timeout(&self, secs: u64) -> bool { + declare_wait!(waiter); + let deadline = axhal::time::current_time() + Duration::from_secs(secs); + let timeout = loop { + let mut inner = self.inner.lock(); + assert!(inner.done >=0); + if inner.done == 0 { + inner.queue.prepare_to_wait(waiter.clone()); + drop(inner); + if schedule_timeout(deadline) { + break true; + } + } else { + inner.done -= 1; + break false; + } + }; + + timeout + } + + /// signals a single thread waiting on this completion + pub fn complete(&self) { + let mut inner = self.inner.lock(); + inner.done+=1; + inner.queue.notify_one(); + } + + /// signals a single thread waiting on this completion + pub fn complete_all(&self) { + let mut inner = self.inner.lock(); + inner.done = i32::MAX; + inner.queue.notify_all(); + } +} diff --git a/modules/axsync/src/lib.rs b/modules/axsync/src/lib.rs new file mode 100644 index 0000000..a7744ce --- /dev/null +++ b/modules/axsync/src/lib.rs @@ -0,0 +1,34 @@ +//! [ArceOS](https://github.com/rcore-os/arceos) synchronization primitives. +//! +//! Currently supported primitives: +//! +//! - [`Mutex`]: A mutual exclusion primitive. +//! - mod [`spin`](spinlock): spin-locks. +//! +//! # Cargo Features +//! +//! - `multitask`: For use in the multi-threaded environments. If the feature is +//! not enabled, [`Mutex`] will be an alias of [`spin::SpinNoIrq`]. This +//! feature is enabled by default. + +#![cfg_attr(not(test), no_std)] +#![feature(doc_cfg)] +cfg_if::cfg_if! { + if #[cfg(feature = "multitask")] { + extern crate axtask; + extern crate alloc; + + mod mutex; + mod completion; + + pub use self::completion::Completion; + + #[doc(cfg(feature = "multitask"))] + pub use self::mutex::{Mutex, MutexGuard}; + } else { + #[doc(cfg(not(feature = "multitask")))] + pub use spinlock::{SpinNoIrq as Mutex, SpinNoIrqGuard as MutexGuard}; + } +} + +pub use spinlock as spin; diff --git a/modules/axsync/src/mutex.rs b/modules/axsync/src/mutex.rs new file mode 100644 index 0000000..0abb2cc --- /dev/null +++ b/modules/axsync/src/mutex.rs @@ -0,0 +1,252 @@ +//! A naïve sleeping mutex. + +use core::cell::UnsafeCell; +use core::fmt; +use core::ops::{Deref, DerefMut}; +use core::sync::atomic::{AtomicU64, Ordering}; + +use axtask::{current, WaitQueue}; + +/// A mutual exclusion primitive useful for protecting shared data, similar to +/// [`std::sync::Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html). +/// +/// When the mutex is locked, the current task will block and be put into the +/// wait queue. When the mutex is unlocked, all tasks waiting on the queue +/// will be woken up. +pub struct Mutex { + wq: WaitQueue, + owner_id: AtomicU64, + data: UnsafeCell, +} + +/// A guard that provides mutable data access. +/// +/// When the guard falls out of scope it will release the lock. +pub struct MutexGuard<'a, T: ?Sized + 'a> { + lock: &'a Mutex, + data: *mut T, +} + +// Same unsafe impls as `std::sync::Mutex` +unsafe impl Sync for Mutex {} +unsafe impl Send for Mutex {} + +impl Mutex { + /// Creates a new [`Mutex`] wrapping the supplied data. + #[inline(always)] + pub const fn new(data: T) -> Self { + Self { + wq: WaitQueue::new(), + owner_id: AtomicU64::new(0), + data: UnsafeCell::new(data), + } + } + + /// Consumes this [`Mutex`] and unwraps the underlying data. + #[inline(always)] + pub fn into_inner(self) -> T { + // We know statically that there are no outstanding references to + // `self` so there's no need to lock. + let Mutex { data, .. } = self; + data.into_inner() + } +} + +impl Mutex { + /// Returns `true` if the lock is currently held. + /// + /// # Safety + /// + /// This function provides no synchronization guarantees and so its result should be considered 'out of date' + /// the instant it is called. Do not use it for synchronization purposes. However, it may be useful as a heuristic. + #[inline(always)] + pub fn is_locked(&self) -> bool { + self.owner_id.load(Ordering::Relaxed) != 0 + } + + /// Locks the [`Mutex`] and returns a guard that permits access to the inner data. + /// + /// The returned value may be dereferenced for data access + /// and the lock will be dropped when the guard falls out of scope. + pub fn lock(&self) -> MutexGuard { + let current_id = current().id().as_u64(); + loop { + // Can fail to lock even if the spinlock is not locked. May be more efficient than `try_lock` + // when called in a loop. + match self.owner_id.compare_exchange_weak( + 0, + current_id, + Ordering::Acquire, + Ordering::Relaxed, + ) { + Ok(_) => break, + Err(owner_id) => { + assert_ne!( + owner_id, + current_id, + "{} tried to acquire mutex it already owns.", + current().id_name() + ); + // Wait until the lock looks unlocked before retrying + self.wq.wait_until(|| !self.is_locked()); + } + } + } + MutexGuard { + lock: self, + data: unsafe { &mut *self.data.get() }, + } + } + + /// Try to lock this [`Mutex`], returning a lock guard if successful. + #[inline(always)] + pub fn try_lock(&self) -> Option> { + let current_id = current().id().as_u64(); + // The reason for using a strong compare_exchange is explained here: + // https://github.com/Amanieu/parking_lot/pull/207#issuecomment-575869107 + if self + .owner_id + .compare_exchange(0, current_id, Ordering::Acquire, Ordering::Relaxed) + .is_ok() + { + Some(MutexGuard { + lock: self, + data: unsafe { &mut *self.data.get() }, + }) + } else { + None + } + } + + /// Force unlock the [`Mutex`]. + /// + /// # Safety + /// + /// This is *extremely* unsafe if the lock is not held by the current + /// thread. However, this can be useful in some instances for exposing + /// the lock to FFI that doesn’t know how to deal with RAII. + pub unsafe fn force_unlock(&self) { + let owner_id = self.owner_id.swap(0, Ordering::Release); + assert_eq!( + owner_id, + current().id().as_u64(), + "{} tried to release mutex it doesn't own", + current().id_name() + ); + self.wq.notify_one(); + } + + /// Returns a mutable reference to the underlying data. + /// + /// Since this call borrows the [`Mutex`] mutably, and a mutable reference is guaranteed to be exclusive in + /// Rust, no actual locking needs to take place -- the mutable borrow statically guarantees no locks exist. As + /// such, this is a 'zero-cost' operation. + #[inline(always)] + pub fn get_mut(&mut self) -> &mut T { + // We know statically that there are no other references to `self`, so + // there's no need to lock the inner mutex. + unsafe { &mut *self.data.get() } + } +} + +impl Default for Mutex { + #[inline(always)] + fn default() -> Self { + Self::new(Default::default()) + } +} + +impl fmt::Debug for Mutex { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.try_lock() { + Some(guard) => write!(f, "Mutex {{ data: ") + .and_then(|()| (*guard).fmt(f)) + .and_then(|()| write!(f, "}}")), + None => write!(f, "Mutex {{ }}"), + } + } +} + +impl<'a, T: ?Sized> Deref for MutexGuard<'a, T> { + type Target = T; + #[inline(always)] + fn deref(&self) -> &T { + // We know statically that only we are referencing data + unsafe { &*self.data } + } +} + +impl<'a, T: ?Sized> DerefMut for MutexGuard<'a, T> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut T { + // We know statically that only we are referencing data + unsafe { &mut *self.data } + } +} + +impl<'a, T: ?Sized + fmt::Debug> fmt::Debug for MutexGuard<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl<'a, T: ?Sized> Drop for MutexGuard<'a, T> { + /// The dropping of the [`MutexGuard`] will release the lock it was created from. + fn drop(&mut self) { + unsafe { self.lock.force_unlock() } + } +} + +#[cfg(test)] +mod tests { + use crate::Mutex; + use axtask as thread; + use std::sync::Once; + + static INIT: Once = Once::new(); + + fn may_interrupt() { + // simulate interrupts + if rand::random::() % 3 == 0 { + thread::yield_now(); + } + } + + #[test] + fn lots_and_lots() { + INIT.call_once(thread::init_scheduler); + + const NUM_TASKS: u32 = 10; + const NUM_ITERS: u32 = 10_000; + static M: Mutex = Mutex::new(0); + + fn inc(delta: u32) { + for _ in 0..NUM_ITERS { + let mut val = M.lock(); + *val += delta; + may_interrupt(); + drop(val); + may_interrupt(); + } + } + + for _ in 0..NUM_TASKS { + thread::spawn(|| inc(1)); + thread::spawn(|| inc(2)); + } + + println!("spawn OK"); + loop { + let val = M.lock(); + if *val == NUM_ITERS * NUM_TASKS * 3 { + break; + } + may_interrupt(); + drop(val); + may_interrupt(); + } + + assert_eq!(*M.lock(), NUM_ITERS * NUM_TASKS * 3); + println!("Mutex test OK"); + } +} diff --git a/modules/axtask/.gitignore b/modules/axtask/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/modules/axtask/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/modules/axtask/Cargo.toml b/modules/axtask/Cargo.toml new file mode 100644 index 0000000..76a74f3 --- /dev/null +++ b/modules/axtask/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "axtask" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "ArceOS task management module" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axtask" +documentation = "https://rcore-os.github.io/arceos/axtask/index.html" +keywords = ["Starry"] + +[features] +default = [] + +multitask = [ + "dep:percpu", "dep:spinlock", "dep:lazy_init", "dep:memory_addr", + "dep:scheduler", "dep:timer_list", "kernel_guard","taskctx/multitask" +] +irq = [] +tls = ["axhal/tls", "taskctx/tls"] +preempt = ["irq", "percpu?/preempt", "kernel_guard/preempt", "taskctx/preempt"] + +sched_fifo = ["multitask"] +sched_rr = ["multitask", "preempt"] +sched_cfs = ["multitask", "preempt"] + +test = ["percpu?/sp-naive"] + +monolithic = ["multitask", "axhal/monolithic", "taskctx/monolithic"] + +[dependencies] +cfg-if = "1.0" +log = "0.4" +numeric-enum-macro = { git = "https://github.com/mexus/numeric-enum-macro" } +axhal = { workspace = true } +axsignal = { workspace = true } +axconfig = { workspace = true } +percpu = { git = "https://github.com/Starry-OS/percpu.git", optional = true } +spinlock = { git = "https://github.com/Starry-OS/spinlock.git", optional = true } +lazy_init = { git = "https://github.com/Starry-OS/lazy_init.git", optional = true } +memory_addr = { git = "https://github.com/Starry-OS/memory_addr.git", optional = true } +scheduler = { git = "https://github.com/Starry-OS/scheduler.git", optional = true } +timer_list = { git = "https://github.com/Starry-OS/timer_list.git", optional = true } +kernel_guard = { git = "https://github.com/Starry-OS/kernel_guard.git", optional = true } +taskctx = { git = "https://github.com/Starry-OS/taskctx.git" } +axlog = { workspace = true } +linked_list = { git = "https://github.com/Starry-OS/linked_list.git" } +axbacktrace = { git = "https://github.com/kern-crates/axbacktrace.git" } + +[dev-dependencies] +rand = "0.8" +axhal = { workspace = true, features = ["fp_simd"] } +axtask = { workspace = true, features = ["test"] } diff --git a/modules/axtask/src/api.rs b/modules/axtask/src/api.rs new file mode 100644 index 0000000..884d794 --- /dev/null +++ b/modules/axtask/src/api.rs @@ -0,0 +1,248 @@ +//! Task APIs for multi-task configuration. + +use alloc::{string::String, sync::Arc}; +#[cfg(feature = "monolithic")] +use axhal::KERNEL_PROCESS_ID; + +use crate::task::{ScheduleTask, TaskState}; + +use crate::schedule::get_wait_for_exit_queue; +#[doc(cfg(feature = "multitask"))] +pub use crate::task::{new_task, CurrentTask, TaskId}; +#[doc(cfg(feature = "multitask"))] +pub use crate::wait_queue::WaitQueue; + +pub use crate::processor::{current_processor, Processor}; + +pub use crate::schedule::schedule; + +#[cfg(feature = "irq")] +pub use crate::schedule::schedule_timeout; + +/// The reference type of a task. +pub type AxTaskRef = Arc; + +cfg_if::cfg_if! { + if #[cfg(feature = "sched_rr")] { + const MAX_TIME_SLICE: usize = 5; + pub(crate) type AxTask = scheduler::RRTask; + pub(crate) type Scheduler = scheduler::RRScheduler; + } else if #[cfg(feature = "sched_cfs")] { + pub(crate) type AxTask = scheduler::CFSTask; + pub(crate) type Scheduler = scheduler::CFScheduler; + } else { + // If no scheduler features are set, use FIFO as the default. + pub(crate) type AxTask = scheduler::FifoTask; + pub(crate) type Scheduler = scheduler::FifoScheduler; + } +} + +/// Gets the current task, or returns [`None`] if the current task is not +/// initialized. +pub fn current_may_uninit() -> Option { + CurrentTask::try_get() +} + +/// Gets the current task. +/// +/// # Panics +/// +/// Panics if the current task is not initialized. +pub fn current() -> CurrentTask { + CurrentTask::get() +} + +/// Initializes the task scheduler (for the primary CPU). +pub fn init_scheduler() { + info!("Initialize scheduling..."); + + crate::processor::init(); + #[cfg(feature = "irq")] + crate::timers::init(); + + info!(" use {} scheduler.", Scheduler::scheduler_name()); +} + +/// Initializes the task scheduler for secondary CPUs. +pub fn init_scheduler_secondary() { + crate::processor::init_secondary(); +} + +/// Handles periodic timer ticks for the task manager. +/// +/// For example, advance scheduler states, checks timed events, etc. +#[cfg(feature = "irq")] +#[doc(cfg(feature = "irq"))] +pub fn on_timer_tick() { + crate::timers::check_events(); + crate::schedule::scheduler_timer_tick(); +} + +#[cfg(feature = "preempt")] +/// Checks if the current task should be preempted. +/// This api called after handle irq,it may be on a +/// disable_preempt ctx +pub fn current_check_preempt_pending() { + let curr = crate::current(); + // if task is already exited or blocking, + // no need preempt, they are rescheduling + if curr.get_preempt_pending() && curr.can_preempt() && !curr.is_exited() && !curr.is_blocking() + { + debug!( + "current {} is to be preempted , allow {}", + curr.id_name(), + curr.can_preempt() + ); + crate::schedule::schedule() + } +} + +/// Spawns a new task with the given parameters. +/// +/// Returns the task reference. +pub fn spawn_raw(f: F, name: String, stack_size: usize) -> AxTaskRef +where + F: FnOnce() + Send + 'static, +{ + let task = new_task( + f, + name, + stack_size, + #[cfg(feature = "monolithic")] + KERNEL_PROCESS_ID, + #[cfg(feature = "monolithic")] + 0, + ); + Processor::first_add_task(task.clone()); + task +} + +/// Spawns a new task with the default parameters. +/// +/// The default task name is an empty string. The default task stack size is +/// [`axconfig::TASK_STACK_SIZE`]. +/// +/// Returns the task reference. +pub fn spawn(f: F) -> AxTaskRef +where + F: FnOnce() + Send + 'static, +{ + spawn_raw(f, "".into(), axconfig::TASK_STACK_SIZE) +} + +/// Set the priority for current task. +/// +/// The range of the priority is dependent on the underlying scheduler. For +/// example, in the [CFS] scheduler, the priority is the nice value, ranging from +/// -20 to 19. +/// +/// Returns `true` if the priority is set successfully. +/// +/// [CFS]: https://en.wikipedia.org/wiki/Completely_Fair_Scheduler +pub fn set_priority(prio: isize) -> bool { + crate::schedule::set_current_priority(prio) +} + +/// Current task gives up the CPU time voluntarily, and switches to another +/// ready task. +pub fn yield_now() { + crate::schedule::yield_current(); +} + +/// Current task is going to sleep for the given duration. +/// +/// If the feature `irq` is not enabled, it uses busy-wait instead. +pub fn sleep(dur: core::time::Duration) { + sleep_until(axhal::time::current_time() + dur); +} + +/// Current task is going to sleep, it will be woken up at the given deadline. +/// +/// If the feature `irq` is not enabled, it uses busy-wait instead. +pub fn sleep_until(deadline: axhal::time::TimeValue) { + #[cfg(feature = "irq")] + crate::schedule::schedule_timeout(deadline); + #[cfg(not(feature = "irq"))] + axhal::time::busy_wait_until(deadline); +} +/// wake up task +pub fn wakeup_task(task: AxTaskRef) { + crate::schedule::wakeup_task(task) +} + +/// Current task is going to sleep, it will be woken up when the given task exits. +/// +/// If the given task is already exited, it will return immediately. +pub fn join(task: &AxTaskRef) -> Option { + if let Some(wait_queue) = get_wait_for_exit_queue(task) { + wait_queue.wait_until(|| task.state() == TaskState::Exited) + } + Some(task.get_exit_code()) +} + +#[cfg(feature = "monolithic")] +/// Current task is going to sleep. It will be woken up when the given task does exec syscall or exit. +pub fn vfork_suspend(task: &AxTaskRef) { + if let Some(wait_queue) = get_wait_for_exit_queue(task) { + wait_queue.wait_until(|| { + // If the given task does the exec syscall, it will be the leader of the new process. + task.is_leader() || task.state() == TaskState::Exited + }); + } +} + +#[cfg(feature = "monolithic")] +/// To wake up the task that is blocked because vfork out of current task +pub fn wake_vfork_process(task: &AxTaskRef) { + if let Some(wait_queue) = get_wait_for_exit_queue(task) { + wait_queue.notify_all() + } +} + +/// Exits the current task. +pub fn exit(exit_code: i32) -> ! { + crate::schedule::exit_current(exit_code) +} + +/// The idle task routine. +/// +/// It runs an infinite loop that keeps calling [`yield_now()`]. +pub fn run_idle() -> ! { + loop { + yield_now(); + //debug!("idle task: waiting for IRQs..."); + #[cfg(feature = "irq")] + axhal::arch::wait_for_irqs(); + } +} + +/// Dump the current task backtrace. +pub fn dump_curr_backtrace() { + dump_task_backtrace(current().as_task_ref().clone()); +} + +/// Dump the given task backtrace. +pub fn dump_task_backtrace(task: AxTaskRef) { + use axbacktrace::{dump_backtrace, StackInfo, Unwind, UnwindIf}; + + let stack_low = task.get_kernel_stack_down().unwrap(); + let stack_high = task.get_kernel_stack_top().unwrap(); + info!( + "dump task: {}, stack range: {:#016x}: {:#016x}", + task.id_name(), + stack_low, + stack_high + ); + let stack_info = StackInfo::new(stack_low, stack_high); + + //Init Unwind instance from current context + let curr = crate::current(); + let mut unwind = if curr.ptr_eq(&task) { + Unwind::new_from_cur_ctx(stack_info) + } else { + let (pc, fp) = task.ctx_unwind(); + Unwind::new(pc, fp, stack_info) + }; + // dump current task trace + dump_backtrace(&mut unwind); +} diff --git a/modules/axtask/src/api_s.rs b/modules/axtask/src/api_s.rs new file mode 100644 index 0000000..28e2754 --- /dev/null +++ b/modules/axtask/src/api_s.rs @@ -0,0 +1,46 @@ +//! Task APIs for single-task configuration. + +/// For single-task situation, we just relax the CPU and wait for incoming +/// interrupts. +pub fn yield_now() { + if cfg!(feature = "irq") { + axhal::arch::wait_for_irqs(); + } else { + core::hint::spin_loop(); + } +} + +/// For single-task situation, we just busy wait for the given duration. +pub fn sleep(dur: core::time::Duration) { + axhal::time::busy_wait(dur); +} + +/// For single-task situation, we just busy wait until reaching the given +/// deadline. +pub fn sleep_until(deadline: axhal::time::TimeValue) { + axhal::time::busy_wait_until(deadline); +} + +// arch_boot +extern "C" { + fn current_boot_stack() -> *mut u8; +} + +pub fn global_unique_ts() -> (usize, usize) { + let boot_stack = unsafe { + current_boot_stack() as usize + }; + (boot_stack, boot_stack + axconfig::TASK_STACK_SIZE) +} + +pub fn dump_curr_backtrace() { + //Init Unwind instance from current context + use axbacktrace::{dump_backtrace, Unwind, UnwindIf, StackInfo}; + let stack = global_unique_ts(); + let stack_info = StackInfo::new(stack.0, stack.1); + axlog::info!("dump task stack range: {:#016x}: {:#016x}", + stack.0, stack.1); + let mut unwind = Unwind::new_from_cur_ctx(stack_info); + // dump current task trace + dump_backtrace(&mut unwind); +} diff --git a/modules/axtask/src/lib.rs b/modules/axtask/src/lib.rs new file mode 100644 index 0000000..c52432b --- /dev/null +++ b/modules/axtask/src/lib.rs @@ -0,0 +1,61 @@ +//! [ArceOS](https://github.com/rcore-os/arceos) task management module. +//! +//! This module provides primitives for task management, including task +//! creation, scheduling, sleeping, termination, etc. The scheduler algorithm +//! is configurable by cargo features. +//! +//! # Cargo Features +//! +//! - `multitask`: Enable multi-task support. If it's enabled, complex task +//! management and scheduling is used, as well as more task-related APIs. +//! Otherwise, only a few APIs with naive implementation is available. +//! - `irq`: Interrupts are enabled. If this feature is enabled, timer-based +//! APIs can be used, such as [`sleep`], [`sleep_until`], and +//! [`WaitQueue::wait_timeout`]. +//! - `preempt`: Enable preemptive scheduling. +//! - `sched_fifo`: Use the [FIFO cooperative scheduler][1]. It also enables the +//! `multitask` feature if it is enabled. This feature is enabled by default, +//! and it can be overriden by other scheduler features. +//! - `sched_rr`: Use the [Round-robin preemptive scheduler][2]. It also enables +//! the `multitask` and `preempt` features if it is enabled. +//! - `sched_cfs`: Use the [Completely Fair Scheduler][3]. It also enables the +//! the `multitask` and `preempt` features if it is enabled. +//! +//! [1]: scheduler::FifoScheduler +//! [2]: scheduler::RRScheduler +//! [3]: scheduler::CFScheduler + +#![cfg_attr(not(test), no_std)] +#![feature(doc_cfg)] +#![feature(doc_auto_cfg)] +#![feature(stmt_expr_attributes)] +cfg_if::cfg_if! { + if #[cfg(feature = "multitask")] { + #[macro_use] + extern crate log; + extern crate alloc; + + mod processor; + mod task; + + mod schedule; + mod api; + mod wait_list; + mod wait_queue; + + pub use wait_list::{WaitTaskList, WaitTaskNode}; + pub use crate::task::TaskState; + pub use taskctx::{SchedPolicy, SchedStatus, MAX_RT_PRIO}; + + #[cfg(feature = "irq")] + mod timers; + + #[doc(cfg(feature = "multitask"))] + pub use self::api::*; + pub use self::api::{sleep, sleep_until, yield_now}; + + } else { + mod api_s; + pub use self::api_s::*; + } +} diff --git a/modules/axtask/src/processor.rs b/modules/axtask/src/processor.rs new file mode 100644 index 0000000..20cff80 --- /dev/null +++ b/modules/axtask/src/processor.rs @@ -0,0 +1,282 @@ +use alloc::collections::VecDeque; +use alloc::sync::Arc; +use core::mem::ManuallyDrop; +use core::sync::atomic::{AtomicUsize, Ordering}; +use lazy_init::LazyInit; +use scheduler::BaseScheduler; +use spinlock::{SpinNoIrq, SpinNoIrqOnly, SpinNoIrqOnlyGuard}; + +#[cfg(feature = "monolithic")] +use axhal::KERNEL_PROCESS_ID; + +use crate::task::{new_init_task, new_task, CurrentTask, TaskState}; + +use crate::{AxTaskRef, Scheduler, WaitQueue}; + +static PROCESSORS: SpinNoIrqOnly> = + SpinNoIrqOnly::new(VecDeque::new()); + +#[percpu::def_percpu] +static PROCESSOR: LazyInit = LazyInit::new(); + +/// Processor on coresponding core +pub struct Processor { + /// Processor SCHEDULER + scheduler: SpinNoIrq, + /// Owned this Processor task num + task_nr: AtomicUsize, + /// The exited task-queue of the current processor + exited_tasks: SpinNoIrq>, + /// GC wait or notify use + gc_wait: WaitQueue, + /// Pre save ctx when processor switch ctx + prev_ctx_save: SpinNoIrq, + /// The idle task of the processor + idle_task: AxTaskRef, + /// The gc task of the processor + gc_task: AxTaskRef, +} + +unsafe impl Sync for Processor {} +unsafe impl Send for Processor {} + +impl Processor { + /// Create a new processor + pub fn new(idle_task: AxTaskRef) -> Self { + let gc_task = new_task( + gc_entry, + "gc".into(), + axconfig::TASK_STACK_SIZE, + #[cfg(feature = "monolithic")] + KERNEL_PROCESS_ID, + #[cfg(feature = "monolithic")] + 0, + ); + + Processor { + scheduler: SpinNoIrq::new(Scheduler::new()), + idle_task, + prev_ctx_save: SpinNoIrq::new(PrevCtxSave::new_empty()), + exited_tasks: SpinNoIrq::new(VecDeque::new()), + gc_wait: WaitQueue::new(), + task_nr: AtomicUsize::new(0), + gc_task, + } + } + + /// The idle task of the processor + pub fn idle_task(&self) -> &AxTaskRef { + &self.idle_task + } + + pub(crate) fn kick_exited_task(&self, task: &AxTaskRef) { + self.exited_tasks.lock().push_back(task.clone()); + self.task_nr.fetch_sub(1, Ordering::Acquire); + self.gc_wait.notify_one(); + } + + pub(crate) fn clean_task_wait(&self) { + loop { + // Drop all exited tasks and recycle resources. + let n = self.exited_tasks.lock().len(); + for _ in 0..n { + // Do not do the slow drops in the critical section. + let task = self.exited_tasks.lock().pop_front(); + if let Some(task) = task { + if Arc::strong_count(&task) == 1 { + // If I'm the last holder of the task, drop it immediately. + debug!("clean task :{} ", task.id().as_u64()); + drop(task); + } else { + // Otherwise (e.g, `switch_to` is not compeleted, held by the + // joiner, etc), push it back and wait for them to drop first. + self.exited_tasks.lock().push_back(task); + } + } + } + // gc wait other task exit + self.gc_wait.wait(); + } + } + + #[inline] + /// Pick one task from processor + pub(crate) fn pick_next_task(&self) -> AxTaskRef { + self.scheduler + .lock() + .pick_next_task() + .unwrap_or_else(|| self.idle_task.clone()) + } + + #[inline] + /// Add curr task to Processor, it ususally add to back + pub(crate) fn put_prev_task(&self, task: AxTaskRef, front: bool) { + self.scheduler.lock().put_prev_task(task, front); + } + + #[inline] + /// Add task to processor, now just put it to own processor + /// TODO: support task migrate on differ processor + pub(crate) fn add_task(task: AxTaskRef) { + task.get_processor().scheduler.lock().add_task(task); + } + + #[inline] + /// Processor Clean + pub(crate) fn task_tick(&self, task: &AxTaskRef) -> bool { + self.scheduler.lock().task_tick(task) + } + + #[inline] + /// Processor Clean + pub(crate) fn set_priority(&self, task: &AxTaskRef, prio: isize) -> bool { + self.scheduler.lock().set_priority(task, prio) + } + + #[inline] + /// update prev_ctx_save when ctx_switch + pub(crate) fn set_prev_ctx_save(&self, prev_save: PrevCtxSave) { + *self.prev_ctx_save.lock() = prev_save; + } + + #[inline] + /// post process prev_ctx_save + pub(crate) fn switch_post(&self) { + let mut prev_ctx = self.prev_ctx_save.lock(); + if let Some(prev_lock_state) = prev_ctx.get_mut().take() { + // Note the lock sequence: prev_lock_state.lock -> prev_ctx_save.lock -> + // prev_ctx_save.unlock -> prev_lock_state.unlock + drop(prev_ctx); + ManuallyDrop::into_inner(prev_lock_state); + } else { + panic!("no prev ctx"); + } + + #[cfg(feature = "irq")] + { + let curr = crate::current(); + match curr.get_irq_state() { + true => axhal::arch::enable_irqs(), + false => axhal::arch::disable_irqs(), + } + } + } + + #[inline] + /// Processor Clean + fn clean(&self) { + self.exited_tasks.lock().clear() + } + + #[inline] + /// Processor Clean all + pub fn clean_all() { + for p in PROCESSORS.lock().iter() { + p.clean() + } + } + + #[inline] + /// First add task to processor + pub fn first_add_task(task: AxTaskRef) { + let p = Processor::select_one_processor(); + task.init_processor(p); + p.scheduler.lock().add_task(task); + p.task_nr.fetch_add(1, Ordering::Relaxed); + } + + #[inline] + /// gc init + pub(crate) fn gc_init(&'static self) { + self.gc_task.init_processor(&self); + self.scheduler.lock().add_task(self.gc_task.clone()); + } + + #[inline] + /// Add task to processor + fn select_one_processor() -> &'static Processor { + PROCESSORS + .lock() + .iter() + .min_by_key(|p| p.task_nr.load(Ordering::Acquire)) + .unwrap() + } +} + +/// Get current processor pointer +/// +/// # Safety +/// The processor pointer is a per-core global variable, so it is safe to access it. +pub fn current_processor() -> &'static Processor { + unsafe { PROCESSOR.current_ref_raw() } +} + +pub(crate) struct PrevCtxSave(Option>>); + +impl PrevCtxSave { + pub(crate) fn new( + prev_lock_state: ManuallyDrop>, + ) -> PrevCtxSave { + Self(Some(prev_lock_state)) + } + + const fn new_empty() -> PrevCtxSave { + Self(None) + } + + #[allow(unused)] + pub(crate) fn get(&self) -> &Option>> { + &self.0 + } + + pub(crate) fn get_mut( + &mut self, + ) -> &mut Option>> { + &mut self.0 + } +} + +fn gc_entry() { + current_processor().clean_task_wait(); +} + +pub(crate) fn init() { + const IDLE_TASK_STACK_SIZE: usize = 4096; + + let idle_task = new_task( + || crate::run_idle(), + "idle".into(), // FIXME: name 现已被用作 prctl 使用的程序名,应另选方式判断 idle 进程 + IDLE_TASK_STACK_SIZE, + #[cfg(feature = "monolithic")] + KERNEL_PROCESS_ID, + #[cfg(feature = "monolithic")] + 0, + ); + + let main_task = new_init_task("main".into()); + #[cfg(feature = "monolithic")] + main_task.set_process_id(KERNEL_PROCESS_ID); + + let processor = Processor::new(idle_task.clone()); + PROCESSOR.with_current(|i| i.init_by(processor)); + current_processor().gc_init(); + PROCESSORS.lock().push_back(current_processor()); + + main_task.init_processor(current_processor()); + + unsafe { CurrentTask::init_current(main_task) } +} + +pub(crate) fn init_secondary() { + // FIXME: name 现已被用作 prctl 使用的程序名,应另选方式判断 idle 进程 + let idle_task = new_init_task("idle".into()); + #[cfg(feature = "monolithic")] + idle_task.set_process_id(KERNEL_PROCESS_ID); + + let processor = Processor::new(idle_task.clone()); + PROCESSOR.with_current(|i| i.init_by(processor)); + current_processor().gc_init(); + PROCESSORS.lock().push_back(current_processor()); + + unsafe { CurrentTask::init_current(idle_task) }; +} diff --git a/modules/axtask/src/schedule.rs b/modules/axtask/src/schedule.rs new file mode 100644 index 0000000..99821b9 --- /dev/null +++ b/modules/axtask/src/schedule.rs @@ -0,0 +1,215 @@ +use alloc::{collections::BTreeMap, sync::Arc}; + +use core::mem::ManuallyDrop; + +use crate::processor::{current_processor, PrevCtxSave, Processor}; +use crate::task::{CurrentTask, TaskState}; +use crate::{AxTaskRef, WaitQueue}; +use spinlock::{SpinNoIrq, SpinNoIrqOnlyGuard}; + +/// A map to store tasks' wait queues, which stores tasks that are waiting for this task to exit. +pub(crate) static WAIT_FOR_TASK_EXITS: SpinNoIrq>> = + SpinNoIrq::new(BTreeMap::new()); + +pub(crate) fn add_wait_for_exit_queue(task: &AxTaskRef) { + WAIT_FOR_TASK_EXITS + .lock() + .insert(task.id().as_u64(), Arc::new(WaitQueue::new())); +} + +pub(crate) fn get_wait_for_exit_queue(task: &AxTaskRef) -> Option> { + WAIT_FOR_TASK_EXITS.lock().get(&task.id().as_u64()).cloned() +} + +/// When the task exits, notify all tasks that are waiting for this task to exit, and +/// then remove the wait queue of the exited task. +pub(crate) fn notify_wait_for_exit(task: &AxTaskRef) { + if let Some(wait_queue) = WAIT_FOR_TASK_EXITS.lock().remove(&task.id().as_u64()) { + wait_queue.notify_all(); + } +} + +pub(crate) fn exit_current(exit_code: i32) -> ! { + let curr = crate::current(); + debug!("task exit: {}, exit_code={}", curr.id_name(), exit_code); + curr.set_state(TaskState::Exited); + + // maybe others join on this thread + // must set state before notify wait_exit + notify_wait_for_exit(curr.as_task_ref()); + + current_processor().kick_exited_task(curr.as_task_ref()); + if curr.is_init() { + Processor::clean_all(); + axhal::misc::terminate(); + } else { + curr.set_exit_code(exit_code); + schedule(); + } + unreachable!("task exited!"); +} + +pub(crate) fn yield_current() { + let curr = crate::current(); + assert!(curr.is_runable()); + trace!("task yield: {}", curr.id_name()); + schedule(); +} + +/// Sleep the current task until the deadline. +#[cfg(feature = "irq")] +pub fn schedule_timeout(deadline: axhal::time::TimeValue) -> bool { + let curr = crate::current(); + debug!("task sleep: {}, deadline={:?}", curr.id_name(), deadline); + assert!(!curr.is_idle()); + crate::timers::set_alarm_wakeup(deadline, curr.clone()); + schedule(); + let timeout = axhal::time::current_time() >= deadline; + // may wake up by others + crate::timers::cancel_alarm(curr.as_task_ref()); + timeout +} + +/// Check whether the current task should be preempted. +#[cfg(feature = "irq")] +pub fn scheduler_timer_tick() { + let curr = crate::current(); + if !curr.is_idle() && current_processor().task_tick(curr.as_task_ref()) { + #[cfg(feature = "preempt")] + curr.set_preempt_pending(true); + } +} + +/// Set the sched priority of the current task. +pub fn set_current_priority(prio: isize) -> bool { + current_processor().set_priority(crate::current().as_task_ref(), prio) +} + +/// Wake up the given task from the blocking state. +pub fn wakeup_task(task: AxTaskRef) { + let mut state = task.state_lock_manual(); + match **state { + TaskState::Blocking => **state = TaskState::Runable, + TaskState::Runable => (), + TaskState::Blocked => { + debug!("task unblock: {}", task.id_name()); + **state = TaskState::Runable; + ManuallyDrop::into_inner(state); + // may be other processor wake up + Processor::add_task(task.clone()); + return; + } + _ => panic!("try to wakeup {:?} unexpect state {:?}", task.id(), **state), + } + ManuallyDrop::into_inner(state); +} + +/// Schedule the next task to run. +pub fn schedule() { + let next_task = current_processor().pick_next_task(); + switch_to(next_task); +} + +fn switch_to(mut next_task: AxTaskRef) { + let prev_task = crate::current(); + + // task in a disable_preempt context? it not allowed ctx switch + #[cfg(feature = "preempt")] + assert!( + prev_task.can_preempt(), + "task can_preempt failed {}", + prev_task.id_name() + ); + + // When the prev_task state_lock is locked, it records the irq configuration of + // the prev_task at that time, after swich(in switch_post) it would be unlocked, + // and restore the irq configuration to the lock_state store(NOTE: it own the prev_task). + // + // so have to save the prev_task irq config here,and restore it after swich_post + #[cfg(feature = "irq")] + prev_task.set_irq_state(axhal::arch::irqs_enabled()); + + // Here must lock curr state, and no one can change curr state + // when excuting ctx_switch + let mut prev_state_lock = prev_task.state_lock_manual(); + + match **prev_state_lock { + TaskState::Runable => { + if next_task.is_idle() { + next_task = prev_task.clone(); + } else if !prev_task.is_idle() { + #[cfg(feature = "preempt")] + current_processor() + .put_prev_task(prev_task.clone(), prev_task.get_preempt_pending()); + #[cfg(not(feature = "preempt"))] + current_processor().put_prev_task(prev_task.clone(), false); + } + } + TaskState::Blocking => { + debug!("task block: {}", prev_task.id_name()); + **prev_state_lock = TaskState::Blocked; + } + TaskState::Exited => {} + _ => { + panic!("unexpect state when switch_to happend "); + } + } + + #[cfg(feature = "preempt")] + //reset preempt pending + next_task.set_preempt_pending(false); + + if prev_task.ptr_eq(&next_task) { + ManuallyDrop::into_inner(prev_state_lock); + return; + } + + // 当任务进行切换时,更新两个任务的时间统计信息 + #[cfg(feature = "monolithic")] + { + let current_timestamp = axhal::time::current_time_nanos() as usize; + next_task.time_stat_when_switch_to(current_timestamp); + prev_task.time_stat_when_switch_from(current_timestamp); + } + + trace!( + "context switch: {} -> {}", + prev_task.id_name(), + next_task.id_name(), + ); + unsafe { + let prev_ctx_ptr = prev_task.ctx_mut_ptr(); + let next_ctx_ptr = next_task.ctx_mut_ptr(); + + // The strong reference count of `prev_task` will be decremented by 1, + // but won't be dropped until `gc_entry()` is called. + assert!( + Arc::strong_count(prev_task.as_task_ref()) > 1, + "task id {} strong count {}", + prev_task.id().as_u64(), + Arc::strong_count(prev_task.as_task_ref()) + ); + + assert!(Arc::strong_count(&next_task) >= 1); + #[cfg(feature = "monolithic")] + { + let page_table_token = *next_task.page_table_token.get(); + if page_table_token != 0 { + axhal::arch::write_page_table_root0(page_table_token.into()); + } + } + + let prev_ctx = PrevCtxSave::new(core::mem::transmute::< + ManuallyDrop>, + ManuallyDrop>, + >(prev_state_lock)); + + current_processor().set_prev_ctx_save(prev_ctx); + + CurrentTask::set_current(prev_task, next_task); + + axhal::arch::task_context_switch(&mut (*prev_ctx_ptr), &(*next_ctx_ptr)); + + current_processor().switch_post(); + } +} diff --git a/modules/axtask/src/task.rs b/modules/axtask/src/task.rs new file mode 100644 index 0000000..8fd329a --- /dev/null +++ b/modules/axtask/src/task.rs @@ -0,0 +1,336 @@ +use alloc::{string::String, sync::Arc}; + +use core::{mem::ManuallyDrop, ops::Deref}; + +use alloc::boxed::Box; + +use memory_addr::VirtAddr; + +#[cfg(feature = "monolithic")] +use axhal::arch::TrapFrame; + +use crate::{ + current_processor, processor::Processor, schedule::add_wait_for_exit_queue, AxTask, AxTaskRef, +}; + +pub use taskctx::{TaskId, TaskInner}; + +use core::sync::atomic::{AtomicBool, Ordering}; +use spinlock::{SpinNoIrq, SpinNoIrqOnly, SpinNoIrqOnlyGuard}; + +extern "C" { + fn _stdata(); + fn _etdata(); + fn _etbss(); +} + +#[cfg(feature = "tls")] +pub(crate) fn tls_area() -> (usize, usize) { + (_stdata as usize, _etbss as usize) +} + +/// The possible states of a task. +#[repr(u8)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[allow(missing_docs)] +pub enum TaskState { + Runable = 1, + Blocking = 2, + Blocked = 3, + Exited = 4, +} + +pub struct ScheduleTask { + inner: TaskInner, + /// Store task irq state + irq_state: AtomicBool, + /// Task state + state: SpinNoIrqOnly, + /// Task own which Processor + processor: SpinNoIrq>, +} + +impl ScheduleTask { + fn new(inner: TaskInner, irq_init_state: bool) -> Self { + Self { + state: SpinNoIrqOnly::new(TaskState::Runable), + processor: SpinNoIrq::new(None), + irq_state: AtomicBool::new(irq_init_state), + inner, + } + } + + #[inline] + /// lock the task state and ctx_ptr access + pub fn state_lock_manual(&self) -> ManuallyDrop> { + ManuallyDrop::new(self.state.lock()) + } + + #[inline] + /// set the state of the task + pub fn state(&self) -> TaskState { + *self.state.lock() + } + + #[inline] + /// set the state of the task + pub fn set_state(&self, state: TaskState) { + *self.state.lock() = state + } + + /// Whether the task is Exited + #[inline] + pub fn is_exited(&self) -> bool { + matches!(*self.state.lock(), TaskState::Exited) + } + + /// Whether the task is runnalbe + #[inline] + pub fn is_runable(&self) -> bool { + matches!(*self.state.lock(), TaskState::Runable) + } + + /// Whether the task is blocking + #[inline] + pub fn is_blocking(&self) -> bool { + matches!(*self.state.lock(), TaskState::Blocking) + } + + /// Whether the task is blocked + #[inline] + pub fn is_blocked(&self) -> bool { + matches!(*self.state.lock(), TaskState::Blocked) + } + + /// Whether the task is blocked + #[inline] + pub(crate) fn init_processor(&self, p: &'static Processor) { + *self.processor.lock() = Some(p); + } + + /// Whether the task is blocked + #[inline] + pub(crate) fn get_processor(&self) -> &'static Processor { + self.processor + .lock() + .as_ref() + .expect("task {} processor not init") + } + + /// set irq state + #[cfg(feature = "irq")] + #[inline] + pub(crate) fn set_irq_state(&self, irq_state: bool) { + self.irq_state.store(irq_state, Ordering::Relaxed); + } + + /// get irq state + #[cfg(feature = "irq")] + #[inline] + pub(crate) fn get_irq_state(&self) -> bool { + self.irq_state.load(Ordering::Relaxed) + } +} + +impl Deref for ScheduleTask { + type Target = TaskInner; + #[inline] + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +#[cfg(feature = "monolithic")] +/// Create a new task. +/// +/// # Arguments +/// - `entry`: The entry function of the task. +/// - `name`: The name of the task. +/// - `stack_size`: The size of the stack. +/// - `process_id`: The process ID of the task. +/// - `page_table_token`: The page table token of the task. +/// - `sig_child`: Whether the task will send a signal to its parent when it exits. +pub fn new_task( + entry: F, + name: String, + stack_size: usize, + process_id: u64, + page_table_token: usize, +) -> AxTaskRef +where + F: FnOnce() + Send + 'static, +{ + use axhal::time::current_time_nanos; + + use crate::schedule::add_wait_for_exit_queue; + + let mut task = taskctx::TaskInner::new( + entry, + name, + stack_size, + process_id, + page_table_token, + #[cfg(feature = "tls")] + tls_area(), + ); + #[cfg(feature = "tls")] + let tls = VirtAddr::from(task.get_tls_ptr()); + #[cfg(not(feature = "tls"))] + let tls = VirtAddr::from(0); + + // 当 trap 进内核的时候,内核栈会先存储 trap frame,然后再存储 task context + task.init_task_ctx( + task_entry as usize, + (task.get_kernel_stack_top().unwrap() - core::mem::size_of::()).into(), + tls, + ); + + // 设置 CPU 亲和集 + task.set_cpu_set((1 << axconfig::SMP) - 1, 1, axconfig::SMP); + + task.reset_time_stat(current_time_nanos() as usize); + + // a new task start, irq should be enabled by default + let axtask = Arc::new(AxTask::new(ScheduleTask::new(task, true))); + add_wait_for_exit_queue(&axtask); + axtask +} + +#[cfg(not(feature = "monolithic"))] +/// Create a new task. +/// +/// # Arguments +/// - `entry`: The entry function of the task. +/// - `name`: The name of the task. +/// - `stack_size`: The size of the kernel stack. +pub fn new_task(entry: F, name: String, stack_size: usize) -> AxTaskRef +where + F: FnOnce() + Send + 'static, +{ + let mut task = taskctx::TaskInner::new( + entry, + name, + stack_size, + #[cfg(feature = "tls")] + tls_area(), + ); + #[cfg(feature = "tls")] + let tls = VirtAddr::from(task.get_tls_ptr()); + #[cfg(not(feature = "tls"))] + let tls = VirtAddr::from(0); + + task.init_task_ctx( + task_entry as usize, + task.get_kernel_stack_top().unwrap().into(), + tls, + ); + // a new task start, irq should be enabled by default + let axtask = Arc::new(AxTask::new(ScheduleTask::new(task, true))); + add_wait_for_exit_queue(&axtask); + axtask +} + +pub(crate) fn new_init_task(name: String) -> AxTaskRef { + // init task irq should be disabled by default + // it would be reinit when switch happend + let axtask = Arc::new(AxTask::new(ScheduleTask::new( + taskctx::TaskInner::new_init( + name, + #[cfg(feature = "tls")] + tls_area(), + ), + false, + ))); + + #[cfg(feature = "monolithic")] + // 设置 CPU 亲和集 + axtask.set_cpu_set((1 << axconfig::SMP) - 1, 1, axconfig::SMP); + + add_wait_for_exit_queue(&axtask); + axtask +} + +/// A wrapper of [`AxTaskRef`] as the current task. +pub struct CurrentTask(ManuallyDrop); + +impl CurrentTask { + pub(crate) fn try_get() -> Option { + let ptr: *const super::AxTask = taskctx::current_task_ptr(); + if !ptr.is_null() { + Some(Self(unsafe { ManuallyDrop::new(AxTaskRef::from_raw(ptr)) })) + } else { + None + } + } + + pub(crate) fn get() -> Self { + Self::try_get().expect("current task is uninitialized") + } + + /// Converts [`CurrentTask`] to [`AxTaskRef`]. + pub fn as_task_ref(&self) -> &AxTaskRef { + &self.0 + } + + pub(crate) fn clone(&self) -> AxTaskRef { + self.0.deref().clone() + } + + pub(crate) fn ptr_eq(&self, other: &AxTaskRef) -> bool { + Arc::ptr_eq(&self.0, other) + } + + pub(crate) unsafe fn init_current(init_task: AxTaskRef) { + #[cfg(feature = "tls")] + axhal::arch::write_thread_pointer(init_task.get_tls_ptr()); + let ptr = Arc::into_raw(init_task); + taskctx::set_current_task_ptr(ptr); + } + + pub(crate) unsafe fn set_current(prev: Self, next: AxTaskRef) { + let Self(arc) = prev; + ManuallyDrop::into_inner(arc); // `call Arc::drop()` to decrease prev task reference count. + let ptr = Arc::into_raw(next); + taskctx::set_current_task_ptr(ptr); + } +} + +impl Deref for CurrentTask { + type Target = ScheduleTask; + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +extern "C" fn task_entry() -> ! { + // SAFETY: INIT when switch_to + // First into task entry, manually perform the subsequent work of switch_to + + current_processor().switch_post(); + + let task = crate::current(); + if let Some(entry) = task.get_entry() { + cfg_if::cfg_if! { + if #[cfg(feature = "monolithic")] { + use axhal::KERNEL_PROCESS_ID; + if task.get_process_id() == KERNEL_PROCESS_ID { + // 是初始调度进程,直接执行即可 + unsafe { Box::from_raw(entry)() }; + // 继续执行对应的函数 + } else { + // 需要通过切换特权级进入到对应的应用程序 + let kernel_sp = task.get_kernel_stack_top().unwrap(); + // 切换页表已经在switch实现了 + // 记得更新时间 + task.time_stat_from_kernel_to_user(axhal::time::current_time_nanos() as usize); + axhal::arch::first_into_user(kernel_sp); + } + } + else { + unsafe { Box::from_raw(entry)() }; + } + } + } + // only for kernel task + crate::exit(0); +} diff --git a/modules/axtask/src/tests.rs b/modules/axtask/src/tests.rs new file mode 100644 index 0000000..c3dbe0a --- /dev/null +++ b/modules/axtask/src/tests.rs @@ -0,0 +1,130 @@ +use core::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Mutex, Once}; + +use crate::{self as axtask, current, WaitQueue}; + +static INIT: Once = Once::new(); +static SERIAL: Mutex<()> = Mutex::new(()); + +#[test] +fn test_sched_fifo() { + let _lock = SERIAL.lock(); + INIT.call_once(axtask::init_scheduler); + + const NUM_TASKS: usize = 10; + static FINISHED_TASKS: AtomicUsize = AtomicUsize::new(0); + + for i in 0..NUM_TASKS { + axtask::spawn_raw( + move || { + println!("sched_fifo: Hello, task {}! ({})", i, current().id_name()); + axtask::yield_now(); + let order = FINISHED_TASKS.fetch_add(1, Ordering::Relaxed); + assert_eq!(order, i); // FIFO scheduler + }, + format!("T{}", i), + 0x1000, + ); + } + + while FINISHED_TASKS.load(Ordering::Relaxed) < NUM_TASKS { + axtask::yield_now(); + } +} + +#[test] +fn test_fp_state_switch() { + let _lock = SERIAL.lock(); + INIT.call_once(axtask::init_scheduler); + + const NUM_TASKS: usize = 5; + const FLOATS: [f64; NUM_TASKS] = [ + 3.141592653589793, + 2.718281828459045, + -1.4142135623730951, + 0.0, + 0.618033988749895, + ]; + static FINISHED_TASKS: AtomicUsize = AtomicUsize::new(0); + + for (i, float) in FLOATS.iter().enumerate() { + axtask::spawn(move || { + let mut value = float + i as f64; + axtask::yield_now(); + value -= i as f64; + + println!("fp_state_switch: Float {} = {}", i, value); + assert!((value - float).abs() < 1e-9); + FINISHED_TASKS.fetch_add(1, Ordering::Relaxed); + }); + } + while FINISHED_TASKS.load(Ordering::Relaxed) < NUM_TASKS { + axtask::yield_now(); + } +} + +#[test] +fn test_wait_queue() { + let _lock = SERIAL.lock(); + INIT.call_once(axtask::init_scheduler); + + const NUM_TASKS: usize = 10; + + static WQ1: WaitQueue = WaitQueue::new(); + static WQ2: WaitQueue = WaitQueue::new(); + static COUNTER: AtomicUsize = AtomicUsize::new(0); + + for _ in 0..NUM_TASKS { + axtask::spawn(move || { + COUNTER.fetch_add(1, Ordering::Relaxed); + println!("wait_queue: task {:?} started", current().id()); + WQ1.notify_one(true); // WQ1.wait_until() + WQ2.wait(); + + assert!(!current().in_wait_queue()); + + COUNTER.fetch_sub(1, Ordering::Relaxed); + println!("wait_queue: task {:?} finished", current().id()); + WQ1.notify_one(true); // WQ1.wait_until() + }); + } + + println!("task {:?} is waiting for tasks to start...", current().id()); + WQ1.wait_until(|| COUNTER.load(Ordering::Relaxed) == NUM_TASKS); + assert_eq!(COUNTER.load(Ordering::Relaxed), NUM_TASKS); + assert!(!current().in_wait_queue()); + WQ2.notify_all(true); // WQ2.wait() + + println!( + "task {:?} is waiting for tasks to finish...", + current().id() + ); + WQ1.wait_until(|| COUNTER.load(Ordering::Relaxed) == 0); + assert_eq!(COUNTER.load(Ordering::Relaxed), 0); + assert!(!current().in_wait_queue()); +} + +#[test] +fn test_task_join() { + let _lock = SERIAL.lock(); + INIT.call_once(axtask::init_scheduler); + + const NUM_TASKS: usize = 10; + let mut tasks = Vec::with_capacity(NUM_TASKS); + + for i in 0..NUM_TASKS { + tasks.push(axtask::spawn_raw( + move || { + println!("task_join: task {}! ({})", i, current().id_name()); + axtask::yield_now(); + axtask::exit(i as _); + }, + format!("T{}", i), + 0x1000, + )); + } + + for i in 0..NUM_TASKS { + assert_eq!(tasks[i].join(), Some(i as _)); + } +} diff --git a/modules/axtask/src/timers.rs b/modules/axtask/src/timers.rs new file mode 100644 index 0000000..d4c2ea8 --- /dev/null +++ b/modules/axtask/src/timers.rs @@ -0,0 +1,45 @@ +use alloc::sync::Arc; +use axhal::time::current_time; +use lazy_init::LazyInit; +use spinlock::SpinNoIrq; +use timer_list::{TimeValue, TimerEvent, TimerList}; + +use crate::{AxTaskRef,TaskState}; + +// TODO: per-CPU +static TIMER_LIST: LazyInit>> = LazyInit::new(); + +struct TaskWakeupEvent(AxTaskRef); + +impl TimerEvent for TaskWakeupEvent { + fn callback(self, _now: TimeValue) { + crate::schedule::wakeup_task(self.0); + } +} + +pub fn set_alarm_wakeup(deadline: TimeValue, task: AxTaskRef) { + let mut timer_list = TIMER_LIST.lock(); + task.set_state(TaskState::Blocking); + timer_list.set(deadline, TaskWakeupEvent(task)); + drop(timer_list) +} + +pub fn cancel_alarm(task: &AxTaskRef) { + TIMER_LIST.lock().cancel(|t| Arc::ptr_eq(&t.0, task)); +} + +pub fn check_events() { + loop { + let now = current_time(); + let event = TIMER_LIST.lock().expire_one(now); + if let Some((_deadline, event)) = event { + event.callback(now); + } else { + break; + } + } +} + +pub fn init() { + TIMER_LIST.init_by(SpinNoIrq::new(TimerList::new())); +} diff --git a/modules/axtask/src/wait_list.rs b/modules/axtask/src/wait_list.rs new file mode 100644 index 0000000..800ca63 --- /dev/null +++ b/modules/axtask/src/wait_list.rs @@ -0,0 +1,118 @@ +use crate::schedule::wakeup_task; +use crate::task::TaskState; +use crate::AxTaskRef; +use alloc::sync::Arc; +use core::ops::Deref; + +use linked_list::{GetLinks, Links, List}; + +/// A task wrapper. +/// +/// It add extra states to use in [`linked_list::List`]. +pub struct WaitTaskNode { + inner: AxTaskRef, + links: Links, +} + +impl GetLinks for WaitTaskNode { + type EntryType = Self; + + #[inline] + fn get_links(t: &Self) -> &Links { + &t.links + } +} + +impl WaitTaskNode { + /// Creates a new FifoTask from the inner task struct. + pub const fn new(inner: AxTaskRef) -> Self { + Self { + inner, + links: Links::new(), + } + } + + /// Returns a reference to the inner task struct. + pub const fn inner(&self) -> &AxTaskRef { + &self.inner + } +} + +impl Deref for WaitTaskNode { + type Target = AxTaskRef; + #[inline] + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +/// A simple FIFO wait task list +/// +/// When a task is added to the list, it's placed at the end of the waitlist. +/// When picking the next task to run, the head of the wait list is taken. +pub struct WaitTaskList { + list: List>, +} + +impl WaitTaskList { + /// Creates a new empty WaitList. + pub const fn new() -> Self { + Self { list: List::new() } + } + + /// add wait to list back + pub fn prepare_to_wait(&mut self, task: Arc) { + task.inner().set_state(TaskState::Blocking); + self.list.push_back(task); + } + + /// Removes the given Node + /// + /// # Safety + /// + /// Callers must ensure that `data` is either on this list or in no list. It being on another + /// list leads to memory unsafety. + pub fn remove(&mut self, node: &Arc) -> Option> { + unsafe { self.list.remove(node) } + } + + /// notify special task and remove it + pub fn notify_task(&mut self, task: &AxTaskRef) -> bool { + let mut cursor = self.list.cursor_front_mut(); + let wake = loop { + match cursor.current() { + Some(node) => { + if Arc::ptr_eq(node.inner(), task) { + wakeup_task(node.inner().clone()); + break true; + } + } + None => break false, + } + cursor.move_next(); + }; + if wake { + cursor.remove_current(); + } + + false + } + + /// notify first task and remove it + pub fn notify_one(&mut self) -> bool { + if let Some(node) = self.list.pop_front() { + wakeup_task(node.inner().clone()); + return true; + } + false + } + + /// notify all task and remove it + pub fn notify_all(&mut self) { + loop { + if !self.notify_one() { + break; + } + } + } +} diff --git a/modules/axtask/src/wait_queue.rs b/modules/axtask/src/wait_queue.rs new file mode 100644 index 0000000..74a69d8 --- /dev/null +++ b/modules/axtask/src/wait_queue.rs @@ -0,0 +1,168 @@ +use crate::schedule::schedule; +#[cfg(feature = "irq")] +use crate::schedule::schedule_timeout; +use crate::wait_list::WaitTaskList; +use crate::wait_list::WaitTaskNode; +use crate::AxTaskRef; +use alloc::sync::Arc; +use spinlock::SpinNoIrq; +/// A queue to store sleeping tasks. +/// +/// # Examples +/// +/// ``` +/// use axtask::WaitQueue; +/// use core::sync::atomic::{AtomicU32, Ordering}; +/// +/// static VALUE: AtomicU32 = AtomicU32::new(0); +/// static WQ: WaitQueue = WaitQueue::new(); +/// +/// axtask::init_scheduler(); +/// // spawn a new task that updates `VALUE` and notifies the main task +/// axtask::spawn(|| { +/// assert_eq!(VALUE.load(Ordering::Relaxed), 0); +/// VALUE.fetch_add(1, Ordering::Relaxed); +/// WQ.notify_one(true); // wake up the main task +/// }); +/// +/// WQ.wait(); // block until `notify()` is called +/// assert_eq!(VALUE.load(Ordering::Relaxed), 1); +/// ``` +/// + +#[macro_export] +macro_rules! declare_wait { + ($name: ident) => { + let $name = Arc::new(WaitTaskNode::new($crate::current().as_task_ref().clone())); + }; +} + +/// A queue to store tasks that are waiting for some conditions. +pub struct WaitQueue { + // Support queue lock by external caller,use SpinNoIrq + // Arceos SpinNoirq current implementation implies irq_save, + // so it can be nested + // use linked list has good performance + queue: SpinNoIrq, +} + +impl WaitQueue { + /// Creates an empty wait queue. + pub const fn new() -> Self { + Self { + queue: SpinNoIrq::new(WaitTaskList::new()), + } + } + + // CPU0 wait CPU1 notify CPU2 signal + // q.lock() + // task.lock() + // task.state = blocking + // task.unlock() + // q.unlock() + // q.lock() + // task = q.get; + // wakeup(task) + // task == blocking + // task = runable + // q.unlock() + // schedule() + // queue.lock().remove(curr) + // + /// Blocks the current task and put it into the wait queue, until other task + /// notifies it. + pub fn wait(&self) { + declare_wait!(waiter); + self.queue.lock().prepare_to_wait(waiter.clone()); + schedule(); + + // maybe wakeup by signal or others, try to delete again + // 1. starry support UNINTERRUPT mask, no need to check + // 2. starry support INTERRUPTABLE mask, still need to check + self.queue.lock().remove(&waiter); + } + + /// Blocks the current task and put it into the wait queue, until the given + /// `condition` becomes true. + /// + /// Note that even other tasks notify this task, it will not wake up until + /// the condition becomes true. + pub fn wait_until(&self, condition: F) + where + F: Fn() -> bool, + { + declare_wait!(waiter); + loop { + if condition() { + break; + } + // maybe wakeup by signal or others, should check before push + // wait_list will do check + self.queue.lock().prepare_to_wait(waiter.clone()); + schedule(); + } + + //maybe wakeup by signal or others, try to delete again + self.queue.lock().remove(&waiter); + } + + /// Blocks the current task and put it into the wait queue, until other tasks + /// notify it, or the given duration has elapsed. + #[cfg(feature = "irq")] + pub fn wait_timeout(&self, dur: core::time::Duration) -> bool { + declare_wait!(waiter); + let deadline = axhal::time::current_time() + dur; + + self.queue.lock().prepare_to_wait(waiter.clone()); + let timeout = schedule_timeout(deadline); + + //maybe wakeup by timer or signal, try to delete again + self.queue.lock().remove(&waiter); + timeout + } + + /// Blocks the current task and put it into the wait queue, until the given + /// `condition` becomes true, or the given duration has elapsed. + /// + /// Note that even other tasks notify this task, it will not wake up until + /// the above conditions are met. + #[cfg(feature = "irq")] + pub fn wait_timeout_until(&self, dur: core::time::Duration, condition: F) -> bool + where + F: Fn() -> bool, + { + declare_wait!(waiter); + let deadline = axhal::time::current_time() + dur; + let mut timeout = false; + loop { + if condition() { + break; + } + //maybe wakeup by signal or others, should check before push + self.queue.lock().prepare_to_wait(waiter.clone()); + timeout = schedule_timeout(deadline); + if timeout { + break; + } + } + + //maybe wakeup by timer or signal, try to delete again + self.queue.lock().remove(&waiter); + timeout + } + + /// Wake up the given task in the wait queue. + pub fn notify_task(&self, task: &AxTaskRef) -> bool { + self.queue.lock().notify_task(task) + } + + /// Wakes up one task in the wait queue, usually the first one. + pub fn notify_one(&self) -> bool { + self.queue.lock().notify_one() + } + + /// Wakes all tasks in the wait queue. + pub fn notify_all(&self) { + self.queue.lock().notify_all() + } +} diff --git a/modules/axtrap/.gitignore b/modules/axtrap/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/modules/axtrap/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/modules/axtrap/Cargo.toml b/modules/axtrap/Cargo.toml new file mode 100644 index 0000000..2d7c571 --- /dev/null +++ b/modules/axtrap/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "axtrap" +version = "0.1.0" +edition = "2021" +keywords = ["Starry", "trap", "arch"] +description = "Trap handling module over different architectures" +authors = ["Youjie Zheng "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = [] +monolithic = ["dep:linux_syscall_api"] +irq = [] +preempt = ["axtask/preempt", "percpu/preempt", "kernel_guard/preempt"] + +[dependencies] +axlog = { workspace = true } +cfg-if = "1.0" +log = "0.4" +linux_syscall_api = { workspace = true, optional = true } +handler_table = { git = "https://github.com/Starry-OS/handler_table.git" } +lazy_init = { git = "https://github.com/Starry-OS/lazy_init.git" } +axhal = { workspace = true } +axtask = { workspace = true, optional = true } +kernel_guard = { git = "https://github.com/Starry-OS/kernel_guard.git" } +percpu = { git = "https://github.com/Starry-OS/percpu.git" } + +[target.'cfg(target_arch = "x86_64")'.dependencies] +x86 = "0.52" +x86_64 = "0.15" + +[target.'cfg(any(target_arch = "riscv32", target_arch = "riscv64"))'.dependencies] +riscv = "0.10" + +[target.'cfg(target_arch = "aarch64")'.dependencies] +aarch64-cpu = "9.3" +tock-registers = "0.8" \ No newline at end of file diff --git a/modules/axtrap/src/arch/aarch64/mem_fault.rs b/modules/axtrap/src/arch/aarch64/mem_fault.rs new file mode 100644 index 0000000..1a4ecfe --- /dev/null +++ b/modules/axtrap/src/arch/aarch64/mem_fault.rs @@ -0,0 +1,114 @@ +use tock_registers::interfaces::Readable; +use tock_registers::register_bitfields; + +use crate::trap::handle_page_fault; +use linux_syscall_api::trap::MappingFlags; + +use axhal::arch::TrapFrame; + +register_bitfields! { u64, + pub ESR_EL1_WRAPPER [ + WNR OFFSET(5) NUMBITS(1) [ + READ_FAULT = 0, + WRITE_FAULT = 1, + ], + DFSC OFFSET(0) NUMBITS(5) [ + TTBR_ADDRESS_SIZE_FAULT = 0b00_0000, + LEVEL1_ADDRESS_SIZE_FAULT = 0b00_0001, + LEVEL2_ADDRESS_SIZE_FAULT = 0b00_0010, + LEVEL3_ADDRESS_SIZE_FAULT = 0b00_0011, + LEVEL0_TRANS_FAULT = 0b00_0100, + LEVEL1_TRANS_FAULT = 0b00_0101, + LEVEL2_TRANS_FAULT = 0b00_0110, + LEVEL3_TRANS_FAULT = 0b00_0111, + LEVEL0_ACCESS_FLAG_FAULT = 0b00_1000,// When FEAT_LPA2 is implemented + LEVEL1_ACCESS_FLAG_FAULT = 0b00_1001, + LEVEL2_ACCESS_FLAG_FAULT = 0b00_1010, + LEVEL3_ACCESS_FLAG_FAULT = 0b00_1011, + LEVEL0_PERMISSION_FAULT = 0b00_1100,// When FEAT_LPA2 is implemented + LEVEL1_PERMISSION_FAULT = 0b00_1101, + LEVEL2_PERMISSION_FAULT = 0b00_1110, + LEVEL3_PERMISSION_FAULT = 0b00_1111, + ] + ] +} + +struct EsrReg(u64); + +impl Readable for EsrReg { + type T = u64; + type R = ESR_EL1_WRAPPER::Register; + + fn get(&self) -> Self::T { + self.0 + } +} + +fn do_page_fault(far: usize, esr: EsrReg) { + match esr.read_as_enum(ESR_EL1_WRAPPER::WNR) { + Some(ESR_EL1_WRAPPER::WNR::Value::READ_FAULT) => { + log::info!("EL0 data read abort "); + handle_page_fault(far.into(), MappingFlags::USER | MappingFlags::READ); + } + Some(ESR_EL1_WRAPPER::WNR::Value::WRITE_FAULT) => { + log::info!("EL0 data write abort"); + handle_page_fault(far.into(), MappingFlags::USER | MappingFlags::WRITE); + } + _ => { + panic!("impossible value"); + } + } +} + +pub fn el0_ia(far: usize, esr: u64, tf: &TrapFrame) { + let esr_wrapper = EsrReg(esr); + match esr_wrapper.read_as_enum(ESR_EL1_WRAPPER::DFSC) { + Some(ESR_EL1_WRAPPER::DFSC::Value::LEVEL0_TRANS_FAULT) + | Some(ESR_EL1_WRAPPER::DFSC::Value::LEVEL1_TRANS_FAULT) + | Some(ESR_EL1_WRAPPER::DFSC::Value::LEVEL2_TRANS_FAULT) + | Some(ESR_EL1_WRAPPER::DFSC::Value::LEVEL3_TRANS_FAULT) => { + log::info!("EL0 instruction fault"); + handle_page_fault(far.into(), MappingFlags::USER | MappingFlags::EXECUTE); + } + Some(ESR_EL1_WRAPPER::DFSC::Value::LEVEL0_PERMISSION_FAULT) + | Some(ESR_EL1_WRAPPER::DFSC::Value::LEVEL1_PERMISSION_FAULT) + | Some(ESR_EL1_WRAPPER::DFSC::Value::LEVEL2_PERMISSION_FAULT) + | Some(ESR_EL1_WRAPPER::DFSC::Value::LEVEL3_PERMISSION_FAULT) => { + log::info!("EL0 permisiion fault"); + handle_page_fault(far.into(), MappingFlags::USER | MappingFlags::EXECUTE); + } + _ => { + panic!( + "Unknown EL0 ia {:#x?} esr: {:#x?} tf {:#x?}", + far, + esr_wrapper.get(), + tf + ); + } + } +} + +pub fn el0_da(far: usize, esr: u64, tf: &TrapFrame) { + let esr_wrapper = EsrReg(esr); + match esr_wrapper.read_as_enum(ESR_EL1_WRAPPER::DFSC) { + Some(ESR_EL1_WRAPPER::DFSC::Value::LEVEL3_TRANS_FAULT) => { + log::info!("EL0 data abort l3 fault"); + do_page_fault(far, esr_wrapper); + } + Some(ESR_EL1_WRAPPER::DFSC::Value::LEVEL0_PERMISSION_FAULT) + | Some(ESR_EL1_WRAPPER::DFSC::Value::LEVEL1_PERMISSION_FAULT) + | Some(ESR_EL1_WRAPPER::DFSC::Value::LEVEL2_PERMISSION_FAULT) + | Some(ESR_EL1_WRAPPER::DFSC::Value::LEVEL3_PERMISSION_FAULT) => { + log::info!("EL0 permisiion fault"); + do_page_fault(far, esr_wrapper); + } + _ => { + panic!( + "Unknown EL0 da {:#x?} esr: {:#x?} tf {:#x?}", + far, + esr_wrapper.get(), + tf + ); + } + } +} diff --git a/modules/axtrap/src/arch/aarch64/mod.rs b/modules/axtrap/src/arch/aarch64/mod.rs new file mode 100644 index 0000000..d4f47e0 --- /dev/null +++ b/modules/axtrap/src/arch/aarch64/mod.rs @@ -0,0 +1,16 @@ +use self::trap::set_exception_vector_base; + +mod trap; + +#[cfg(feature = "monolithic")] +mod mem_fault; + +extern "C" { + fn exception_vector_base(); + +} +/// To initialize the exception vector base address. +#[inline] +pub fn init_interrupt() { + set_exception_vector_base(exception_vector_base as usize); +} diff --git a/modules/axtrap/src/arch/aarch64/trap.S b/modules/axtrap/src/arch/aarch64/trap.S new file mode 100644 index 0000000..d6239b9 --- /dev/null +++ b/modules/axtrap/src/arch/aarch64/trap.S @@ -0,0 +1,174 @@ +.macro clear_gp_regs +.irp n,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29 +mov x\n, xzr +.endr +.endm + +.macro SAVE_REGS, el + stp x0, x1, [sp] + stp x2, x3, [sp, 2 * 8] + stp x4, x5, [sp, 4 * 8] + stp x6, x7, [sp, 6 * 8] + stp x8, x9, [sp, 8 * 8] + stp x10, x11, [sp, 10 * 8] + stp x12, x13, [sp, 12 * 8] + stp x14, x15, [sp, 14 * 8] + stp x16, x17, [sp, 16 * 8] + stp x18, x19, [sp, 18 * 8] + stp x20, x21, [sp, 20 * 8] + stp x22, x23, [sp, 22 * 8] + stp x24, x25, [sp, 24 * 8] + stp x26, x27, [sp, 26 * 8] + stp x28, x29, [sp, 28 * 8] + str x30, [sp, 30 * 8] + + mrs x10, elr_el1 + mrs x11, spsr_el1 + + stp x10, x11, [sp, 32 * 8] + + .if \el == 0 + clear_gp_regs + mrs x12, tpidr_el0 // save user tls pointer + + ldr x13, [sp, 31 * 8] // restore current ktask ptr + + mrs x9, sp_el0 // save user stack pointer */ + msr sp_el0, x13 // restore kernel task ptr + .else + mov x9, sp + mov x12, xzr + .endif + + str x12, [sp, 34 * 8] // save tpidr_el0 + str x9, [sp, 31 * 8] // save user sp +.endm + +.macro RESTORE_REGS, el + ldp x30, x9, [sp, 30 * 8] // load user sp_el0 + + ldp x10, x11, [sp, 32 * 8] // load ELR, SPSR + msr elr_el1, x10 + msr spsr_el1, x11 + + ldr x12, [sp, 34 * 8] + + .if \el == 0 + msr tpidr_el0, x12 // restore user tls pointer + + mrs x13, sp_el0 // save current ktask ptr + str x13, [sp, 31 * 8] + msr sp_el0, x9 // restore user sp + .endif + + ldp x28, x29, [sp, 28 * 8] + ldp x26, x27, [sp, 26 * 8] + ldp x24, x25, [sp, 24 * 8] + ldp x22, x23, [sp, 22 * 8] + ldp x20, x21, [sp, 20 * 8] + ldp x18, x19, [sp, 18 * 8] + ldp x16, x17, [sp, 16 * 8] + ldp x14, x15, [sp, 14 * 8] + ldp x12, x13, [sp, 12 * 8] + ldp x10, x11, [sp, 10 * 8] + ldp x8, x9, [sp, 8 * 8] + ldp x6, x7, [sp, 6 * 8] + ldp x4, x5, [sp, 4 * 8] + ldp x2, x3, [sp, 2 * 8] + ldp x0, x1, [sp] + add sp, sp, 35 * 8 +.endm + +.macro HANDLE_TRAP, el, ht, regsize, label +.p2align 7 + b handle_el\el\ht\()_\regsize\()_\label +.endm + +.macro HANDLE, el, ht, regsize, label +.section .text +handle_el\el\ht\()_\regsize\()_\label: + sub sp, sp, 35 * 8 + SAVE_REGS \el + + mov x0, sp + bl handle_el\el\ht\()_\regsize\()_\label\()_exception + + .if \el == 1 + b ret_to_kernel + .else + b ret_to_user + .endif +.endm + +.section .text +.p2align 11 +.global exception_vector_base +exception_vector_base: + // current EL, with SP_EL0 + HANDLE_TRAP 1, t, 64, sync + HANDLE_TRAP 1, t, 64, irq + HANDLE_TRAP 1, t, 64, fiq + HANDLE_TRAP 1, t, 64, error + + // current EL, with SP_ELx + HANDLE_TRAP 1, h, 64, sync + HANDLE_TRAP 1, h, 64, irq + HANDLE_TRAP 1, h, 64, fiq + HANDLE_TRAP 1, h, 64, error + + // lower EL, aarch64 with SP_EL0 + HANDLE_TRAP 0, t, 64, sync + HANDLE_TRAP 0, t, 64, irq + HANDLE_TRAP 0, t, 64, fiq + HANDLE_TRAP 0, t, 64, error + + // lower EL, aarch32 + HANDLE_TRAP 0, t, 32, sync + HANDLE_TRAP 0, t, 32, irq + HANDLE_TRAP 0, t, 32, fiq + HANDLE_TRAP 0, t, 32, error + +/* + * used to create handle_el_label_trap +*/ + // current EL, with SP_EL0 + HANDLE 1, t, 64, sync + HANDLE 1, t, 64, irq + HANDLE 1, t, 64, fiq + HANDLE 1, t, 64, error + + // current EL, with SP_ELx + HANDLE 1, h, 64, sync + HANDLE 1, h, 64, irq + HANDLE 1, h, 64, fiq + HANDLE 1, h, 64, error + + // lower EL, aarch64 with SP_EL0 + HANDLE 0, t, 64, sync + HANDLE 0, t, 64, irq + HANDLE 0, t, 64, fiq + HANDLE 0, t, 64, error + + // lower EL, aarch32 + HANDLE 0, t, 32, sync + HANDLE 0, t, 32, irq + HANDLE 0, t, 32, fiq + HANDLE 0, t, 32, error + +.section .text +.global ret_to_kernel +ret_to_kernel: + RESTORE_REGS 1 + eret + +.section .text +.global ret_to_user +ret_to_user: + RESTORE_REGS 0 + eret + +.section .text +.global ret_to_first_user +ret_to_first_user: + mov sp, x0 + b ret_to_user diff --git a/modules/axtrap/src/arch/aarch64/trap.rs b/modules/axtrap/src/arch/aarch64/trap.rs new file mode 100644 index 0000000..0f1bb3a --- /dev/null +++ b/modules/axtrap/src/arch/aarch64/trap.rs @@ -0,0 +1,207 @@ +use core::arch::global_asm; + +use aarch64_cpu::registers::{ESR_EL1, FAR_EL1, SP_EL1, VBAR_EL1}; +use tock_registers::interfaces::{Readable, Writeable}; + +use axhal::arch::TrapFrame; + +global_asm!(include_str!("trap.S")); + +#[cfg(feature = "monolithic")] +use axhal::arch::{disable_irqs, enable_irqs}; + +#[cfg(feature = "monolithic")] +use crate::trap::{handle_signals, handle_syscall}; + +#[repr(u8)] +#[derive(Debug)] +#[allow(dead_code)] +enum TrapKind { + Synchronous = 0, + Irq = 1, + Fiq = 2, + SError = 3, +} + +#[repr(u8)] +#[derive(Debug)] +#[allow(dead_code)] +enum TrapSource { + CurrentSpEl0 = 0, + CurrentSpElx = 1, + LowerAArch64 = 2, + LowerAArch32 = 3, +} + +pub(crate) fn set_exception_vector_base(exception_vector_base: usize) { + VBAR_EL1.set(exception_vector_base as _); +} + +#[no_mangle] +fn invalid_exception(tf: &TrapFrame, kind: TrapKind, source: TrapSource) { + panic!( + "Invalid exception {:?} from {:?}:\n{:#x?}", + kind, source, tf + ); +} + +#[no_mangle] +fn handle_el1t_64_sync_exception(tf: &mut TrapFrame) { + invalid_exception(tf, TrapKind::Synchronous, TrapSource::CurrentSpEl0); +} + +#[no_mangle] +fn handle_el1t_64_irq_exception(tf: &mut TrapFrame) { + invalid_exception(tf, TrapKind::Irq, TrapSource::CurrentSpEl0); +} + +#[no_mangle] +fn handle_el1t_64_fiq_exception(tf: &mut TrapFrame) { + invalid_exception(tf, TrapKind::Fiq, TrapSource::CurrentSpEl0); +} + +#[no_mangle] +fn handle_el1t_64_error_exception(tf: &mut TrapFrame) { + invalid_exception(tf, TrapKind::SError, TrapSource::CurrentSpEl0); +} + +#[no_mangle] +fn handle_el1h_64_sync_exception(tf: &mut TrapFrame) { + let esr = ESR_EL1.extract(); + + match esr.read_as_enum(ESR_EL1::EC) { + Some(ESR_EL1::EC::Value::Brk64) => { + let iss = esr.read(ESR_EL1::ISS); + log::debug!("BRK #{:#x} @ {:#x} ", iss, tf.elr); + tf.elr += 4; + } + Some(ESR_EL1::EC::Value::DataAbortCurrentEL) + | Some(ESR_EL1::EC::Value::InstrAbortCurrentEL) => { + let iss = esr.read(ESR_EL1::ISS); + panic!( + "EL1 Page Fault @ {:#x}, FAR={:#x}, ISS={:#x}:\n{:#x?}", + tf.elr, + FAR_EL1.get(), + iss, + tf, + ); + } + _ => { + panic!( + "Unhandled synchronous exception @ {:#x}: ESR={:#x} (EC {:#08b}, ISS {:#x}) SP{:#x}", + tf.elr, + esr.get(), + esr.read(ESR_EL1::EC), + esr.read(ESR_EL1::ISS), + SP_EL1.get(), + ); + } + } +} + +#[no_mangle] +fn handle_el1h_64_irq_exception(_tf: &TrapFrame) { + crate::trap::handle_irq(0, false); +} + +#[no_mangle] +fn handle_el1h_64_fiq_exception(tf: &mut TrapFrame) { + invalid_exception(tf, TrapKind::Fiq, TrapSource::CurrentSpElx); +} + +#[no_mangle] +fn handle_el1h_64_error_exception(tf: &mut TrapFrame) { + invalid_exception(tf, TrapKind::SError, TrapSource::CurrentSpElx); +} + +#[no_mangle] +#[cfg(feature = "monolithic")] +fn handle_el0t_64_sync_exception(tf: &mut TrapFrame) { + let esr = ESR_EL1.extract(); + + match esr.read_as_enum(ESR_EL1::EC) { + Some(ESR_EL1::EC::Value::SVC64) => { + enable_irqs(); + let result = handle_syscall( + tf.r[8], + [tf.r[0], tf.r[1], tf.r[2], tf.r[3], tf.r[4], tf.r[5]], + ); + if -result == linux_syscall_api::SyscallError::ERESTART as isize { + // Restart the syscall + tf.rewind_pc(); + } else { + tf.r[0] = result as usize; + } + } + Some(ESR_EL1::EC::Value::DataAbortLowerEL) => { + let far = FAR_EL1.get() as usize; + super::mem_fault::el0_da(far, esr.get(), tf); + } + Some(ESR_EL1::EC::Value::InstrAbortLowerEL) => { + let far = FAR_EL1.get() as usize; + super::mem_fault::el0_ia(far, esr.get(), tf); + } + _ => { + panic!( + "Unhandled synchronous exception @ {:#x}: ESR={:#x} (EC {:#08b}, ISS {:#x})", + tf.elr, + esr.get(), + esr.read(ESR_EL1::EC), + esr.read(ESR_EL1::ISS), + ); + } + } + + handle_signals(); + + disable_irqs(); +} + +#[no_mangle] +#[cfg(not(feature = "monolithic"))] +fn handle_el0t_64_sync_exception(tf: &mut TrapFrame) { + invalid_exception(tf, TrapKind::Synchronous, TrapSource::LowerAArch64); +} + +#[no_mangle] +#[cfg(feature = "monolithic")] +fn handle_el0t_64_irq_exception(_tf: &TrapFrame) { + crate::trap::handle_irq(0, true); + handle_signals(); +} + +#[no_mangle] +#[cfg(not(feature = "monolithic"))] +fn handle_el0t_64_irq_exception(tf: &TrapFrame) { + invalid_exception(tf, TrapKind::Irq, TrapSource::LowerAArch64); +} + +#[no_mangle] +fn handle_el0t_64_fiq_exception(tf: &TrapFrame) { + invalid_exception(tf, TrapKind::Fiq, TrapSource::LowerAArch64); +} + +#[no_mangle] +fn handle_el0t_64_error_exception(tf: &TrapFrame) { + invalid_exception(tf, TrapKind::SError, TrapSource::LowerAArch64); +} + +#[no_mangle] +fn handle_el0t_32_sync_exception(tf: &TrapFrame) { + invalid_exception(tf, TrapKind::Synchronous, TrapSource::LowerAArch32); +} + +#[no_mangle] +fn handle_el0t_32_irq_exception(tf: &TrapFrame) { + invalid_exception(tf, TrapKind::Irq, TrapSource::LowerAArch32); +} + +#[no_mangle] +fn handle_el0t_32_fiq_exception(tf: &TrapFrame) { + invalid_exception(tf, TrapKind::Fiq, TrapSource::LowerAArch32); +} + +#[no_mangle] +fn handle_el0t_32_error_exception(tf: &TrapFrame) { + invalid_exception(tf, TrapKind::SError, TrapSource::LowerAArch32); +} diff --git a/modules/axtrap/src/arch/mod.rs b/modules/axtrap/src/arch/mod.rs new file mode 100644 index 0000000..b8bc0af --- /dev/null +++ b/modules/axtrap/src/arch/mod.rs @@ -0,0 +1,14 @@ +//! Architecture-specific types and operations. + +cfg_if::cfg_if! { + if #[cfg(target_arch = "x86_64")] { + mod x86_64; + pub use self::x86_64::*; + } else if #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] { + mod riscv; + pub use self::riscv::*; + } else if #[cfg(target_arch = "aarch64")]{ + mod aarch64; + pub use self::aarch64::*; + } +} diff --git a/modules/axtrap/src/arch/riscv/macros.rs b/modules/axtrap/src/arch/riscv/macros.rs new file mode 100644 index 0000000..a1542c8 --- /dev/null +++ b/modules/axtrap/src/arch/riscv/macros.rs @@ -0,0 +1,81 @@ +macro_rules! include_trap_asm_marcos { + () => { + #[cfg(target_arch = "riscv32")] + core::arch::global_asm!( + r" + .ifndef XLENB + .equ XLENB, 4 + + .macro LDR rd, rs, off + lw \rd, \off*XLENB(\rs) + .endm + .macro STR rs2, rs1, off + sw \rs2, \off*XLENB(\rs1) + .endm + + .endif" + ); + + #[cfg(target_arch = "riscv64")] + core::arch::global_asm!( + r" + .ifndef XLENB + .equ XLENB, 8 + + .macro LDR rd, rs, off + ld \rd, \off*XLENB(\rs) + .endm + .macro STR rs2, rs1, off + sd \rs2, \off*XLENB(\rs1) + .endm + + .endif", + ); + + core::arch::global_asm!( + r" + .ifndef .LPUSH_POP_GENERAL_REGS + .equ .LPUSH_POP_GENERAL_REGS, 0 + + .macro PUSH_POP_GENERAL_REGS, op + \op ra, sp, 0 + \op t0, sp, 4 + \op t1, sp, 5 + \op t2, sp, 6 + \op s0, sp, 7 + \op s1, sp, 8 + \op a0, sp, 9 + \op a1, sp, 10 + \op a2, sp, 11 + \op a3, sp, 12 + \op a4, sp, 13 + \op a5, sp, 14 + \op a6, sp, 15 + \op a7, sp, 16 + \op s2, sp, 17 + \op s3, sp, 18 + \op s4, sp, 19 + \op s5, sp, 20 + \op s6, sp, 21 + \op s7, sp, 22 + \op s8, sp, 23 + \op s9, sp, 24 + \op s10, sp, 25 + \op s11, sp, 26 + \op t3, sp, 27 + \op t4, sp, 28 + \op t5, sp, 29 + \op t6, sp, 30 + .endm + + .macro PUSH_GENERAL_REGS + PUSH_POP_GENERAL_REGS STR + .endm + .macro POP_GENERAL_REGS + PUSH_POP_GENERAL_REGS LDR + .endm + + .endif" + ); + }; +} diff --git a/modules/axtrap/src/arch/riscv/mod.rs b/modules/axtrap/src/arch/riscv/mod.rs new file mode 100644 index 0000000..469737d --- /dev/null +++ b/modules/axtrap/src/arch/riscv/mod.rs @@ -0,0 +1,125 @@ +#[macro_use] +mod macros; + +use axhal::arch::TrapFrame; + +use riscv::register::scause::{self, Exception as E, Trap}; +use riscv::register::stvec; + +use crate::trap::*; + +#[cfg(feature = "monolithic")] +use linux_syscall_api::trap::MappingFlags; + +include_trap_asm_marcos!(); +core::arch::global_asm!( + include_str!("trap.S"), + trapframe_size = const core::mem::size_of::(), +); + +/// Writes Supervisor Trap Vector Base Address Register (`stvec`). +#[inline] +pub fn set_trap_vector_base(stvec: usize) { + unsafe { stvec::write(stvec, stvec::TrapMode::Direct) } +} + +extern "C" { + fn trap_vector_base(); +} + +/// To initialize the trap vector base address. +pub fn init_interrupt() { + set_trap_vector_base(trap_vector_base as usize); +} + +fn handle_breakpoint(sepc: &mut usize) { + axlog::debug!("Exception(Breakpoint) @ {:#x} ", sepc); + *sepc += 2 +} + +#[no_mangle] +pub fn riscv_trap_handler(tf: &mut TrapFrame, from_user: bool) { + let scause = scause::read(); + // Read the stval before enable_irqs + // Otherwise, the stval may be changed after the time interrupt is handled + let stval = riscv::register::stval::read(); + + #[cfg(feature = "monolithic")] + linux_syscall_api::trap::record_trap(scause.code()); + + match scause.cause() { + Trap::Exception(E::Breakpoint) => handle_breakpoint(&mut tf.sepc), + Trap::Interrupt(_) => handle_irq(scause.bits(), from_user), + + #[cfg(feature = "monolithic")] + Trap::Exception(E::UserEnvCall) => { + axhal::arch::enable_irqs(); + tf.sepc += 4; + let result = handle_syscall( + tf.regs.a7, + [ + tf.regs.a0, tf.regs.a1, tf.regs.a2, tf.regs.a3, tf.regs.a4, tf.regs.a5, + ], + ); + if -result == linux_syscall_api::SyscallError::ERESTART as isize { + // Restart the syscall + tf.rewind_pc(); + } else { + tf.regs.a0 = result as usize; + } + axhal::arch::disable_irqs(); + } + + #[cfg(feature = "monolithic")] + Trap::Exception(E::InstructionPageFault) => { + if !from_user { + unimplemented!( + "I page fault from kernel, addr: {:X}, sepc: {:X}", + stval, + tf.sepc + ); + } + handle_page_fault(stval.into(), MappingFlags::USER | MappingFlags::EXECUTE); + } + + #[cfg(feature = "monolithic")] + Trap::Exception(E::LoadPageFault) => { + if !from_user { + unimplemented!( + "L page fault from kernel, addr: {:X}, sepc: {:X}", + stval, + tf.sepc + ); + } + handle_page_fault(stval.into(), MappingFlags::USER | MappingFlags::READ); + } + + #[cfg(feature = "monolithic")] + Trap::Exception(E::StorePageFault) => { + if !from_user { + unimplemented!( + "S page fault from kernel, addr: {:X}, sepc: {:X}", + stval, + tf.sepc + ); + } + handle_page_fault(stval.into(), MappingFlags::USER | MappingFlags::WRITE); + } + + _ => { + panic!( + "Unhandled trap {:?} @ {:#x}:\n{:#x?}", + scause.cause(), + tf.sepc, + tf + ); + } + } + + #[cfg(feature = "monolithic")] + { + if from_user { + handle_signals(); + } + } +} diff --git a/modules/axtrap/src/arch/riscv/trap.S b/modules/axtrap/src/arch/riscv/trap.S new file mode 100644 index 0000000..9cff418 --- /dev/null +++ b/modules/axtrap/src/arch/riscv/trap.S @@ -0,0 +1,71 @@ +.macro SAVE_REGS, from_user + addi sp, sp, -{trapframe_size} + PUSH_GENERAL_REGS + + csrr t0, sepc + csrr t1, sstatus + csrrw t2, sscratch, zero // save sscratch (sp) and zero it + STR t0, sp, 31 // tf.sepc + STR t1, sp, 32 // tf.sstatus + STR t2, sp, 1 // tf.regs.sp + .short 0xa622 // fsd fs0,264(sp) + .short 0xaa26 // fsd fs1,272(sp) +.if \from_user == 1 + LDR t1, sp, 2 // load user gp with CPU ID + LDR t0, sp, 3 // load supervisor tp + STR gp, sp, 2 // save user gp and tp + STR tp, sp, 3 + mv gp, t1 + mv tp, t0 +.endif +.endm + +.macro RESTORE_REGS, from_user +.if \from_user == 1 + LDR t1, sp, 2 + LDR t0, sp, 3 + STR gp, sp, 2 // load user gp and tp + STR tp, sp, 3 // save supervisor tp + mv gp, t1 + mv tp, t0 + addi t0, sp, {trapframe_size} // put supervisor sp to scratch + csrw sscratch, t0 +.endif + + LDR t0, sp, 31 + LDR t1, sp, 32 + csrw sepc, t0 + csrw sstatus, t1 + .short 0x2432 // fld fs0,264(sp) + .short 0x24d2 // fld fs1,272(sp) + POP_GENERAL_REGS + LDR sp, sp, 1 // load sp from tf.regs.sp +.endm + +.section .text +.balign 4 +.global trap_vector_base +trap_vector_base: + // sscratch == 0: trap from S mode + // sscratch != 0: trap from U mode + csrrw sp, sscratch, sp // switch sscratch and sp + bnez sp, .Ltrap_entry_u + + csrr sp, sscratch // put supervisor sp back + j .Ltrap_entry_s + +.Ltrap_entry_s: + SAVE_REGS 0 + mv a0, sp + li a1, 0 + call riscv_trap_handler + RESTORE_REGS 0 + sret + +.Ltrap_entry_u: + SAVE_REGS 1 + mv a0, sp + li a1, 1 + call riscv_trap_handler + RESTORE_REGS 1 + sret diff --git a/modules/axtrap/src/arch/x86_64/idt.rs b/modules/axtrap/src/arch/x86_64/idt.rs new file mode 100644 index 0000000..c6124b2 --- /dev/null +++ b/modules/axtrap/src/arch/x86_64/idt.rs @@ -0,0 +1,83 @@ +use core::fmt; + +use lazy_init::LazyInit; +use x86_64::{ + structures::{ + idt::{Entry, HandlerFunc, InterruptDescriptorTable}, + DescriptorTablePointer, + }, + VirtAddr, +}; +const NUM_INT: usize = 256; + +/// A wrapper of the Interrupt Descriptor Table (IDT). +#[repr(transparent)] +pub struct IdtStruct { + table: InterruptDescriptorTable, +} + +impl IdtStruct { + /// Constructs a new IDT struct that filled with entries from + /// `trap_handler_table`. + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + extern "C" { + #[link_name = "trap_handler_table"] + static ENTRIES: [extern "C" fn(); NUM_INT]; + } + let mut idt = Self { + table: InterruptDescriptorTable::new(), + }; + + let entries = unsafe { + core::slice::from_raw_parts_mut( + &mut idt.table as *mut _ as *mut Entry, + NUM_INT, + ) + }; + for i in 0..NUM_INT { + entries[i].set_handler_fn(unsafe { core::mem::transmute(ENTRIES[i]) }); + } + idt + } + + /// Returns the IDT pointer (base and limit) that can be used in the `lidt` + /// instruction. + pub fn pointer(&self) -> DescriptorTablePointer { + DescriptorTablePointer { + base: VirtAddr::new(&self.table as *const _ as u64), + limit: (core::mem::size_of::() - 1) as u16, + } + } + + /// Loads the IDT into the CPU (executes the `lidt` instruction). + /// + /// # Safety + /// + /// This function is unsafe because it manipulates the CPU's privileged + /// states. + pub unsafe fn load(&'static self) { + self.table.load(); + } +} + +impl fmt::Debug for IdtStruct { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("IdtStruct") + .field("pointer", &self.pointer()) + .field("table", &self.table) + .finish() + } +} + +static IDT: LazyInit = LazyInit::new(); + +pub(super) fn init_idt() { + if !IDT.is_init() { + // It will be finished by the primary core + IDT.init_by(IdtStruct::new()); + } + unsafe { + IDT.load(); + } +} diff --git a/modules/axtrap/src/arch/x86_64/mod.rs b/modules/axtrap/src/arch/x86_64/mod.rs new file mode 100644 index 0000000..5d830b9 --- /dev/null +++ b/modules/axtrap/src/arch/x86_64/mod.rs @@ -0,0 +1,86 @@ +use axhal::arch::TrapFrame; +use x86::{controlregs::cr2, irq::*}; + +mod idt; + +#[cfg(feature = "monolithic")] +mod syscall; + +core::arch::global_asm!(include_str!("trap.S")); + +const IRQ_VECTOR_START: u8 = 0x20; +const IRQ_VECTOR_END: u8 = 0xff; + +/// To init the interrupt entry +pub fn init_interrupt() { + // To init the IDT + idt::init_idt(); + #[cfg(feature = "monolithic")] + syscall::init_syscall(); +} + +#[no_mangle] +fn x86_trap_handler(tf: &mut TrapFrame) { + // Read cr2 before enable_irqs + // Otherwise, the cr2 may be changed after the time interrupt is handled + let cr2 = unsafe { cr2() }; + + match tf.vector as u8 { + PAGE_FAULT_VECTOR => { + if tf.is_user() { + axlog::debug!( + "User #PF @ {:#x}, fault_vaddr={:#x}, error_code={:#x}", + tf.rip, + cr2, + tf.error_code, + ); + #[cfg(feature = "monolithic")] + { + use axhal::paging::MappingFlags; + // 31 15 4 0 + // +---+-- --+---+-----+---+-- --+---+----+----+---+---+---+---+---+ + // | Reserved | SGX | Reserved | SS | PK | I | R | U | W | P | + // +---+-- --+---+-----+---+-- --+---+----+----+---+---+---+---+---+ + let mut map_flags = MappingFlags::USER; // TODO: add this flags through user tf. + if tf.error_code & (1 << 1) != 0 { + map_flags |= MappingFlags::WRITE; + } + if tf.error_code & (1 << 2) != 0 { + map_flags |= MappingFlags::USER; + } + if tf.error_code & (1 << 3) != 0 { + map_flags |= MappingFlags::READ; + } + if tf.error_code & (1 << 4) != 0 { + map_flags |= MappingFlags::EXECUTE; + } + axlog::debug!("error_code: {:?}", tf.error_code); + crate::trap::handle_page_fault(cr2.into(), map_flags); + } + } else { + panic!( + "Kernel #PF @ {:#x}, fault_vaddr={:#x}, error_code={:#x}:\n{:#x?}", + tf.rip, cr2, tf.error_code, tf, + ); + } + } + BREAKPOINT_VECTOR => axlog::debug!("#BP @ {:#x} ", tf.rip), + GENERAL_PROTECTION_FAULT_VECTOR => { + panic!( + "#GP @ {:#x}, error_code={:#x}:\n{:#x?}", + tf.rip, tf.error_code, tf + ); + } + IRQ_VECTOR_START..=IRQ_VECTOR_END => crate::trap::handle_irq(tf.vector as _, false), + _ => { + panic!( + "Unhandled exception {} (error_code = {:#x}) @ {:#x}:\n{:#x?}", + tf.vector, tf.error_code, tf.rip, tf + ); + } + } + #[cfg(feature = "monolithic")] + if tf.is_user() { + crate::trap::handle_signals(); + } +} diff --git a/modules/axtrap/src/arch/x86_64/syscall.S b/modules/axtrap/src/arch/x86_64/syscall.S new file mode 100644 index 0000000..69d4d37 --- /dev/null +++ b/modules/axtrap/src/arch/x86_64/syscall.S @@ -0,0 +1,55 @@ +.section .text +syscall_entry: + swapgs + # The user rsp and kernel rsp are defined in axhal + mov gs:[offset __PERCPU_USER_RSP_OFFSET], rsp + mov rsp, gs:[offset __PERCPU_KERNEL_RSP_OFFSET] + + sub rsp, 8 // skip user_ss + push gs:[offset __PERCPU_USER_RSP_OFFSET] // user_rsp + push r11 // rflags + mov [rsp - 2 * 8], rcx // rip + sub rsp, 4 * 8 // skip until general registers + + push r15 + push r14 + push r13 + push r12 + push r11 + push r10 + push r9 + push r8 + push rdi + push rsi + push rbp + push rbx + push rdx + push rcx + push rax + + mov rdi, rsp + call x86_syscall_handler + + pop rax + pop rcx + pop rdx + pop rbx + pop rbp + pop rsi + pop rdi + pop r8 + pop r9 + pop r10 + pop r11 + pop r12 + pop r13 + pop r14 + pop r15 + + add rsp, 7 * 8 + mov rcx, [rsp - 5 * 8] // rip + mov r11, [rsp - 3 * 8] // rflags + mov rsp, [rsp - 2 * 8] // user_rsp + + swapgs + sysretq \ No newline at end of file diff --git a/modules/axtrap/src/arch/x86_64/syscall.rs b/modules/axtrap/src/arch/x86_64/syscall.rs new file mode 100644 index 0000000..4ba1260 --- /dev/null +++ b/modules/axtrap/src/arch/x86_64/syscall.rs @@ -0,0 +1,65 @@ +use core::arch::global_asm; + +use x86_64::{ + registers::{ + model_specific::{Efer, EferFlags, LStar, SFMask, Star}, + rflags::RFlags, + }, + VirtAddr, +}; + +use axhal::arch::{GdtStruct, TrapFrame}; + +global_asm!(include_str!("syscall.S")); + +#[no_mangle] +#[percpu::def_percpu] +static USER_RSP_OFFSET: usize = 0; + +#[no_mangle] +#[percpu::def_percpu] +static KERNEL_RSP_OFFSET: usize = 0; + +pub(super) fn init_syscall() { + extern "C" { + fn syscall_entry(); + } + LStar::write(VirtAddr::new(syscall_entry as usize as _)); + Star::write( + GdtStruct::UCODE64_SELECTOR, + GdtStruct::UDATA_SELECTOR, + GdtStruct::KCODE64_SELECTOR, + GdtStruct::KDATA_SELECTOR, + ) + .unwrap(); + SFMask::write( + RFlags::TRAP_FLAG + | RFlags::INTERRUPT_FLAG + | RFlags::DIRECTION_FLAG + | RFlags::IOPL_LOW + | RFlags::IOPL_HIGH + | RFlags::NESTED_TASK + | RFlags::ALIGNMENT_CHECK, + ); // TF | IF | DF | IOPL | AC | NT (0x47700) + unsafe { + Efer::update(|efer| *efer |= EferFlags::SYSTEM_CALL_EXTENSIONS); + } +} + +#[no_mangle] +fn x86_syscall_handler(tf: &mut TrapFrame) { + axhal::arch::enable_irqs(); + let result = crate::trap::handle_syscall(tf.get_syscall_num(), tf.get_syscall_args()); + if -result == linux_syscall_api::SyscallError::ERESTART as isize { + axlog::error!("rewind pc"); + // Restart the syscall + tf.rewind_pc(); + } else { + tf.rax = result as u64; + } + + #[cfg(feature = "monolithic")] + if tf.is_user() { + crate::trap::handle_signals(); + } +} diff --git a/modules/axtrap/src/arch/x86_64/trap.S b/modules/axtrap/src/arch/x86_64/trap.S new file mode 100644 index 0000000..4bd0d94 --- /dev/null +++ b/modules/axtrap/src/arch/x86_64/trap.S @@ -0,0 +1,84 @@ +.equ NUM_INT, 256 + +.altmacro +.macro DEF_HANDLER, i +.Ltrap_handler_\i: +.if \i == 8 || (\i >= 10 && \i <= 14) || \i == 17 + # error code pushed by CPU + push \i # interrupt vector + jmp .Ltrap_common +.else + push 0 # fill in error code in TrapFrame + push \i # interrupt vector + jmp .Ltrap_common +.endif +.endm + +.macro DEF_TABLE_ENTRY, i + .quad .Ltrap_handler_\i +.endm + +.section .text +.code64 +_trap_handlers: +.set i, 0 +.rept NUM_INT + DEF_HANDLER %i + .set i, i + 1 +.endr + +.Ltrap_common: + test byte ptr [rsp + 3 * 8], 3 # swap GS if it comes from user space + jz 1f + swapgs +1: + push r15 + push r14 + push r13 + push r12 + push r11 + push r10 + push r9 + push r8 + push rdi + push rsi + push rbp + push rbx + push rdx + push rcx + push rax + + mov rdi, rsp + call x86_trap_handler + + pop rax + pop rcx + pop rdx + pop rbx + pop rbp + pop rsi + pop rdi + pop r8 + pop r9 + pop r10 + pop r11 + pop r12 + pop r13 + pop r14 + pop r15 + + test byte ptr [rsp + 3 * 8], 3 # swap GS back if return to user space + jz 2f + swapgs +2: + add rsp, 16 # pop vector, error_code + iretq + +.section .rodata +.global trap_handler_table +trap_handler_table: +.set i, 0 +.rept NUM_INT + DEF_TABLE_ENTRY %i + .set i, i + 1 +.endr diff --git a/modules/axtrap/src/lib.rs b/modules/axtrap/src/lib.rs new file mode 100644 index 0000000..455954f --- /dev/null +++ b/modules/axtrap/src/lib.rs @@ -0,0 +1,23 @@ +//! The entry of trap handler, which will handle the context switch and distribute the trap to the corresponding function. +//! +//! The trapframe is defined in `axhal` module, and the trap handler is defined in `axtrap` module. +//! +//! The reason for the separation of definition and implementation is to ensure a one-way dependence on the calling relationship. +//! +//! The trap handler need to dispatch the trap to the corresponding functions, which use `trapframe` struct to finish their functions. +//! +//! If you want to use this module, please ensure that the trap context layout you pass to the handler is +//! in consistent or compatible with the trap frame layout defined in [axhal](https://github.com/Azure-stars/Starry/tree/main/modules/axhal). +//! +//! Otherwise, you may need to modify the unsafe assembly code of this module to complete the privilege switching function +#![cfg_attr(not(test), no_std)] +#![feature(asm_const)] +#![feature(naked_functions)] +#![feature(const_option)] +#![feature(doc_auto_cfg)] + +mod arch; + +mod trap; + +pub use arch::init_interrupt; diff --git a/modules/axtrap/src/trap.rs b/modules/axtrap/src/trap.rs new file mode 100644 index 0000000..22b91f4 --- /dev/null +++ b/modules/axtrap/src/trap.rs @@ -0,0 +1,20 @@ +pub fn handle_irq(_irq_num: usize, _from_user: bool) { + #[cfg(feature = "irq")] + { + let guard = kernel_guard::NoPreempt::new(); + // trap进来,统计时间信息 + // 只有当trap是来自用户态才进行统计 + #[cfg(feature = "monolithic")] + linux_syscall_api::trap::handle_irq(_irq_num, _from_user); + + #[cfg(not(feature = "monolithic"))] + axhal::irq::dispatch_irq(_irq_num); + drop(guard); // rescheduling may occur when preemption is re-enabled. + + #[cfg(feature = "preempt")] + axtask::current_check_preempt_pending(); + } +} + +#[cfg(feature = "monolithic")] +pub use linux_syscall_api::trap::{handle_page_fault, handle_signals, handle_syscall}; diff --git a/scripts/make/build_c.mk b/scripts/make/build_c.mk index 0c57083..4f805b6 100644 --- a/scripts/make/build_c.mk +++ b/scripts/make/build_c.mk @@ -1,7 +1,7 @@ rust_lib_name := axlibc rust_lib := target/$(TARGET)/$(MODE)/lib$(rust_lib_name).a -ulib_dir := tools/axlibc +ulib_dir := ulib/axlibc src_dir := $(ulib_dir)/c obj_dir := $(ulib_dir)/build_$(ARCH) inc_dir := $(ulib_dir)/include diff --git a/scripts/make/cargo.mk b/scripts/make/cargo.mk index 2509161..868da7c 100644 --- a/scripts/make/cargo.mk +++ b/scripts/make/cargo.mk @@ -30,15 +30,18 @@ endef clippy_args := -A clippy::new_without_default define cargo_clippy - $(call run_cmd,cargo clippy,--all-features --workspace $(1) $(verbose) -- $(clippy_args)) - $(call run_cmd,cargo clippy,-p axlog -p percpu -p percpu_macros $(1) $(verbose) -- $(clippy_args)) + $(call run_cmd,cargo clippy,--all-features --workspace --exclude axlog $(1) $(verbose) -- $(clippy_args)) + $(call run_cmd,cargo clippy,-p axlog $(1) $(verbose) -- $(clippy_args)) endef -all_packages := $(shell ls $(CURDIR)/crates) +all_packages := \ + $(shell ls $(CURDIR)/modules) \ + axfeat arceos_api axstd axlibc define cargo_doc $(call run_cmd,cargo doc,--no-deps --all-features --workspace --exclude "arceos-*" $(verbose)) @# run twice to fix broken hyperlinks - @# for some crates, re-generate without `--all-features` - $(call run_cmd,cargo doc,--no-deps -p percpu $(verbose)) -endef + $(foreach p,$(all_packages), \ + $(call run_cmd,cargo rustdoc,--all-features -p $(p) $(verbose)) + ) +endef \ No newline at end of file diff --git a/tools/axlibc/build_x86_64/.cflags b/tools/axlibc/build_x86_64/.cflags new file mode 100644 index 0000000..9956c05 --- /dev/null +++ b/tools/axlibc/build_x86_64/.cflags @@ -0,0 +1 @@ +-DAX_CONFIG_FP_SIMD -DAX_LOG_OFF -nostdinc -fno-builtin -ffreestanding -Wall -I/home/zyj/OS/StarryOrg/Starry-New/tools/axlibc/include -O3 diff --git a/tools/axlibc/build_x86_64/libc.a b/tools/axlibc/build_x86_64/libc.a new file mode 100644 index 0000000..8b277f0 --- /dev/null +++ b/tools/axlibc/build_x86_64/libc.a @@ -0,0 +1 @@ +! diff --git a/tools/axlibc/.gitignore b/ulib/axlibc/.gitignore similarity index 100% rename from tools/axlibc/.gitignore rename to ulib/axlibc/.gitignore diff --git a/tools/axlibc/Cargo.toml b/ulib/axlibc/Cargo.toml similarity index 88% rename from tools/axlibc/Cargo.toml rename to ulib/axlibc/Cargo.toml index 7f68981..d375de8 100644 --- a/tools/axlibc/Cargo.toml +++ b/ulib/axlibc/Cargo.toml @@ -54,11 +54,11 @@ select = ["arceos_posix_api/select"] epoll = ["arceos_posix_api/epoll"] [dependencies] -axfeat = { git = "https://github.com/Starry-OS/axfeat.git" } -arceos_posix_api = { git = "https://github.com/Starry-OS/arceos_posix_api.git" } +axfeat = { workspace = true } +arceos_posix_api = { workspace = true } axio = { git = "https://github.com/Starry-OS/axio.git" } axerrno = { git = "https://github.com/Starry-OS/axerrno.git" } -arch_boot = { git = "https://github.com/Starry-OS/arch_boot.git" } +arch_boot = { workspace = true } [build-dependencies] bindgen ={ version = "0.69" } \ No newline at end of file diff --git a/tools/axlibc/build.rs b/ulib/axlibc/build.rs similarity index 100% rename from tools/axlibc/build.rs rename to ulib/axlibc/build.rs diff --git a/tools/axlibc/c/assert.c b/ulib/axlibc/c/assert.c similarity index 100% rename from tools/axlibc/c/assert.c rename to ulib/axlibc/c/assert.c diff --git a/tools/axlibc/c/ctype.c b/ulib/axlibc/c/ctype.c similarity index 100% rename from tools/axlibc/c/ctype.c rename to ulib/axlibc/c/ctype.c diff --git a/tools/axlibc/c/dirent.c b/ulib/axlibc/c/dirent.c similarity index 100% rename from tools/axlibc/c/dirent.c rename to ulib/axlibc/c/dirent.c diff --git a/tools/axlibc/c/dlfcn.c b/ulib/axlibc/c/dlfcn.c similarity index 100% rename from tools/axlibc/c/dlfcn.c rename to ulib/axlibc/c/dlfcn.c diff --git a/tools/axlibc/c/env.c b/ulib/axlibc/c/env.c similarity index 100% rename from tools/axlibc/c/env.c rename to ulib/axlibc/c/env.c diff --git a/tools/axlibc/c/fcntl.c b/ulib/axlibc/c/fcntl.c similarity index 100% rename from tools/axlibc/c/fcntl.c rename to ulib/axlibc/c/fcntl.c diff --git a/tools/axlibc/c/flock.c b/ulib/axlibc/c/flock.c similarity index 100% rename from tools/axlibc/c/flock.c rename to ulib/axlibc/c/flock.c diff --git a/tools/axlibc/c/fnmatch.c b/ulib/axlibc/c/fnmatch.c similarity index 100% rename from tools/axlibc/c/fnmatch.c rename to ulib/axlibc/c/fnmatch.c diff --git a/tools/axlibc/c/glob.c b/ulib/axlibc/c/glob.c similarity index 100% rename from tools/axlibc/c/glob.c rename to ulib/axlibc/c/glob.c diff --git a/tools/axlibc/c/ioctl.c b/ulib/axlibc/c/ioctl.c similarity index 100% rename from tools/axlibc/c/ioctl.c rename to ulib/axlibc/c/ioctl.c diff --git a/tools/axlibc/c/libgen.c b/ulib/axlibc/c/libgen.c similarity index 100% rename from tools/axlibc/c/libgen.c rename to ulib/axlibc/c/libgen.c diff --git a/tools/axlibc/c/libm.c b/ulib/axlibc/c/libm.c similarity index 100% rename from tools/axlibc/c/libm.c rename to ulib/axlibc/c/libm.c diff --git a/tools/axlibc/c/libm.h b/ulib/axlibc/c/libm.h similarity index 100% rename from tools/axlibc/c/libm.h rename to ulib/axlibc/c/libm.h diff --git a/tools/axlibc/c/locale.c b/ulib/axlibc/c/locale.c similarity index 100% rename from tools/axlibc/c/locale.c rename to ulib/axlibc/c/locale.c diff --git a/tools/axlibc/c/log.c b/ulib/axlibc/c/log.c similarity index 100% rename from tools/axlibc/c/log.c rename to ulib/axlibc/c/log.c diff --git a/tools/axlibc/c/math.c b/ulib/axlibc/c/math.c similarity index 100% rename from tools/axlibc/c/math.c rename to ulib/axlibc/c/math.c diff --git a/tools/axlibc/c/mmap.c b/ulib/axlibc/c/mmap.c similarity index 100% rename from tools/axlibc/c/mmap.c rename to ulib/axlibc/c/mmap.c diff --git a/tools/axlibc/c/network.c b/ulib/axlibc/c/network.c similarity index 100% rename from tools/axlibc/c/network.c rename to ulib/axlibc/c/network.c diff --git a/tools/axlibc/c/poll.c b/ulib/axlibc/c/poll.c similarity index 100% rename from tools/axlibc/c/poll.c rename to ulib/axlibc/c/poll.c diff --git a/tools/axlibc/c/pow.c b/ulib/axlibc/c/pow.c similarity index 100% rename from tools/axlibc/c/pow.c rename to ulib/axlibc/c/pow.c diff --git a/tools/axlibc/c/printf.c b/ulib/axlibc/c/printf.c similarity index 100% rename from tools/axlibc/c/printf.c rename to ulib/axlibc/c/printf.c diff --git a/tools/axlibc/c/printf.h b/ulib/axlibc/c/printf.h similarity index 100% rename from tools/axlibc/c/printf.h rename to ulib/axlibc/c/printf.h diff --git a/tools/axlibc/c/printf_config.h b/ulib/axlibc/c/printf_config.h similarity index 100% rename from tools/axlibc/c/printf_config.h rename to ulib/axlibc/c/printf_config.h diff --git a/tools/axlibc/c/pthread.c b/ulib/axlibc/c/pthread.c similarity index 100% rename from tools/axlibc/c/pthread.c rename to ulib/axlibc/c/pthread.c diff --git a/tools/axlibc/c/pwd.c b/ulib/axlibc/c/pwd.c similarity index 100% rename from tools/axlibc/c/pwd.c rename to ulib/axlibc/c/pwd.c diff --git a/tools/axlibc/c/resource.c b/ulib/axlibc/c/resource.c similarity index 100% rename from tools/axlibc/c/resource.c rename to ulib/axlibc/c/resource.c diff --git a/tools/axlibc/c/sched.c b/ulib/axlibc/c/sched.c similarity index 100% rename from tools/axlibc/c/sched.c rename to ulib/axlibc/c/sched.c diff --git a/tools/axlibc/c/select.c b/ulib/axlibc/c/select.c similarity index 100% rename from tools/axlibc/c/select.c rename to ulib/axlibc/c/select.c diff --git a/tools/axlibc/c/signal.c b/ulib/axlibc/c/signal.c similarity index 100% rename from tools/axlibc/c/signal.c rename to ulib/axlibc/c/signal.c diff --git a/tools/axlibc/c/socket.c b/ulib/axlibc/c/socket.c similarity index 100% rename from tools/axlibc/c/socket.c rename to ulib/axlibc/c/socket.c diff --git a/tools/axlibc/c/stat.c b/ulib/axlibc/c/stat.c similarity index 100% rename from tools/axlibc/c/stat.c rename to ulib/axlibc/c/stat.c diff --git a/tools/axlibc/c/stdio.c b/ulib/axlibc/c/stdio.c similarity index 100% rename from tools/axlibc/c/stdio.c rename to ulib/axlibc/c/stdio.c diff --git a/tools/axlibc/c/stdlib.c b/ulib/axlibc/c/stdlib.c similarity index 100% rename from tools/axlibc/c/stdlib.c rename to ulib/axlibc/c/stdlib.c diff --git a/tools/axlibc/c/string.c b/ulib/axlibc/c/string.c similarity index 100% rename from tools/axlibc/c/string.c rename to ulib/axlibc/c/string.c diff --git a/tools/axlibc/c/syslog.c b/ulib/axlibc/c/syslog.c similarity index 100% rename from tools/axlibc/c/syslog.c rename to ulib/axlibc/c/syslog.c diff --git a/tools/axlibc/c/time.c b/ulib/axlibc/c/time.c similarity index 100% rename from tools/axlibc/c/time.c rename to ulib/axlibc/c/time.c diff --git a/tools/axlibc/c/unistd.c b/ulib/axlibc/c/unistd.c similarity index 100% rename from tools/axlibc/c/unistd.c rename to ulib/axlibc/c/unistd.c diff --git a/tools/axlibc/c/utsname.c b/ulib/axlibc/c/utsname.c similarity index 100% rename from tools/axlibc/c/utsname.c rename to ulib/axlibc/c/utsname.c diff --git a/tools/axlibc/c/wait.c b/ulib/axlibc/c/wait.c similarity index 100% rename from tools/axlibc/c/wait.c rename to ulib/axlibc/c/wait.c diff --git a/tools/axlibc/ctypes.h b/ulib/axlibc/ctypes.h similarity index 100% rename from tools/axlibc/ctypes.h rename to ulib/axlibc/ctypes.h diff --git a/tools/axlibc/include/arpa/inet.h b/ulib/axlibc/include/arpa/inet.h similarity index 100% rename from tools/axlibc/include/arpa/inet.h rename to ulib/axlibc/include/arpa/inet.h diff --git a/tools/axlibc/include/assert.h b/ulib/axlibc/include/assert.h similarity index 100% rename from tools/axlibc/include/assert.h rename to ulib/axlibc/include/assert.h diff --git a/tools/axlibc/include/ctype.h b/ulib/axlibc/include/ctype.h similarity index 100% rename from tools/axlibc/include/ctype.h rename to ulib/axlibc/include/ctype.h diff --git a/tools/axlibc/include/dirent.h b/ulib/axlibc/include/dirent.h similarity index 100% rename from tools/axlibc/include/dirent.h rename to ulib/axlibc/include/dirent.h diff --git a/tools/axlibc/include/dlfcn.h b/ulib/axlibc/include/dlfcn.h similarity index 100% rename from tools/axlibc/include/dlfcn.h rename to ulib/axlibc/include/dlfcn.h diff --git a/tools/axlibc/include/endian.h b/ulib/axlibc/include/endian.h similarity index 100% rename from tools/axlibc/include/endian.h rename to ulib/axlibc/include/endian.h diff --git a/tools/axlibc/include/errno.h b/ulib/axlibc/include/errno.h similarity index 100% rename from tools/axlibc/include/errno.h rename to ulib/axlibc/include/errno.h diff --git a/tools/axlibc/include/fcntl.h b/ulib/axlibc/include/fcntl.h similarity index 100% rename from tools/axlibc/include/fcntl.h rename to ulib/axlibc/include/fcntl.h diff --git a/tools/axlibc/include/features.h b/ulib/axlibc/include/features.h similarity index 100% rename from tools/axlibc/include/features.h rename to ulib/axlibc/include/features.h diff --git a/tools/axlibc/include/float.h b/ulib/axlibc/include/float.h similarity index 100% rename from tools/axlibc/include/float.h rename to ulib/axlibc/include/float.h diff --git a/tools/axlibc/include/fnmatch.h b/ulib/axlibc/include/fnmatch.h similarity index 100% rename from tools/axlibc/include/fnmatch.h rename to ulib/axlibc/include/fnmatch.h diff --git a/tools/axlibc/include/glob.h b/ulib/axlibc/include/glob.h similarity index 100% rename from tools/axlibc/include/glob.h rename to ulib/axlibc/include/glob.h diff --git a/tools/axlibc/include/inttypes.h b/ulib/axlibc/include/inttypes.h similarity index 100% rename from tools/axlibc/include/inttypes.h rename to ulib/axlibc/include/inttypes.h diff --git a/tools/axlibc/include/langinfo.h b/ulib/axlibc/include/langinfo.h similarity index 100% rename from tools/axlibc/include/langinfo.h rename to ulib/axlibc/include/langinfo.h diff --git a/tools/axlibc/include/libgen.h b/ulib/axlibc/include/libgen.h similarity index 100% rename from tools/axlibc/include/libgen.h rename to ulib/axlibc/include/libgen.h diff --git a/tools/axlibc/include/limits.h b/ulib/axlibc/include/limits.h similarity index 100% rename from tools/axlibc/include/limits.h rename to ulib/axlibc/include/limits.h diff --git a/tools/axlibc/include/locale.h b/ulib/axlibc/include/locale.h similarity index 100% rename from tools/axlibc/include/locale.h rename to ulib/axlibc/include/locale.h diff --git a/tools/axlibc/include/math.h b/ulib/axlibc/include/math.h similarity index 100% rename from tools/axlibc/include/math.h rename to ulib/axlibc/include/math.h diff --git a/tools/axlibc/include/memory.h b/ulib/axlibc/include/memory.h similarity index 100% rename from tools/axlibc/include/memory.h rename to ulib/axlibc/include/memory.h diff --git a/tools/axlibc/include/netdb.h b/ulib/axlibc/include/netdb.h similarity index 100% rename from tools/axlibc/include/netdb.h rename to ulib/axlibc/include/netdb.h diff --git a/tools/axlibc/include/netinet/in.h b/ulib/axlibc/include/netinet/in.h similarity index 100% rename from tools/axlibc/include/netinet/in.h rename to ulib/axlibc/include/netinet/in.h diff --git a/tools/axlibc/include/netinet/tcp.h b/ulib/axlibc/include/netinet/tcp.h similarity index 100% rename from tools/axlibc/include/netinet/tcp.h rename to ulib/axlibc/include/netinet/tcp.h diff --git a/tools/axlibc/include/poll.h b/ulib/axlibc/include/poll.h similarity index 100% rename from tools/axlibc/include/poll.h rename to ulib/axlibc/include/poll.h diff --git a/tools/axlibc/include/pthread.h b/ulib/axlibc/include/pthread.h similarity index 100% rename from tools/axlibc/include/pthread.h rename to ulib/axlibc/include/pthread.h diff --git a/tools/axlibc/include/pwd.h b/ulib/axlibc/include/pwd.h similarity index 100% rename from tools/axlibc/include/pwd.h rename to ulib/axlibc/include/pwd.h diff --git a/tools/axlibc/include/regex.h b/ulib/axlibc/include/regex.h similarity index 100% rename from tools/axlibc/include/regex.h rename to ulib/axlibc/include/regex.h diff --git a/tools/axlibc/include/sched.h b/ulib/axlibc/include/sched.h similarity index 100% rename from tools/axlibc/include/sched.h rename to ulib/axlibc/include/sched.h diff --git a/tools/axlibc/include/setjmp.h b/ulib/axlibc/include/setjmp.h similarity index 100% rename from tools/axlibc/include/setjmp.h rename to ulib/axlibc/include/setjmp.h diff --git a/tools/axlibc/include/signal.h b/ulib/axlibc/include/signal.h similarity index 100% rename from tools/axlibc/include/signal.h rename to ulib/axlibc/include/signal.h diff --git a/tools/axlibc/include/stdarg.h b/ulib/axlibc/include/stdarg.h similarity index 100% rename from tools/axlibc/include/stdarg.h rename to ulib/axlibc/include/stdarg.h diff --git a/tools/axlibc/include/stdbool.h b/ulib/axlibc/include/stdbool.h similarity index 100% rename from tools/axlibc/include/stdbool.h rename to ulib/axlibc/include/stdbool.h diff --git a/tools/axlibc/include/stddef.h b/ulib/axlibc/include/stddef.h similarity index 100% rename from tools/axlibc/include/stddef.h rename to ulib/axlibc/include/stddef.h diff --git a/tools/axlibc/include/stdint.h b/ulib/axlibc/include/stdint.h similarity index 100% rename from tools/axlibc/include/stdint.h rename to ulib/axlibc/include/stdint.h diff --git a/tools/axlibc/include/stdio.h b/ulib/axlibc/include/stdio.h similarity index 100% rename from tools/axlibc/include/stdio.h rename to ulib/axlibc/include/stdio.h diff --git a/tools/axlibc/include/stdlib.h b/ulib/axlibc/include/stdlib.h similarity index 100% rename from tools/axlibc/include/stdlib.h rename to ulib/axlibc/include/stdlib.h diff --git a/tools/axlibc/include/string.h b/ulib/axlibc/include/string.h similarity index 100% rename from tools/axlibc/include/string.h rename to ulib/axlibc/include/string.h diff --git a/tools/axlibc/include/strings.h b/ulib/axlibc/include/strings.h similarity index 100% rename from tools/axlibc/include/strings.h rename to ulib/axlibc/include/strings.h diff --git a/tools/axlibc/include/sys/epoll.h b/ulib/axlibc/include/sys/epoll.h similarity index 100% rename from tools/axlibc/include/sys/epoll.h rename to ulib/axlibc/include/sys/epoll.h diff --git a/tools/axlibc/include/sys/file.h b/ulib/axlibc/include/sys/file.h similarity index 100% rename from tools/axlibc/include/sys/file.h rename to ulib/axlibc/include/sys/file.h diff --git a/tools/axlibc/include/sys/ioctl.h b/ulib/axlibc/include/sys/ioctl.h similarity index 100% rename from tools/axlibc/include/sys/ioctl.h rename to ulib/axlibc/include/sys/ioctl.h diff --git a/tools/axlibc/include/sys/mman.h b/ulib/axlibc/include/sys/mman.h similarity index 100% rename from tools/axlibc/include/sys/mman.h rename to ulib/axlibc/include/sys/mman.h diff --git a/tools/axlibc/include/sys/param.h b/ulib/axlibc/include/sys/param.h similarity index 100% rename from tools/axlibc/include/sys/param.h rename to ulib/axlibc/include/sys/param.h diff --git a/tools/axlibc/include/sys/prctl.h b/ulib/axlibc/include/sys/prctl.h similarity index 100% rename from tools/axlibc/include/sys/prctl.h rename to ulib/axlibc/include/sys/prctl.h diff --git a/tools/axlibc/include/sys/resource.h b/ulib/axlibc/include/sys/resource.h similarity index 100% rename from tools/axlibc/include/sys/resource.h rename to ulib/axlibc/include/sys/resource.h diff --git a/tools/axlibc/include/sys/select.h b/ulib/axlibc/include/sys/select.h similarity index 100% rename from tools/axlibc/include/sys/select.h rename to ulib/axlibc/include/sys/select.h diff --git a/tools/axlibc/include/sys/socket.h b/ulib/axlibc/include/sys/socket.h similarity index 100% rename from tools/axlibc/include/sys/socket.h rename to ulib/axlibc/include/sys/socket.h diff --git a/tools/axlibc/include/sys/stat.h b/ulib/axlibc/include/sys/stat.h similarity index 100% rename from tools/axlibc/include/sys/stat.h rename to ulib/axlibc/include/sys/stat.h diff --git a/tools/axlibc/include/sys/time.h b/ulib/axlibc/include/sys/time.h similarity index 100% rename from tools/axlibc/include/sys/time.h rename to ulib/axlibc/include/sys/time.h diff --git a/tools/axlibc/include/sys/types.h b/ulib/axlibc/include/sys/types.h similarity index 100% rename from tools/axlibc/include/sys/types.h rename to ulib/axlibc/include/sys/types.h diff --git a/tools/axlibc/include/sys/uio.h b/ulib/axlibc/include/sys/uio.h similarity index 100% rename from tools/axlibc/include/sys/uio.h rename to ulib/axlibc/include/sys/uio.h diff --git a/tools/axlibc/include/sys/un.h b/ulib/axlibc/include/sys/un.h similarity index 100% rename from tools/axlibc/include/sys/un.h rename to ulib/axlibc/include/sys/un.h diff --git a/tools/axlibc/include/sys/utsname.h b/ulib/axlibc/include/sys/utsname.h similarity index 100% rename from tools/axlibc/include/sys/utsname.h rename to ulib/axlibc/include/sys/utsname.h diff --git a/tools/axlibc/include/sys/wait.h b/ulib/axlibc/include/sys/wait.h similarity index 100% rename from tools/axlibc/include/sys/wait.h rename to ulib/axlibc/include/sys/wait.h diff --git a/tools/axlibc/include/syslog.h b/ulib/axlibc/include/syslog.h similarity index 100% rename from tools/axlibc/include/syslog.h rename to ulib/axlibc/include/syslog.h diff --git a/tools/axlibc/include/termios.h b/ulib/axlibc/include/termios.h similarity index 100% rename from tools/axlibc/include/termios.h rename to ulib/axlibc/include/termios.h diff --git a/tools/axlibc/include/time.h b/ulib/axlibc/include/time.h similarity index 100% rename from tools/axlibc/include/time.h rename to ulib/axlibc/include/time.h diff --git a/tools/axlibc/include/unistd.h b/ulib/axlibc/include/unistd.h similarity index 100% rename from tools/axlibc/include/unistd.h rename to ulib/axlibc/include/unistd.h diff --git a/tools/axlibc/src/errno.rs b/ulib/axlibc/src/errno.rs similarity index 100% rename from tools/axlibc/src/errno.rs rename to ulib/axlibc/src/errno.rs diff --git a/tools/axlibc/src/fd_ops.rs b/ulib/axlibc/src/fd_ops.rs similarity index 100% rename from tools/axlibc/src/fd_ops.rs rename to ulib/axlibc/src/fd_ops.rs diff --git a/tools/axlibc/src/fs.rs b/ulib/axlibc/src/fs.rs similarity index 100% rename from tools/axlibc/src/fs.rs rename to ulib/axlibc/src/fs.rs diff --git a/tools/axlibc/src/io.rs b/ulib/axlibc/src/io.rs similarity index 100% rename from tools/axlibc/src/io.rs rename to ulib/axlibc/src/io.rs diff --git a/tools/axlibc/src/io_mpx.rs b/ulib/axlibc/src/io_mpx.rs similarity index 100% rename from tools/axlibc/src/io_mpx.rs rename to ulib/axlibc/src/io_mpx.rs diff --git a/tools/axlibc/src/lib.rs b/ulib/axlibc/src/lib.rs similarity index 100% rename from tools/axlibc/src/lib.rs rename to ulib/axlibc/src/lib.rs diff --git a/tools/axlibc/src/malloc.rs b/ulib/axlibc/src/malloc.rs similarity index 100% rename from tools/axlibc/src/malloc.rs rename to ulib/axlibc/src/malloc.rs diff --git a/tools/axlibc/src/mktime.rs b/ulib/axlibc/src/mktime.rs similarity index 100% rename from tools/axlibc/src/mktime.rs rename to ulib/axlibc/src/mktime.rs diff --git a/tools/axlibc/src/net.rs b/ulib/axlibc/src/net.rs similarity index 100% rename from tools/axlibc/src/net.rs rename to ulib/axlibc/src/net.rs diff --git a/tools/axlibc/src/pipe.rs b/ulib/axlibc/src/pipe.rs similarity index 100% rename from tools/axlibc/src/pipe.rs rename to ulib/axlibc/src/pipe.rs diff --git a/tools/axlibc/src/pthread.rs b/ulib/axlibc/src/pthread.rs similarity index 100% rename from tools/axlibc/src/pthread.rs rename to ulib/axlibc/src/pthread.rs diff --git a/tools/axlibc/src/rand.rs b/ulib/axlibc/src/rand.rs similarity index 100% rename from tools/axlibc/src/rand.rs rename to ulib/axlibc/src/rand.rs diff --git a/tools/axlibc/src/resource.rs b/ulib/axlibc/src/resource.rs similarity index 100% rename from tools/axlibc/src/resource.rs rename to ulib/axlibc/src/resource.rs diff --git a/tools/axlibc/src/setjmp.rs b/ulib/axlibc/src/setjmp.rs similarity index 100% rename from tools/axlibc/src/setjmp.rs rename to ulib/axlibc/src/setjmp.rs diff --git a/tools/axlibc/src/strftime.rs b/ulib/axlibc/src/strftime.rs similarity index 100% rename from tools/axlibc/src/strftime.rs rename to ulib/axlibc/src/strftime.rs diff --git a/tools/axlibc/src/strtod.rs b/ulib/axlibc/src/strtod.rs similarity index 100% rename from tools/axlibc/src/strtod.rs rename to ulib/axlibc/src/strtod.rs diff --git a/tools/axlibc/src/sys.rs b/ulib/axlibc/src/sys.rs similarity index 100% rename from tools/axlibc/src/sys.rs rename to ulib/axlibc/src/sys.rs diff --git a/tools/axlibc/src/time.rs b/ulib/axlibc/src/time.rs similarity index 100% rename from tools/axlibc/src/time.rs rename to ulib/axlibc/src/time.rs diff --git a/tools/axlibc/src/unistd.rs b/ulib/axlibc/src/unistd.rs similarity index 100% rename from tools/axlibc/src/unistd.rs rename to ulib/axlibc/src/unistd.rs diff --git a/tools/axlibc/src/utils.rs b/ulib/axlibc/src/utils.rs similarity index 100% rename from tools/axlibc/src/utils.rs rename to ulib/axlibc/src/utils.rs diff --git a/tools/axlibc/src/vfp_setjmp.S b/ulib/axlibc/src/vfp_setjmp.S similarity index 100% rename from tools/axlibc/src/vfp_setjmp.S rename to ulib/axlibc/src/vfp_setjmp.S diff --git a/ulib/axstarry/.gitignore b/ulib/axstarry/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/ulib/axstarry/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/ulib/axstarry/Cargo.toml b/ulib/axstarry/Cargo.toml new file mode 100644 index 0000000..1d3f94a --- /dev/null +++ b/ulib/axstarry/Cargo.toml @@ -0,0 +1,73 @@ +[package] +name = "axstarry" +version = "0.1.0" +edition = "2021" +keywords = ["Starry"] + +authors = ["Youjie Zheng "] +description = "ArceOS user program library for linux apps with posix_syscall" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["monolithic"] + +monolithic = ["arch_boot/monolithic", "axfeat/monolithic", "paging", "fs", "multitask", "irq", "net"] + +alloc = ["axfeat/alloc"] + +img = ["axfeat/img"] + +# Multicore +smp = ["axfeat/smp", "arch_boot/smp"] + +# Floating point/SIMD +fp_simd = ["axfeat/fp_simd", "arch_boot/fp_simd"] + +# Interrupts +irq = ["axfeat/irq", "arch_boot/irq"] + +# Memory +paging = ["axfeat/paging"] +tls = ["axfeat/tls"] + +# Multi-threading and scheduler +multitask = ["axfeat/multitask"] +sched_fifo = ["axfeat/sched_fifo"] +sched_rr = ["axfeat/sched_rr", "arch_boot/preempt"] +sched_cfs = ["axfeat/sched_cfs", "arch_boot/preempt"] + +# Display +display = ["axfeat/display"] + +# Bus-Pci +bus-pci = ["axfeat/bus-pci"] + +# Fs +fs = ["axfeat/fs"] +fatfs = ["axfeat/fatfs"] +lwext4_rust = ["axfeat/lwext4_rust"] +ext4_rs = ["axfeat/ext4_rs"] +another_ext4 = ["axfeat/another_ext4"] +myfs = ["axfeat/myfs"] +devfs = [] + +# Network +net = ["axfeat/net", "linux_syscall_api/net"] +ixgbe_net = ["net", "axfeat/driver-ixgbe"] +e1000_net = ["net", "axfeat/driver-e1000"] + +# Logging +log-level-off = ["axfeat/log-level-off"] +log-level-error = ["axfeat/log-level-error"] +log-level-warn = ["axfeat/log-level-warn"] +log-level-info = ["axfeat/log-level-info"] +log-level-debug = ["axfeat/log-level-debug"] +log-level-trace = ["axfeat/log-level-trace"] + +[dependencies] +linux_syscall_api = { workspace = true } +axfeat = { workspace = true } +axlog = { workspace = true } +arch_boot = { workspace = true } +axtask = { workspace = true } +axfs = { workspace = true } \ No newline at end of file diff --git a/ulib/axstarry/src/api.rs b/ulib/axstarry/src/api.rs new file mode 100644 index 0000000..764ae36 --- /dev/null +++ b/ulib/axstarry/src/api.rs @@ -0,0 +1,42 @@ +use alloc::string::String; +use alloc::vec; +use alloc::vec::Vec; +use linux_syscall_api::read_file; +/// To get the environment variables of the application +/// +/// # TODO +/// Now the environment variables are hard coded, we need to read the file "/etc/environment" to get the environment variables +pub fn get_envs() -> Vec { + // Const string for environment variables + let mut envs:Vec = vec![ + "SHLVL=1".into(), + "PWD=/".into(), + "GCC_EXEC_PREFIX=/riscv64-linux-musl-native/bin/../lib/gcc/".into(), + "COLLECT_GCC=./riscv64-linux-musl-native/bin/riscv64-linux-musl-gcc".into(), + "COLLECT_LTO_WRAPPER=/riscv64-linux-musl-native/bin/../libexec/gcc/riscv64-linux-musl/11.2.1/lto-wrapper".into(), + "COLLECT_GCC_OPTIONS='-march=rv64gc' '-mabi=lp64d' '-march=rv64imafdc' '-dumpdir' 'a.'".into(), + "LIBRARY_PATH=/lib/".into(), + "LD_LIBRARY_PATH=/lib/".into(), + "LD_DEBUG=files".into(), + ]; + // read the file "/etc/environment" + // if exist, then append the content to envs + // else set the environment variable to default value + if let Some(environment_vars) = read_file("/etc/environment") { + envs.push(environment_vars); + } else { + envs.push("PATH=/usr/sbin:/usr/bin:/sbin:/bin".into()); + } + envs +} +/// To run a testcase with the given name, which will be used in initproc +/// +/// The environment variables are hard coded, we need to read the file "/etc/environment" to get the environment variables +pub fn run_testcase(testcase: &str) { + let _ = linux_syscall_api::run_testcase(testcase, get_envs()); +} + +/// To print a string to the console +pub fn println(s: &str) { + axlog::ax_println!("{}", s); +} diff --git a/ulib/axstarry/src/file.rs b/ulib/axstarry/src/file.rs new file mode 100644 index 0000000..3e543cd --- /dev/null +++ b/ulib/axstarry/src/file.rs @@ -0,0 +1,239 @@ +//! Init some files and links in the filesystem for the apps + +use alloc::{format, vec::*, string::ToString}; +use linux_syscall_api::{create_link, new_file, FileFlags, FilePath}; + +fn meminfo() -> &'static str { + "MemTotal: 32246488 kB +MemFree: 5239804 kB +MemAvailable: 10106000 kB +Buffers: 235604 kB +Cached: 5204940 kB +SwapCached: 0 kB +Active: 17890456 kB +Inactive: 2119348 kB +Active(anon): 14891328 kB +Inactive(anon): 0 kB +Active(file): 2999128 kB +Inactive(file): 2119348 kB +Unevictable: 144 kB +Mlocked: 144 kB +SwapTotal: 8388604 kB +SwapFree: 8388604 kB +Zswap: 0 kB +Zswapped: 0 kB +Dirty: 784 kB +Writeback: 0 kB +AnonPages: 14560300 kB +Mapped: 2108592 kB +Shmem: 323608 kB +KReclaimable: 205804 kB +Slab: 1539752 kB +SReclaimable: 205804 kB +SUnreclaim: 1333948 kB +KernelStack: 630704 kB +PageTables: 2007248 kB +SecPageTables: 0 kB +NFS_Unstable: 0 kB +Bounce: 0 kB +WritebackTmp: 0 kB +CommitLimit: 24511848 kB +Committed_AS: 42466972 kB +VmallocTotal: 34359738367 kB +VmallocUsed: 762644 kB +VmallocChunk: 0 kB +Percpu: 35776 kB +HardwareCorrupted: 0 kB +AnonHugePages: 79872 kB +ShmemHugePages: 0 kB +ShmemPmdMapped: 0 kB +FileHugePages: 0 kB +FilePmdMapped: 0 kB +Unaccepted: 0 kB +HugePages_Total: 0 +HugePages_Free: 0 +HugePages_Rsvd: 0 +HugePages_Surp: 0 +Hugepagesize: 2048 kB +Hugetlb: 0 kB +DirectMap4k: 6500036 kB +DirectMap2M: 23283712 kB +DirectMap1G: 3145728 kB" +} + +// TODO: Implement the real content of overcommit_memory +fn oominfo() -> &'static str { + "0" +} + +fn get_status_info(task: &axtask::CurrentTask) -> Vec { + let name = task.name().as_bytes(); + let id = task.id().as_u64().to_string(); + let status_vec = [name, b"\n", id.as_bytes(), b"\n256\n"].concat(); + status_vec +} + +/// 在执行系统调用前初始化文件系统 +/// +/// 包括建立软连接,提前准备好一系列的文件与文件夹 +/// +/// Fat32 filesystem doesn't exists the concept of soft link, so we need to call this function every time we boot the system +pub fn fs_init() { + #[cfg(target_arch = "riscv64")] + let libc_so = &"ld-musl-riscv64-sf.so.1"; + #[cfg(target_arch = "riscv64")] + let libc_so2 = &"ld-musl-riscv64.so.1"; // 另一种名字的 libc.so,非 libc-test 测例库用 + + #[cfg(target_arch = "x86_64")] + let libc_so = &"ld-musl-x86_64-sf.so.1"; + #[cfg(target_arch = "x86_64")] + let libc_so2 = &"ld-musl-x86_64.so.1"; // 另一种名字的 libc.so,非 libc-test 测例库用 + + #[cfg(target_arch = "aarch64")] + let libc_so = &"ld-musl-aarch64-sf.so.1"; + #[cfg(target_arch = "aarch64")] + let libc_so2 = &"ld-musl-aarch64.so.1"; // 另一种名字的 libc.so,非 libc-test 测例库用 + + create_link( + &(FilePath::new(("/lib/".to_string() + libc_so).as_str()).unwrap()), + &(FilePath::new("libc.so").unwrap()), + ); + create_link( + &(FilePath::new(("/lib/".to_string() + libc_so2).as_str()).unwrap()), + &(FilePath::new("libc.so").unwrap()), + ); + + let tls_so = &"tls_get_new-dtv_dso.so"; + create_link( + &(FilePath::new(("/lib/".to_string() + tls_so).as_str()).unwrap()), + &(FilePath::new("tls_get_new-dtv_dso.so").unwrap()), + ); + + // 接下来对 busybox 相关的指令建立软链接 + let busybox_arch = ["ls", "mkdir", "touch", "mv", "busybox", "sh", "which", "cp"]; + for arch in busybox_arch { + let src_path = "/usr/sbin/".to_string() + arch; + create_link( + &(FilePath::new(src_path.as_str()).unwrap()), + &(FilePath::new("busybox").unwrap()), + ); + let src_path = "/usr/bin/".to_string() + arch; + create_link( + &(FilePath::new(src_path.as_str()).unwrap()), + &(FilePath::new("busybox").unwrap()), + ); + let src_path = "/bin/".to_string() + arch; + create_link( + &(FilePath::new(src_path.as_str()).unwrap()), + &(FilePath::new("busybox").unwrap()), + ); + } + create_link( + &(FilePath::new("/bin/lmbench_all").unwrap()), + &(FilePath::new("/lmbench_all").unwrap()), + ); + create_link( + &(FilePath::new("/bin/iozone").unwrap()), + &(FilePath::new("/iozone").unwrap()), + ); + + #[cfg(target_arch = "x86_64")] + { + let libc_zlm = &"/lib/ld-linux-x86-64.so.2"; + create_link( + &(FilePath::new(libc_zlm).unwrap()), + &(FilePath::new("ld-linux-x86-64.so.2").unwrap()), + ); + + create_link( + &(FilePath::new("/lib/libssl.so.3").unwrap()), + &(FilePath::new("libssl.so.3").unwrap()), + ); + + create_link( + &(FilePath::new("/lib/libcrypto.so.3").unwrap()), + &(FilePath::new("libcrypto.so.3").unwrap()), + ); + + create_link( + &(FilePath::new("/lib/libstdc++.so.6").unwrap()), + &(FilePath::new("libstdc++.so.6").unwrap()), + ); + + create_link( + &(FilePath::new("/lib/libm.so.6").unwrap()), + &(FilePath::new("libm.so.6").unwrap()), + ); + + create_link( + &(FilePath::new("/lib/libgcc_s.so.1").unwrap()), + &(FilePath::new("libgcc_s.so.1").unwrap()), + ); + + create_link( + &(FilePath::new("/lib/libc.so.6").unwrap()), + &(FilePath::new("libc.so.6").unwrap()), + ); + } + + let mem_file = axfs::api::lookup("/proc/meminfo").unwrap(); + mem_file.write_at(0, meminfo().as_bytes()).unwrap(); + let oom_file = axfs::api::lookup("/proc/sys/vm/overcommit_memory").unwrap(); + oom_file.write_at(0, oominfo().as_bytes()).unwrap(); + let fs_file = axfs::api::lookup("/proc/filesystems").unwrap(); + fs_file.write_at(0, b"fat32\next4\n").unwrap(); + + let status_file = axfs::api::lookup("/proc/self/status").unwrap(); + let status_info: &[u8] = &get_status_info(&axtask::current()); + status_file.write_at(0, status_info).unwrap(); + + // create the file for the lmbench testcase + let _ = new_file("/lat_sig", &(FileFlags::CREATE | FileFlags::RDWR)); + + // gcc相关的链接,可以在testcases/gcc/riscv64-linux-musl-native/lib目录下使用ls -al指令查看 + let src_dir = "riscv64-linux-musl-native/lib"; + create_link( + &FilePath::new(format!("{}/ld-musl-riscv64.so.1", src_dir).as_str()).unwrap(), + &FilePath::new("/lib/libc.so").unwrap(), + ); + create_link( + &FilePath::new(format!("{}/libatomic.so", src_dir).as_str()).unwrap(), + &FilePath::new(format!("{}/libatomic.so.1.2.0", src_dir).as_str()).unwrap(), + ); + create_link( + &FilePath::new(format!("{}/libatomic.so.1", src_dir).as_str()).unwrap(), + &FilePath::new(format!("{}/libatomic.so.1.2.0", src_dir).as_str()).unwrap(), + ); + create_link( + &FilePath::new(format!("{}/libgfortran.so", src_dir).as_str()).unwrap(), + &FilePath::new(format!("{}/libgfortran.so.5.0.0", src_dir).as_str()).unwrap(), + ); + create_link( + &FilePath::new(format!("{}/libgfortran.so.5", src_dir).as_str()).unwrap(), + &FilePath::new(format!("{}/libgfortran.so.5.0.0", src_dir).as_str()).unwrap(), + ); + create_link( + &FilePath::new(format!("{}/libgomp.so", src_dir).as_str()).unwrap(), + &FilePath::new(format!("{}/libgomp.so.1.0.0", src_dir).as_str()).unwrap(), + ); + create_link( + &FilePath::new(format!("{}/libgomp.so.1", src_dir).as_str()).unwrap(), + &FilePath::new(format!("{}/libgomp.so.1.0.0", src_dir).as_str()).unwrap(), + ); + create_link( + &FilePath::new(format!("{}/libssp.so", src_dir).as_str()).unwrap(), + &FilePath::new(format!("{}/libssp.so.0.0.0", src_dir).as_str()).unwrap(), + ); + create_link( + &FilePath::new(format!("{}/libssp.so.0", src_dir).as_str()).unwrap(), + &FilePath::new(format!("{}/libssp.so.0.0.0", src_dir).as_str()).unwrap(), + ); + create_link( + &FilePath::new(format!("{}/libstdc++.so", src_dir).as_str()).unwrap(), + &FilePath::new(format!("{}/libstdc++.so.6.0.29", src_dir).as_str()).unwrap(), + ); + create_link( + &FilePath::new(format!("{}/libstdc++.so.6", src_dir).as_str()).unwrap(), + &FilePath::new(format!("{}/libstdc++.so.6.0.29", src_dir).as_str()).unwrap(), + ); +} diff --git a/ulib/axstarry/src/lib.rs b/ulib/axstarry/src/lib.rs new file mode 100644 index 0000000..b3dbe5b --- /dev/null +++ b/ulib/axstarry/src/lib.rs @@ -0,0 +1,11 @@ +//! The entry of syscall, which will distribute the syscall to the corresponding function +#![cfg_attr(all(not(test), not(doc)), no_std)] + +extern crate alloc; +extern crate arch_boot; +mod file; +pub use file::fs_init; +mod api; +pub use api::{println, run_testcase}; + +pub use linux_syscall_api::{init_current_dir, recycle_user_process}; diff --git a/ulib/axstd/.gitignore b/ulib/axstd/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/ulib/axstd/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/ulib/axstd/Cargo.toml b/ulib/axstd/Cargo.toml new file mode 100644 index 0000000..7aa5d2a --- /dev/null +++ b/ulib/axstd/Cargo.toml @@ -0,0 +1,80 @@ +[package] +name = "axstd" +version = "0.1.0" +edition = "2021" +authors = [ + "Yuekai Jia ", + "yanjuguang ", + "wudashuai ", + "yfblock <321353225@qq.com>", + "scPointer ", + "Shiping Yuan ", +] +description = "ArceOS user library with an interface similar to rust std" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/ulib/axstd" +documentation = "https://rcore-os.github.io/arceos/axstd/index.html" +keywords = ["Starry"] + +[features] +default = [] + +# Multicore +smp = ["axfeat/smp", "spinlock/smp", "arch_boot/smp"] + +# Floating point/SIMD +fp_simd = ["axfeat/fp_simd", "arch_boot/fp_simd"] + +# Interrupts +irq = ["arceos_api/irq", "axfeat/irq", "arch_boot/irq"] + +# Memory +alloc = ["arceos_api/alloc", "axfeat/alloc", "axio/alloc"] +alloc-tlsf = ["axfeat/alloc-tlsf"] +alloc-slab = ["axfeat/alloc-slab"] +alloc-buddy = ["axfeat/alloc-buddy"] +paging = ["axfeat/paging"] +tls = ["axfeat/tls"] + +# Multi-threading and scheduler +multitask = ["arceos_api/multitask", "axfeat/multitask"] +sched_fifo = ["axfeat/sched_fifo"] +sched_rr = ["axfeat/sched_rr", "arch_boot/preempt"] +sched_cfs = ["axfeat/sched_cfs", "arch_boot/preempt"] + +# File system +fs = ["arceos_api/fs", "axfeat/fs"] +myfs = ["arceos_api/myfs", "axfeat/myfs"] +fatfs = ["axfeat/fatfs"] +lwext4_rust = ["axfeat/lwext4_rust", "fs"] + +# Networking +net = ["arceos_api/net", "axfeat/net"] +dns = [] + +# Display +display = ["arceos_api/display", "axfeat/display"] + +# Device drivers +bus-mmio = ["axfeat/bus-mmio"] +bus-pci = ["axfeat/bus-pci"] +driver-ramdisk = ["axfeat/driver-ramdisk"] +driver-ixgbe = ["axfeat/driver-ixgbe"] +driver-bcm2835-sdhci = ["axfeat/driver-bcm2835-sdhci"] + +# Logging +log-level-off = ["axfeat/log-level-off"] +log-level-error = ["axfeat/log-level-error"] +log-level-warn = ["axfeat/log-level-warn"] +log-level-info = ["axfeat/log-level-info"] +log-level-debug = ["axfeat/log-level-debug"] +log-level-trace = ["axfeat/log-level-trace"] + +[dependencies] +axfeat = { workspace = true } +arceos_api = { workspace = true } +axio = { git = "https://github.com/Starry-OS/axio.git" } +axerrno = { git = "https://github.com/Starry-OS/axerrno.git" } +spinlock = { git = "https://github.com/Starry-OS/spinlock.git" } +arch_boot = { workspace = true } \ No newline at end of file diff --git a/ulib/axstd/src/env.rs b/ulib/axstd/src/env.rs new file mode 100644 index 0000000..13c06e2 --- /dev/null +++ b/ulib/axstd/src/env.rs @@ -0,0 +1,19 @@ +//! Inspection and manipulation of the process’s environment. + +#[cfg(feature = "fs")] +extern crate alloc; + +#[cfg(feature = "fs")] +use {crate::io, alloc::string::String}; + +/// Returns the current working directory as a [`String`]. +#[cfg(feature = "fs")] +pub fn current_dir() -> io::Result { + arceos_api::fs::ax_current_dir() +} + +/// Changes the current working directory to the specified path. +#[cfg(feature = "fs")] +pub fn set_current_dir(path: &str) -> io::Result<()> { + arceos_api::fs::ax_set_current_dir(path) +} diff --git a/ulib/axstd/src/fs/dir.rs b/ulib/axstd/src/fs/dir.rs new file mode 100644 index 0000000..bf49cc8 --- /dev/null +++ b/ulib/axstd/src/fs/dir.rs @@ -0,0 +1,154 @@ +extern crate alloc; + +use alloc::string::String; +use core::fmt; + +use super::FileType; +use crate::io::Result; + +use arceos_api::fs as api; + +/// Iterator over the entries in a directory. +pub struct ReadDir<'a> { + path: &'a str, + inner: api::AxDirHandle, + buf_pos: usize, + buf_end: usize, + end_of_stream: bool, + dirent_buf: [api::AxDirEntry; 31], +} + +/// Entries returned by the [`ReadDir`] iterator. +pub struct DirEntry<'a> { + dir_path: &'a str, + entry_name: String, + entry_type: FileType, +} + +/// A builder used to create directories in various manners. +#[derive(Default, Debug)] +pub struct DirBuilder { + recursive: bool, +} + +impl<'a> ReadDir<'a> { + pub(super) fn new(path: &'a str) -> Result { + let mut opts = api::AxOpenOptions::new(); + opts.read(true); + let inner = api::ax_open_dir(path, &opts)?; + + const EMPTY: api::AxDirEntry = api::AxDirEntry::default(); + let dirent_buf = [EMPTY; 31]; + Ok(ReadDir { + path, + inner, + end_of_stream: false, + buf_pos: 0, + buf_end: 0, + dirent_buf, + }) + } +} + +impl<'a> Iterator for ReadDir<'a> { + type Item = Result>; + + fn next(&mut self) -> Option>> { + if self.end_of_stream { + return None; + } + + loop { + if self.buf_pos >= self.buf_end { + match api::ax_read_dir(&mut self.inner, &mut self.dirent_buf) { + Ok(n) => { + if n == 0 { + self.end_of_stream = true; + return None; + } + self.buf_pos = 0; + self.buf_end = n; + } + Err(e) => { + self.end_of_stream = true; + return Some(Err(e)); + } + } + } + let entry = &self.dirent_buf[self.buf_pos]; + self.buf_pos += 1; + let name_bytes = entry.name_as_bytes(); + if name_bytes == b"." || name_bytes == b".." { + continue; + } + let entry_name = unsafe { core::str::from_utf8_unchecked(name_bytes).into() }; + let entry_type = entry.entry_type(); + + return Some(Ok(DirEntry { + dir_path: self.path, + entry_name, + entry_type, + })); + } + } +} + +impl<'a> DirEntry<'a> { + /// Returns the full path to the file that this entry represents. + /// + /// The full path is created by joining the original path to `read_dir` + /// with the filename of this entry. + pub fn path(&self) -> String { + String::from(self.dir_path.trim_end_matches('/')) + "/" + &self.entry_name + } + + /// Returns the bare file name of this directory entry without any other + /// leading path component. + pub fn file_name(&self) -> String { + self.entry_name.clone() + } + + /// Returns the file type for the file that this entry points at. + pub fn file_type(&self) -> FileType { + self.entry_type + } +} + +impl fmt::Debug for DirEntry<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("DirEntry").field(&self.path()).finish() + } +} + +impl DirBuilder { + /// Creates a new set of options with default mode/security settings for all + /// platforms and also non-recursive. + pub fn new() -> Self { + Self { recursive: false } + } + + /// Indicates that directories should be created recursively, creating all + /// parent directories. Parents that do not exist are created with the same + /// security and permissions settings. + pub fn recursive(&mut self, recursive: bool) -> &mut Self { + self.recursive = recursive; + self + } + + /// Creates the specified directory with the options configured in this + /// builder. + pub fn create(&self, path: &str) -> Result<()> { + if self.recursive { + self.create_dir_all(path) + } else { + api::ax_create_dir(path) + } + } + + fn create_dir_all(&self, _path: &str) -> Result<()> { + axerrno::ax_err!( + Unsupported, + "Recursive directory creation is not supported yet" + ) + } +} diff --git a/ulib/axstd/src/fs/file.rs b/ulib/axstd/src/fs/file.rs new file mode 100644 index 0000000..6984057 --- /dev/null +++ b/ulib/axstd/src/fs/file.rs @@ -0,0 +1,187 @@ +use crate::io::{prelude::*, Result, SeekFrom}; +use core::fmt; + +use arceos_api::fs as api; + +/// A structure representing a type of file with accessors for each file type. +/// It is returned by [`Metadata::file_type`] method. +pub type FileType = api::AxFileType; + +/// Representation of the various permissions on a file. +pub type Permissions = api::AxFilePerm; + +/// An object providing access to an open file on the filesystem. +pub struct File { + inner: api::AxFileHandle, +} + +/// Metadata information about a file. +pub struct Metadata(api::AxFileAttr); + +/// Options and flags which can be used to configure how a file is opened. +#[derive(Clone, Debug)] +pub struct OpenOptions(api::AxOpenOptions); + +impl OpenOptions { + /// Creates a blank new set of options ready for configuration. + pub const fn new() -> Self { + OpenOptions(api::AxOpenOptions::new()) + } + + /// Sets the option for read access. + pub fn read(&mut self, read: bool) -> &mut Self { + self.0.read(read); + self + } + + /// Sets the option for write access. + pub fn write(&mut self, write: bool) -> &mut Self { + self.0.write(write); + self + } + + /// Sets the option for the append mode. + pub fn append(&mut self, append: bool) -> &mut Self { + self.0.append(append); + self + } + + /// Sets the option for truncating a previous file. + pub fn truncate(&mut self, truncate: bool) -> &mut Self { + self.0.truncate(truncate); + self + } + + /// Sets the option to create a new file, or open it if it already exists. + pub fn create(&mut self, create: bool) -> &mut Self { + self.0.create(create); + self + } + + /// Sets the option to create a new file, failing if it already exists. + pub fn create_new(&mut self, create_new: bool) -> &mut Self { + self.0.create_new(create_new); + self + } + + /// Opens a file at `path` with the options specified by `self`. + pub fn open(&self, path: &str) -> Result { + api::ax_open_file(path, &self.0).map(|inner| File { inner }) + } +} + +impl Metadata { + /// Returns the file type for this metadata. + pub const fn file_type(&self) -> FileType { + self.0.file_type() + } + + /// Returns `true` if this metadata is for a directory. The + /// result is mutually exclusive to the result of + /// [`Metadata::is_file`]. + pub const fn is_dir(&self) -> bool { + self.0.is_dir() + } + + /// Returns `true` if this metadata is for a regular file. The + /// result is mutually exclusive to the result of + /// [`Metadata::is_dir`]. + pub const fn is_file(&self) -> bool { + self.0.is_file() + } + + /// Returns the size of the file, in bytes, this metadata is for. + #[allow(clippy::len_without_is_empty)] + pub const fn len(&self) -> u64 { + self.0.size() + } + + /// Returns the permissions of the file this metadata is for. + pub const fn permissions(&self) -> Permissions { + self.0.perm() + } + + /// Returns the total size of this file in bytes. + pub const fn size(&self) -> u64 { + self.0.size() + } + + /// Returns the number of blocks allocated to the file, in 512-byte units. + pub const fn blocks(&self) -> u64 { + self.0.blocks() + } +} + +impl fmt::Debug for Metadata { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Metadata") + .field("file_type", &self.file_type()) + .field("is_dir", &self.is_dir()) + .field("is_file", &self.is_file()) + .field("permissions", &self.permissions()) + .finish_non_exhaustive() + } +} + +impl File { + /// Attempts to open a file in read-only mode. + pub fn open(path: &str) -> Result { + OpenOptions::new().read(true).open(path) + } + + /// Opens a file in write-only mode. + pub fn create(path: &str) -> Result { + OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(path) + } + + /// Creates a new file in read-write mode; error if the file exists. + pub fn create_new(path: &str) -> Result { + OpenOptions::new() + .read(true) + .write(true) + .create_new(true) + .open(path) + } + + /// Returns a new OpenOptions object. + pub fn options() -> OpenOptions { + OpenOptions::new() + } + + /// Truncates or extends the underlying file, updating the size of + /// this file to become `size`. + pub fn set_len(&self, size: u64) -> Result<()> { + api::ax_truncate_file(&self.inner, size) + } + + /// Queries metadata about the underlying file. + pub fn metadata(&self) -> Result { + api::ax_file_attr(&self.inner).map(Metadata) + } +} + +impl Read for File { + fn read(&mut self, buf: &mut [u8]) -> Result { + api::ax_read_file(&mut self.inner, buf) + } +} + +impl Write for File { + fn write(&mut self, buf: &[u8]) -> Result { + api::ax_write_file(&mut self.inner, buf) + } + + fn flush(&mut self) -> Result<()> { + api::ax_flush_file(&self.inner) + } +} + +impl Seek for File { + fn seek(&mut self, pos: SeekFrom) -> Result { + api::ax_seek_file(&mut self.inner, pos) + } +} diff --git a/ulib/axstd/src/fs/mod.rs b/ulib/axstd/src/fs/mod.rs new file mode 100644 index 0000000..5308045 --- /dev/null +++ b/ulib/axstd/src/fs/mod.rs @@ -0,0 +1,77 @@ +//! Filesystem manipulation operations. + +mod dir; +mod file; + +use crate::io::{self, prelude::*}; + +#[cfg(feature = "alloc")] +use alloc::{string::String, vec::Vec}; + +pub use self::dir::{DirBuilder, DirEntry, ReadDir}; +pub use self::file::{File, FileType, Metadata, OpenOptions, Permissions}; + +/// Read the entire contents of a file into a bytes vector. +#[cfg(feature = "alloc")] +pub fn read(path: &str) -> io::Result> { + let mut file = File::open(path)?; + let size = file.metadata().map(|m| m.len()).unwrap_or(0); + let mut bytes = Vec::with_capacity(size as usize); + file.read_to_end(&mut bytes)?; + Ok(bytes) +} + +/// Read the entire contents of a file into a string. +#[cfg(feature = "alloc")] +pub fn read_to_string(path: &str) -> io::Result { + let mut file = File::open(path)?; + let size = file.metadata().map(|m| m.len()).unwrap_or(0); + let mut string = String::with_capacity(size as usize); + file.read_to_string(&mut string)?; + Ok(string) +} + +/// Write a slice as the entire contents of a file. +pub fn write>(path: &str, contents: C) -> io::Result<()> { + File::create(path)?.write_all(contents.as_ref()) +} + +/// Given a path, query the file system to get information about a file, +/// directory, etc. +pub fn metadata(path: &str) -> io::Result { + File::open(path)?.metadata() +} + +/// Returns an iterator over the entries within a directory. +pub fn read_dir(path: &str) -> io::Result { + ReadDir::new(path) +} + +/// Creates a new, empty directory at the provided path. +pub fn create_dir(path: &str) -> io::Result<()> { + DirBuilder::new().create(path) +} + +/// Recursively create a directory and all of its parent components if they +/// are missing. +pub fn create_dir_all(path: &str) -> io::Result<()> { + DirBuilder::new().recursive(true).create(path) +} + +/// Removes an empty directory. +pub fn remove_dir(path: &str) -> io::Result<()> { + arceos_api::fs::ax_remove_dir(path) +} + +/// Removes a file from the filesystem. +pub fn remove_file(path: &str) -> io::Result<()> { + arceos_api::fs::ax_remove_file(path) +} + +/// Rename a file or directory to a new name. +/// Delete the original file if `old` already exists. +/// +/// This only works then the new path is in the same mounted fs. +pub fn rename(old: &str, new: &str) -> io::Result<()> { + arceos_api::fs::ax_rename(old, new) +} diff --git a/ulib/axstd/src/io/mod.rs b/ulib/axstd/src/io/mod.rs new file mode 100644 index 0000000..7a0cf1f --- /dev/null +++ b/ulib/axstd/src/io/mod.rs @@ -0,0 +1,28 @@ +//! Traits, helpers, and type definitions for core I/O functionality. + +mod stdio; + +pub use axio::prelude; +pub use axio::{BufRead, BufReader, Error, Read, Seek, SeekFrom, Write}; + +#[doc(hidden)] +pub use self::stdio::__print_impl; +pub use self::stdio::{stdin, stdout, Stdin, StdinLock, Stdout, StdoutLock}; + +/// A specialized [`Result`] type for I/O operations. +/// +/// This type is broadly used across [`axstd::io`] for any operation which may +/// produce an error. +/// +/// This typedef is generally used to avoid writing out [`io::Error`] directly and +/// is otherwise a direct mapping to [`Result`]. +/// +/// While usual Rust style is to import types directly, aliases of [`Result`] +/// often are not, to make it easier to distinguish between them. [`Result`] is +/// generally assumed to be [`std::result::Result`][`Result`], and so users of this alias +/// will generally use `io::Result` instead of shadowing the [prelude]'s import +/// of [`std::result::Result`][`Result`]. +/// +/// [`axstd::io`]: crate::io +/// [`io::Error`]: Error +pub type Result = axio::Result; diff --git a/ulib/axstd/src/io/stdio.rs b/ulib/axstd/src/io/stdio.rs new file mode 100644 index 0000000..0b07f4b --- /dev/null +++ b/ulib/axstd/src/io/stdio.rs @@ -0,0 +1,173 @@ +use crate::io::{self, prelude::*, BufReader}; +use crate::sync::{Mutex, MutexGuard}; + +#[cfg(feature = "alloc")] +use alloc::{string::String, vec::Vec}; + +struct StdinRaw; +struct StdoutRaw; + +impl Read for StdinRaw { + // Non-blocking read, returns number of bytes read. + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let mut read_len = 0; + while read_len < buf.len() { + if let Some(c) = arceos_api::stdio::ax_console_read_byte() { + buf[read_len] = c; + read_len += 1; + } else { + break; + } + } + Ok(read_len) + } +} + +impl Write for StdoutRaw { + fn write(&mut self, buf: &[u8]) -> io::Result { + arceos_api::stdio::ax_console_write_bytes(buf) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +/// A handle to the standard input stream of a process. +pub struct Stdin { + inner: &'static Mutex>, +} + +/// A locked reference to the [`Stdin`] handle. +pub struct StdinLock<'a> { + inner: MutexGuard<'a, BufReader>, +} + +impl Stdin { + /// Locks this handle to the standard input stream, returning a readable + /// guard. + /// + /// The lock is released when the returned lock goes out of scope. The + /// returned guard also implements the [`Read`] and [`BufRead`] traits for + /// accessing the underlying data. + pub fn lock(&self) -> StdinLock<'static> { + // Locks this handle with 'static lifetime. This depends on the + // implementation detail that the underlying `Mutex` is static. + StdinLock { + inner: self.inner.lock(), + } + } + + /// Locks this handle and reads a line of input, appending it to the specified buffer. + #[cfg(feature = "alloc")] + pub fn read_line(&self, buf: &mut String) -> io::Result { + self.inner.lock().read_line(buf) + } +} + +impl Read for Stdin { + // Block until at least one byte is read. + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let read_len = self.inner.lock().read(buf)?; + if buf.is_empty() || read_len > 0 { + return Ok(read_len); + } + // try again until we got something + loop { + let read_len = self.inner.lock().read(buf)?; + if read_len > 0 { + return Ok(read_len); + } + crate::thread::yield_now(); + } + } +} + +impl Read for StdinLock<'_> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.inner.read(buf) + } +} + +impl BufRead for StdinLock<'_> { + fn fill_buf(&mut self) -> io::Result<&[u8]> { + self.inner.fill_buf() + } + + fn consume(&mut self, n: usize) { + self.inner.consume(n) + } + + #[cfg(feature = "alloc")] + fn read_until(&mut self, byte: u8, buf: &mut Vec) -> io::Result { + self.inner.read_until(byte, buf) + } + + #[cfg(feature = "alloc")] + fn read_line(&mut self, buf: &mut String) -> io::Result { + self.inner.read_line(buf) + } +} + +/// A handle to the global standard output stream of the current process. +pub struct Stdout { + inner: &'static Mutex, +} + +/// A locked reference to the [`Stdout`] handle. +pub struct StdoutLock<'a> { + inner: MutexGuard<'a, StdoutRaw>, +} + +impl Stdout { + /// Locks this handle to the standard output stream, returning a writable + /// guard. + /// + /// The lock is released when the returned lock goes out of scope. The + /// returned guard also implements the `Write` trait for writing data. + pub fn lock(&self) -> StdoutLock<'static> { + StdoutLock { + inner: self.inner.lock(), + } + } +} + +impl Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.inner.lock().write(buf) + } + fn flush(&mut self) -> io::Result<()> { + self.inner.lock().flush() + } +} + +impl Write for StdoutLock<'_> { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.inner.write(buf) + } + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + +/// Constructs a new handle to the standard input of the current process. +pub fn stdin() -> Stdin { + static INSTANCE: Mutex> = Mutex::new(BufReader::new(StdinRaw)); + Stdin { inner: &INSTANCE } +} + +/// Constructs a new handle to the standard output of the current process. +pub fn stdout() -> Stdout { + static INSTANCE: Mutex = Mutex::new(StdoutRaw); + Stdout { inner: &INSTANCE } +} + +#[doc(hidden)] +pub fn __print_impl(args: core::fmt::Arguments) { + if cfg!(feature = "smp") { + // synchronize using the lock in axlog, to avoid interleaving + // with kernel logs + arceos_api::stdio::ax_console_write_fmt(args).unwrap(); + } else { + stdout().lock().write_fmt(args).unwrap(); + } +} diff --git a/ulib/axstd/src/lib.rs b/ulib/axstd/src/lib.rs new file mode 100644 index 0000000..b672e61 --- /dev/null +++ b/ulib/axstd/src/lib.rs @@ -0,0 +1,76 @@ +//! # The ArceOS Standard Library +//! +//! The [ArceOS] Standard Library is a mini-std library, with an interface similar +//! to rust [std], but calling the functions directly in ArceOS modules, instead +//! of using libc and system calls. +//! +//! These features are exactly the same as those in [axfeat], they are used to +//! provide users with the selection of features in axfeat, without import +//! [axfeat] additionally: +//! +//! ## Cargo Features +//! +//! - CPU +//! - `smp`: Enable SMP (symmetric multiprocessing) support. +//! - `fp_simd`: Enable floating point and SIMD support. +//! - Interrupts: +//! - `irq`: Enable interrupt handling support. +//! - Memory +//! - `alloc`: Enable dynamic memory allocation. +//! - `alloc-tlsf`: Use the TLSF allocator. +//! - `alloc-slab`: Use the slab allocator. +//! - `alloc-buddy`: Use the buddy system allocator. +//! - `paging`: Enable page table manipulation. +//! - `tls`: Enable thread-local storage. +//! - Task management +//! - `multitask`: Enable multi-threading support. +//! - `sched_fifo`: Use the FIFO cooperative scheduler. +//! - `sched_rr`: Use the Round-robin preemptive scheduler. +//! - `sched_cfs`: Use the Completely Fair Scheduler (CFS) preemptive scheduler. +//! - Upperlayer stacks +//! - `fs`: Enable file system support. +//! - `myfs`: Allow users to define their custom filesystems to override the default. +//! - `net`: Enable networking support. +//! - `dns`: Enable DNS lookup support. +//! - `display`: Enable graphics support. +//! - Device drivers +//! - `bus-mmio`: Use device tree to probe all MMIO devices. +//! - `bus-pci`: Use PCI bus to probe all PCI devices. +//! - `driver-ramdisk`: Use the RAM disk to emulate the block device. +//! - `driver-ixgbe`: Enable the Intel 82599 10Gbit NIC driver. +//! - `driver-bcm2835-sdhci`: Enable the BCM2835 SDHCI driver (Raspberry Pi SD card). +//! - Logging +//! - `log-level-off`: Disable all logging. +//! - `log-level-error`, `log-level-warn`, `log-level-info`, `log-level-debug`, +//! `log-level-trace`: Keep logging only at the specified level or higher. +//! +//! [ArceOS]: https://github.com/rcore-os/arceos + +#![cfg_attr(all(not(test), not(doc)), no_std)] +#![feature(doc_cfg)] +#![feature(doc_auto_cfg)] +#[cfg(feature = "alloc")] +extern crate alloc; +extern crate arch_boot; +#[cfg(feature = "alloc")] +#[doc(no_inline)] +pub use alloc::{boxed, collections, format, string, vec}; + +#[doc(no_inline)] +pub use core::{arch, cell, cmp, hint, marker, mem, ops, ptr, slice, str}; + +#[macro_use] +mod macros; + +pub mod env; +pub mod io; +pub mod os; +pub mod process; +pub mod sync; +pub mod thread; +pub mod time; + +#[cfg(feature = "fs")] +pub mod fs; +#[cfg(feature = "net")] +pub mod net; diff --git a/ulib/axstd/src/macros.rs b/ulib/axstd/src/macros.rs new file mode 100644 index 0000000..c7cd653 --- /dev/null +++ b/ulib/axstd/src/macros.rs @@ -0,0 +1,23 @@ +//! Standard library macros + +/// Prints to the standard output. +/// +/// Equivalent to the [`println!`] macro except that a newline is not printed at +/// the end of the message. +/// +/// [`println!`]: crate::println +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => { + $crate::io::__print_impl(format_args!($($arg)*)); + } +} + +/// Prints to the standard output, with a newline. +#[macro_export] +macro_rules! println { + () => { $crate::print!("\n") }; + ($($arg:tt)*) => { + $crate::io::__print_impl(format_args!("{}\n", format_args!($($arg)*))); + } +} diff --git a/ulib/axstd/src/net/mod.rs b/ulib/axstd/src/net/mod.rs new file mode 100644 index 0000000..3d789ce --- /dev/null +++ b/ulib/axstd/src/net/mod.rs @@ -0,0 +1,46 @@ +//! Networking primitives for TCP/UDP communication. +//! +//! This module provides networking functionality for the Transmission Control and User +//! Datagram Protocols, as well as types for IP and socket addresses. +//! +//! # Organization +//! +//! * [`TcpListener`] and [`TcpStream`] provide functionality for communication over TCP +//! * [`UdpSocket`] provides functionality for communication over UDP +//! * [`IpAddr`] represents IP addresses of either IPv4 or IPv6; [`Ipv4Addr`] and +//! [`Ipv6Addr`] are respectively IPv4 and IPv6 addresses +//! * [`SocketAddr`] represents socket addresses of either IPv4 or IPv6; [`SocketAddrV4`] +//! and [`SocketAddrV6`] are respectively IPv4 and IPv6 socket addresses +//! * [`ToSocketAddrs`] is a trait that is used for generic address resolution when interacting +//! with networking objects like [`TcpListener`], [`TcpStream`] or [`UdpSocket`] + +mod socket_addr; +mod tcp; +mod udp; + +pub use self::socket_addr::{IpAddr, Ipv4Addr, Ipv6Addr}; +pub use self::socket_addr::{SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; +pub use self::tcp::{TcpListener, TcpStream}; +pub use self::udp::UdpSocket; + +use crate::io; + +fn each_addr(addr: A, mut f: F) -> io::Result +where + F: FnMut(io::Result<&SocketAddr>) -> io::Result, +{ + let addrs = match addr.to_socket_addrs() { + Ok(addrs) => addrs, + Err(e) => return f(Err(e)), + }; + let mut last_err = None; + for addr in addrs { + match f(Ok(&addr)) { + Ok(l) => return Ok(l), + Err(e) => last_err = Some(e), + } + } + Err(last_err.unwrap_or_else(|| { + axerrno::ax_err_type!(InvalidInput, "could not resolve to any addresses") + })) +} diff --git a/ulib/axstd/src/net/socket_addr.rs b/ulib/axstd/src/net/socket_addr.rs new file mode 100644 index 0000000..56f7e0b --- /dev/null +++ b/ulib/axstd/src/net/socket_addr.rs @@ -0,0 +1,190 @@ +extern crate alloc; + +use crate::io; +use alloc::string::String; +use core::{iter, option, slice}; + +pub use core::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; + +/// A trait for objects which can be converted or resolved to one or more +/// [`SocketAddr`] values. +/// +/// This trait is used for generic address resolution when constructing network +/// objects. By default it is implemented for the following types: +/// +/// * [`SocketAddr`]: [`to_socket_addrs`] is the identity function. +/// +/// * [`SocketAddrV4`], ([IpAddr], [u16]), +/// ([Ipv4Addr], [u16]): +/// [`to_socket_addrs`] constructs a [`SocketAddr`] trivially. +/// +/// * (&[str], [u16]): &[str] should be either a string representation +/// of an [`IpAddr`] address as expected by [`FromStr`] implementation or a host +/// name. [`u16`] is the port number. +/// +/// * &[str]: the string should be either a string representation of a +/// [`SocketAddr`] as expected by its [`FromStr`] implementation or a string like +/// `:` pair where `` is a [`u16`] value. +/// +/// [`FromStr`]: core::str::FromStr +/// [`to_socket_addrs`]: ToSocketAddrs::to_socket_addrs +pub trait ToSocketAddrs { + /// Returned iterator over socket addresses which this type may correspond to. + type Iter: Iterator; + + /// Converts this object to an iterator of resolved [`SocketAddr`]s. + fn to_socket_addrs(&self) -> io::Result; +} + +impl ToSocketAddrs for SocketAddr { + type Iter = option::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + Ok(Some(*self).into_iter()) + } +} + +impl ToSocketAddrs for SocketAddrV4 { + type Iter = option::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + SocketAddr::V4(*self).to_socket_addrs() + } +} + +impl ToSocketAddrs for (IpAddr, u16) { + type Iter = option::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + let (ip, port) = *self; + SocketAddr::new(ip, port).to_socket_addrs() + } +} + +impl ToSocketAddrs for (Ipv4Addr, u16) { + type Iter = option::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + let (ip, port) = *self; + SocketAddrV4::new(ip, port).to_socket_addrs() + } +} + +impl<'a> ToSocketAddrs for &'a [SocketAddr] { + type Iter = iter::Cloned>; + + fn to_socket_addrs(&self) -> io::Result { + Ok(self.iter().cloned()) + } +} + +impl ToSocketAddrs for &T { + type Iter = T::Iter; + fn to_socket_addrs(&self) -> io::Result { + (**self).to_socket_addrs() + } +} + +#[cfg(not(feature = "dns"))] +#[doc(cfg(feature = "net"))] +mod no_dns { + use super::*; + + impl ToSocketAddrs for (&str, u16) { + type Iter = option::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + let (host, port) = *self; + Ok(host + .parse::() + .ok() + .map(|addr| { + let addr = SocketAddrV4::new(addr, port); + SocketAddr::V4(addr) + }) + .into_iter()) + } + } + + impl ToSocketAddrs for str { + type Iter = option::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + // parse as a regular SocketAddr first + Ok(self.parse().ok().into_iter()) + } + } + + impl ToSocketAddrs for (String, u16) { + type Iter = option::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + (&*self.0, self.1).to_socket_addrs() + } + } + + impl ToSocketAddrs for String { + type Iter = option::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + (**self).to_socket_addrs() + } + } +} + +#[cfg(feature = "dns")] +#[doc(cfg(feature = "net"))] +mod dns { + use super::*; + use alloc::{vec, vec::Vec}; + + impl ToSocketAddrs for (&str, u16) { + type Iter = vec::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + let (host, port) = *self; + + // try to parse the host as a regular IP address first + if let Ok(addr) = host.parse::() { + let addr = SocketAddrV4::new(addr, port); + return Ok(vec![SocketAddr::V4(addr)].into_iter()); + } + + Ok(arceos_api::net::ax_dns_query(host)? + .into_iter() + .map(|ip| SocketAddr::new(ip, port)) + .collect::>() + .into_iter()) + } + } + + impl ToSocketAddrs for str { + type Iter = vec::IntoIter; + + fn to_socket_addrs(&self) -> io::Result> { + // try to parse as a regular SocketAddr first + if let Ok(addr) = self.parse() { + return Ok(vec![addr].into_iter()); + } + + // split the string by ':' and convert the second part to u16 + let (host, port_str) = self + .rsplit_once(':') + .ok_or_else(|| axerrno::ax_err_type!(InvalidInput, "invalid socket address"))?; + let port: u16 = port_str + .parse() + .map_err(|_| axerrno::ax_err_type!(InvalidInput, "invalid port value"))?; + + Ok(arceos_api::net::ax_dns_query(host)? + .into_iter() + .map(|ip| SocketAddr::new(ip, port)) + .collect::>() + .into_iter()) + } + } + + impl ToSocketAddrs for (String, u16) { + type Iter = vec::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + (&*self.0, self.1).to_socket_addrs() + } + } + + impl ToSocketAddrs for String { + type Iter = vec::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + (**self).to_socket_addrs() + } + } +} diff --git a/ulib/axstd/src/net/tcp.rs b/ulib/axstd/src/net/tcp.rs new file mode 100644 index 0000000..f527f14 --- /dev/null +++ b/ulib/axstd/src/net/tcp.rs @@ -0,0 +1,105 @@ +use super::{SocketAddr, ToSocketAddrs}; +use crate::io::{self, prelude::*}; + +use arceos_api::net::{self as api, AxTcpSocketHandle}; + +/// A TCP stream between a local and a remote socket. +pub struct TcpStream(AxTcpSocketHandle); + +/// A TCP socket server, listening for connections. +pub struct TcpListener(AxTcpSocketHandle); + +impl TcpStream { + /// Opens a TCP connection to a remote host. + /// + /// `addr` is an address of the remote host. Anything which implements + /// [`ToSocketAddrs`] trait can be supplied for the address; see this trait + /// documentation for concrete examples. + /// + /// If `addr` yields multiple addresses, `connect` will be attempted with + /// each of the addresses until a connection is successful. If none of + /// the addresses result in a successful connection, the error returned from + /// the last connection attempt (the last address) is returned. + pub fn connect(addr: A) -> io::Result { + super::each_addr(addr, |addr: io::Result<&SocketAddr>| { + let addr = addr?; + let socket = api::ax_tcp_socket(); + api::ax_tcp_connect(&socket, *addr)?; + Ok(TcpStream(socket)) + }) + } + + /// Returns the socket address of the local half of this TCP connection. + pub fn local_addr(&self) -> io::Result { + api::ax_tcp_socket_addr(&self.0) + } + + /// Returns the socket address of the remote peer of this TCP connection. + pub fn peer_addr(&self) -> io::Result { + api::ax_tcp_peer_addr(&self.0) + } + + /// Shuts down the connection. + pub fn shutdown(&self) -> io::Result<()> { + api::ax_tcp_shutdown(&self.0) + } +} + +impl Read for TcpStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + api::ax_tcp_recv(&self.0, buf) + } +} + +impl Write for TcpStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + api::ax_tcp_send(&self.0, buf) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl TcpListener { + /// Creates a new `TcpListener` which will be bound to the specified + /// address. + /// + /// The returned listener is ready for accepting connections. + /// + /// Binding with a port number of 0 will request that the OS assigns a port + /// to this listener. The port allocated can be queried via the + /// [`TcpListener::local_addr`] method. + /// + /// The address type can be any implementor of [`ToSocketAddrs`] trait. See + /// its documentation for concrete examples. + /// + /// If `addr` yields multiple addresses, `bind` will be attempted with + /// each of the addresses until one succeeds and returns the listener. If + /// none of the addresses succeed in creating a listener, the error returned + /// from the last attempt (the last address) is returned. + pub fn bind(addr: A) -> io::Result { + super::each_addr(addr, |addr: io::Result<&SocketAddr>| { + let addr = addr?; + let backlog = 128; + let socket = api::ax_tcp_socket(); + api::ax_tcp_bind(&socket, *addr)?; + api::ax_tcp_listen(&socket, backlog)?; + Ok(TcpListener(socket)) + }) + } + + /// Returns the local socket address of this listener. + pub fn local_addr(&self) -> io::Result { + api::ax_tcp_socket_addr(&self.0) + } + + /// Accept a new incoming connection from this listener. + /// + /// This function will block the calling thread until a new TCP connection + /// is established. When established, the corresponding [`TcpStream`] and the + /// remote peer's address will be returned. + pub fn accept(&self) -> io::Result<(TcpStream, SocketAddr)> { + api::ax_tcp_accept(&self.0).map(|(a, b)| (TcpStream(a), b)) + } +} diff --git a/ulib/axstd/src/net/udp.rs b/ulib/axstd/src/net/udp.rs new file mode 100644 index 0000000..65df9e8 --- /dev/null +++ b/ulib/axstd/src/net/udp.rs @@ -0,0 +1,96 @@ +use super::{SocketAddr, ToSocketAddrs}; +use crate::io; + +use arceos_api::net::{self as api, AxUdpSocketHandle}; + +/// A UDP socket. +pub struct UdpSocket(AxUdpSocketHandle); + +impl UdpSocket { + /// Creates a UDP socket from the given address. + /// + /// The address type can be any implementor of [`ToSocketAddrs`] trait. See + /// its documentation for concrete examples. + /// + /// If `addr` yields multiple addresses, `bind` will be attempted with + /// each of the addresses until one succeeds and returns the socket. If none + /// of the addresses succeed in creating a socket, the error returned from + /// the last attempt (the last address) is returned. + pub fn bind(addr: A) -> io::Result { + super::each_addr(addr, |addr: io::Result<&SocketAddr>| { + let addr = addr?; + let socket = api::ax_udp_socket(); + api::ax_udp_bind(&socket, *addr)?; + Ok(UdpSocket(socket)) + }) + } + + /// Returns the socket address that this socket was created from. + pub fn local_addr(&self) -> io::Result { + api::ax_udp_socket_addr(&self.0) + } + + /// Returns the socket address of the remote peer this socket was connected to. + pub fn peer_addr(&self) -> io::Result { + api::ax_udp_peer_addr(&self.0) + } + + /// Receives a single datagram message on the socket. On success, returns + /// the number of bytes read and the origin. + pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + api::ax_udp_recv_from(&self.0, buf) + } + + /// Receives a single datagram message on the socket, without removing it from + /// the queue. On success, returns the number of bytes read and the origin. + pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + api::ax_udp_peek_from(&self.0, buf) + } + + /// Sends data on the socket to the given address. On success, returns the + /// number of bytes written. + /// + /// Address type can be any implementor of [`ToSocketAddrs`] trait. See its + /// documentation for concrete examples. + /// + /// It is possible for `addr` to yield multiple addresses, but `send_to` + /// will only send data to the first address yielded by `addr`. + pub fn send_to(&self, buf: &[u8], addr: A) -> io::Result { + match addr.to_socket_addrs()?.next() { + Some(addr) => api::ax_udp_send_to(&self.0, buf, addr), + None => axerrno::ax_err!(InvalidInput, "no addresses to send data to"), + } + } + + /// Connects this UDP socket to a remote address, allowing the `send` and + /// `recv` syscalls to be used to send data and also applies filters to only + /// receive data from the specified address. + /// + /// If `addr` yields multiple addresses, `connect` will be attempted with + /// each of the addresses until the underlying OS function returns no + /// error. Note that usually, a successful `connect` call does not specify + /// that there is a remote server listening on the port, rather, such an + /// error would only be detected after the first send. If the OS returns an + /// error for each of the specified addresses, the error returned from the + /// last connection attempt (the last address) is returned. + pub fn connect(&self, addr: SocketAddr) -> io::Result<()> { + super::each_addr(addr, |addr: io::Result<&SocketAddr>| { + let addr = addr?; + api::ax_udp_connect(&self.0, *addr) + }) + } + + /// Sends data on the socket to the remote address to which it is connected. + /// + /// [`UdpSocket::connect`] will connect this socket to a remote address. This + /// method will fail if the socket is not connected. + pub fn send(&self, buf: &[u8]) -> io::Result { + api::ax_udp_send(&self.0, buf) + } + + /// Receives a single datagram message on the socket from the remote address to + /// which it is connected. On success, returns the number of bytes read. + pub fn recv(&self, buf: &mut [u8]) -> io::Result { + api::ax_udp_recv(&self.0, buf) + } +} diff --git a/ulib/axstd/src/os.rs b/ulib/axstd/src/os.rs new file mode 100644 index 0000000..d1bdf82 --- /dev/null +++ b/ulib/axstd/src/os.rs @@ -0,0 +1,6 @@ +//! OS-specific functionality. + +/// ArceOS-specific definitions. +pub mod arceos { + pub use arceos_api as api; +} diff --git a/ulib/axstd/src/process.rs b/ulib/axstd/src/process.rs new file mode 100644 index 0000000..28349d2 --- /dev/null +++ b/ulib/axstd/src/process.rs @@ -0,0 +1,10 @@ +//! A module for working with processes. +//! +//! Since ArceOS is a unikernel, there is no concept of processes. The +//! process-related functions will affect the entire system, such as [`exit`] +//! will shutdown the whole system. + +/// Shutdown the whole system. +pub fn exit(_exit_code: i32) -> ! { + arceos_api::sys::ax_terminate(); +} diff --git a/ulib/axstd/src/sync/mod.rs b/ulib/axstd/src/sync/mod.rs new file mode 100644 index 0000000..75f003f --- /dev/null +++ b/ulib/axstd/src/sync/mod.rs @@ -0,0 +1,19 @@ +//! Useful synchronization primitives. + +#[doc(no_inline)] +pub use core::sync::atomic; + +#[cfg(feature = "alloc")] +#[doc(no_inline)] +pub use alloc::sync::{Arc, Weak}; + +#[cfg(feature = "multitask")] +mod mutex; + +#[cfg(feature = "multitask")] +#[doc(cfg(feature = "multitask"))] +pub use self::mutex::{Mutex, MutexGuard}; + +#[cfg(not(feature = "multitask"))] +#[doc(cfg(not(feature = "multitask")))] +pub use spinlock::{SpinRaw as Mutex, SpinRawGuard as MutexGuard}; // never used in IRQ context diff --git a/ulib/axstd/src/sync/mutex.rs b/ulib/axstd/src/sync/mutex.rs new file mode 100644 index 0000000..727324b --- /dev/null +++ b/ulib/axstd/src/sync/mutex.rs @@ -0,0 +1,198 @@ +//! A naïve sleeping mutex. + +use core::cell::UnsafeCell; +use core::fmt; +use core::ops::{Deref, DerefMut}; +use core::sync::atomic::{AtomicU64, Ordering}; + +use arceos_api::task::{self as api, AxWaitQueueHandle}; + +/// A mutual exclusion primitive useful for protecting shared data, similar to +/// [`std::sync::Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html). +/// +/// When the mutex is locked, the current task will block and be put into the +/// wait queue. When the mutex is unlocked, all tasks waiting on the queue +/// will be woken up. +pub struct Mutex { + wq: AxWaitQueueHandle, + owner_id: AtomicU64, + data: UnsafeCell, +} + +/// A guard that provides mutable data access. +/// +/// When the guard falls out of scope it will release the lock. +pub struct MutexGuard<'a, T: ?Sized + 'a> { + lock: &'a Mutex, + data: *mut T, +} + +// Same unsafe impls as `std::sync::Mutex` +unsafe impl Sync for Mutex {} +unsafe impl Send for Mutex {} + +impl Mutex { + /// Creates a new [`Mutex`] wrapping the supplied data. + #[inline(always)] + pub const fn new(data: T) -> Self { + Self { + wq: AxWaitQueueHandle::new(), + owner_id: AtomicU64::new(0), + data: UnsafeCell::new(data), + } + } + + /// Consumes this [`Mutex`] and unwraps the underlying data. + #[inline(always)] + pub fn into_inner(self) -> T { + // We know statically that there are no outstanding references to + // `self` so there's no need to lock. + let Mutex { data, .. } = self; + data.into_inner() + } +} + +impl Mutex { + /// Returns `true` if the lock is currently held. + /// + /// # Safety + /// + /// This function provides no synchronization guarantees and so its result should be considered 'out of date' + /// the instant it is called. Do not use it for synchronization purposes. However, it may be useful as a heuristic. + #[inline(always)] + pub fn is_locked(&self) -> bool { + self.owner_id.load(Ordering::Relaxed) != 0 + } + + /// Locks the [`Mutex`] and returns a guard that permits access to the inner data. + /// + /// The returned value may be dereferenced for data access + /// and the lock will be dropped when the guard falls out of scope. + pub fn lock(&self) -> MutexGuard { + let current_id = api::ax_current_task_id(); + loop { + // Can fail to lock even if the spinlock is not locked. May be more efficient than `try_lock` + // when called in a loop. + match self.owner_id.compare_exchange_weak( + 0, + current_id, + Ordering::Acquire, + Ordering::Relaxed, + ) { + Ok(_) => break, + Err(owner_id) => { + assert_ne!( + owner_id, current_id, + "Thread({}) tried to acquire mutex it already owns.", + current_id, + ); + // Wait until the lock looks unlocked before retrying + api::ax_wait_queue_wait(&self.wq, || !self.is_locked(), None); + } + } + } + MutexGuard { + lock: self, + data: unsafe { &mut *self.data.get() }, + } + } + + /// Try to lock this [`Mutex`], returning a lock guard if successful. + #[inline(always)] + pub fn try_lock(&self) -> Option> { + let current_id = api::ax_current_task_id(); + // The reason for using a strong compare_exchange is explained here: + // https://github.com/Amanieu/parking_lot/pull/207#issuecomment-575869107 + if self + .owner_id + .compare_exchange(0, current_id, Ordering::Acquire, Ordering::Relaxed) + .is_ok() + { + Some(MutexGuard { + lock: self, + data: unsafe { &mut *self.data.get() }, + }) + } else { + None + } + } + + /// Force unlock the [`Mutex`]. + /// + /// # Safety + /// + /// This is *extremely* unsafe if the lock is not held by the current + /// thread. However, this can be useful in some instances for exposing + /// the lock to FFI that doesn’t know how to deal with RAII. + pub unsafe fn force_unlock(&self) { + let owner_id = self.owner_id.swap(0, Ordering::Release); + let current_id = api::ax_current_task_id(); + assert_eq!( + owner_id, current_id, + "Thread({}) tried to release mutex it doesn't own", + current_id, + ); + // wake up one waiting thread. + api::ax_wait_queue_wake(&self.wq, 1); + } + + /// Returns a mutable reference to the underlying data. + /// + /// Since this call borrows the [`Mutex`] mutably, and a mutable reference is guaranteed to be exclusive in + /// Rust, no actual locking needs to take place -- the mutable borrow statically guarantees no locks exist. As + /// such, this is a 'zero-cost' operation. + #[inline(always)] + pub fn get_mut(&mut self) -> &mut T { + // We know statically that there are no other references to `self`, so + // there's no need to lock the inner mutex. + unsafe { &mut *self.data.get() } + } +} + +impl Default for Mutex { + #[inline(always)] + fn default() -> Self { + Self::new(Default::default()) + } +} + +impl fmt::Debug for Mutex { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.try_lock() { + Some(guard) => write!(f, "Mutex {{ data: ") + .and_then(|()| (*guard).fmt(f)) + .and_then(|()| write!(f, "}}")), + None => write!(f, "Mutex {{ }}"), + } + } +} + +impl<'a, T: ?Sized> Deref for MutexGuard<'a, T> { + type Target = T; + #[inline(always)] + fn deref(&self) -> &T { + // We know statically that only we are referencing data + unsafe { &*self.data } + } +} + +impl<'a, T: ?Sized> DerefMut for MutexGuard<'a, T> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut T { + // We know statically that only we are referencing data + unsafe { &mut *self.data } + } +} + +impl<'a, T: ?Sized + fmt::Debug> fmt::Debug for MutexGuard<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl<'a, T: ?Sized> Drop for MutexGuard<'a, T> { + /// The dropping of the [`MutexGuard`] will release the lock it was created from. + fn drop(&mut self) { + unsafe { self.lock.force_unlock() } + } +} diff --git a/ulib/axstd/src/thread/mod.rs b/ulib/axstd/src/thread/mod.rs new file mode 100644 index 0000000..452d2cc --- /dev/null +++ b/ulib/axstd/src/thread/mod.rs @@ -0,0 +1,41 @@ +//! Native threads. + +#[cfg(feature = "multitask")] +mod multi; +#[cfg(feature = "multitask")] +pub use multi::*; + +use arceos_api::task as api; + +/// Current thread gives up the CPU time voluntarily, and switches to another +/// ready thread. +/// +/// For single-threaded configuration (`multitask` feature is disabled), we just +/// relax the CPU and wait for incoming interrupts. +pub fn yield_now() { + api::ax_yield_now(); +} + +/// Exits the current thread. +/// +/// For single-threaded configuration (`multitask` feature is disabled), +/// it directly terminates the main thread and shutdown. +pub fn exit(exit_code: i32) -> ! { + api::ax_exit(exit_code); +} + +/// Current thread is going to sleep for the given duration. +/// +/// If one of `multitask` or `irq` features is not enabled, it uses busy-wait +/// instead. +pub fn sleep(dur: core::time::Duration) { + sleep_until(arceos_api::time::ax_current_time() + dur); +} + +/// Current thread is going to sleep, it will be woken up at the given deadline. +/// +/// If one of `multitask` or `irq` features is not enabled, it uses busy-wait +/// instead. +pub fn sleep_until(deadline: arceos_api::time::AxTimeValue) { + api::ax_sleep_until(deadline); +} diff --git a/ulib/axstd/src/thread/multi.rs b/ulib/axstd/src/thread/multi.rs new file mode 100644 index 0000000..5ac8c3e --- /dev/null +++ b/ulib/axstd/src/thread/multi.rs @@ -0,0 +1,189 @@ +//! Thread APIs for multi-threading configuration. + +extern crate alloc; + +use crate::io; +use alloc::{string::String, sync::Arc}; +use core::{cell::UnsafeCell, num::NonZeroU64}; + +use arceos_api::task::{self as api, AxTaskHandle}; +use axerrno::ax_err_type; + +/// A unique identifier for a running thread. +#[derive(Eq, PartialEq, Clone, Copy, Debug)] +pub struct ThreadId(NonZeroU64); + +/// A handle to a thread. +pub struct Thread { + id: ThreadId, +} + +impl ThreadId { + /// This returns a numeric identifier for the thread identified by this + /// `ThreadId`. + pub fn as_u64(&self) -> NonZeroU64 { + self.0 + } +} + +impl Thread { + fn from_id(id: u64) -> Self { + Self { + id: ThreadId(NonZeroU64::new(id).unwrap()), + } + } + + /// Gets the thread's unique identifier. + pub fn id(&self) -> ThreadId { + self.id + } +} + +/// Thread factory, which can be used in order to configure the properties of +/// a new thread. +/// +/// Methods can be chained on it in order to configure it. +#[derive(Debug)] +pub struct Builder { + // A name for the thread-to-be, for identification in panic messages + name: Option, + // The size of the stack for the spawned thread in bytes + stack_size: Option, +} + +impl Builder { + /// Generates the base configuration for spawning a thread, from which + /// configuration methods can be chained. + pub const fn new() -> Builder { + Builder { + name: None, + stack_size: None, + } + } + + /// Names the thread-to-be. + pub fn name(mut self, name: String) -> Builder { + self.name = Some(name); + self + } + + /// Sets the size of the stack (in bytes) for the new thread. + pub fn stack_size(mut self, size: usize) -> Builder { + self.stack_size = Some(size); + self + } + + /// Spawns a new thread by taking ownership of the `Builder`, and returns an + /// [`io::Result`] to its [`JoinHandle`]. + /// + /// The spawned thread may outlive the caller (unless the caller thread + /// is the main thread; the whole process is terminated when the main + /// thread finishes). The join handle can be used to block on + /// termination of the spawned thread. + pub fn spawn(self, f: F) -> io::Result> + where + F: FnOnce() -> T, + F: Send + 'static, + T: Send + 'static, + { + unsafe { self.spawn_unchecked(f) } + } + + unsafe fn spawn_unchecked(self, f: F) -> io::Result> + where + F: FnOnce() -> T, + F: Send + 'static, + T: Send + 'static, + { + let name = self.name.unwrap_or_default(); + let stack_size = self + .stack_size + .unwrap_or(arceos_api::config::TASK_STACK_SIZE); + + let my_packet = Arc::new(Packet { + result: UnsafeCell::new(None), + }); + let their_packet = my_packet.clone(); + + let main = move || { + let ret = f(); + // SAFETY: `their_packet` as been built just above and moved by the + // closure (it is an Arc<...>) and `my_packet` will be stored in the + // same `JoinHandle` as this closure meaning the mutation will be + // safe (not modify it and affect a value far away). + unsafe { *their_packet.result.get() = Some(ret) }; + drop(their_packet); + }; + + let task = api::ax_spawn(main, name, stack_size); + Ok(JoinHandle { + thread: Thread::from_id(task.id()), + native: task, + packet: my_packet, + }) + } +} + +/// Gets a handle to the thread that invokes it. +pub fn current() -> Thread { + let id = api::ax_current_task_id(); + Thread::from_id(id) +} + +/// Spawns a new thread, returning a [`JoinHandle`] for it. +/// +/// The join handle provides a [`join`] method that can be used to join the +/// spawned thread. +/// +/// The default task name is an empty string. The default thread stack size is +/// [`arceos_api::config::TASK_STACK_SIZE`]. +/// +/// [`join`]: JoinHandle::join +pub fn spawn(f: F) -> JoinHandle +where + F: FnOnce() -> T + Send + 'static, + T: Send + 'static, +{ + Builder::new().spawn(f).expect("failed to spawn thread") +} + +struct Packet { + result: UnsafeCell>, +} + +unsafe impl Sync for Packet {} + +/// An owned permission to join on a thread (block on its termination). +/// +/// A `JoinHandle` *detaches* the associated thread when it is dropped, which +/// means that there is no longer any handle to the thread and no way to `join` +/// on it. +pub struct JoinHandle { + native: AxTaskHandle, + thread: Thread, + packet: Arc>, +} + +unsafe impl Send for JoinHandle {} +unsafe impl Sync for JoinHandle {} + +impl JoinHandle { + /// Extracts a handle to the underlying thread. + pub fn thread(&self) -> &Thread { + &self.thread + } + + /// Waits for the associated thread to finish. + /// + /// This function will return immediately if the associated thread has + /// already finished. + pub fn join(mut self) -> io::Result { + api::ax_wait_for_exit(self.native).ok_or_else(|| ax_err_type!(BadState))?; + Arc::get_mut(&mut self.packet) + .unwrap() + .result + .get_mut() + .take() + .ok_or_else(|| ax_err_type!(BadState)) + } +} diff --git a/ulib/axstd/src/time.rs b/ulib/axstd/src/time.rs new file mode 100644 index 0000000..3b6b592 --- /dev/null +++ b/ulib/axstd/src/time.rs @@ -0,0 +1,97 @@ +//! Temporal quantification. + +use arceos_api::time::AxTimeValue; +use core::ops::{Add, AddAssign, Sub, SubAssign}; + +pub use core::time::Duration; + +/// A measurement of a monotonically nondecreasing clock. +/// Opaque and useful only with [`Duration`]. +#[derive(Clone, Copy)] +pub struct Instant(AxTimeValue); + +impl Instant { + /// Returns an instant corresponding to "now". + pub fn now() -> Instant { + Instant(arceos_api::time::ax_current_time()) + } + + /// Returns the amount of time elapsed from another instant to this one, + /// or zero duration if that instant is later than this one. + /// + /// # Panics + /// + /// Previous rust versions panicked when `earlier` was later than `self`. Currently this + /// method saturates. Future versions may reintroduce the panic in some circumstances. + pub fn duration_since(&self, earlier: Instant) -> Duration { + self.0.checked_sub(earlier.0).unwrap_or_default() + } + + /// Returns the amount of time elapsed since this instant was created. + /// + /// # Panics + /// + /// Previous rust versions panicked when the current time was earlier than self. Currently this + /// method returns a Duration of zero in that case. Future versions may reintroduce the panic. + pub fn elapsed(&self) -> Duration { + Instant::now() - *self + } + + /// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be represented as + /// `Instant` (which means it's inside the bounds of the underlying data structure), `None` + /// otherwise. + pub fn checked_add(&self, duration: Duration) -> Option { + self.0.checked_add(duration).map(Instant) + } + + /// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as + /// `Instant` (which means it's inside the bounds of the underlying data structure), `None` + /// otherwise. + pub fn checked_sub(&self, duration: Duration) -> Option { + self.0.checked_sub(duration).map(Instant) + } +} + +impl Add for Instant { + type Output = Instant; + + /// # Panics + /// + /// This function may panic if the resulting point in time cannot be represented by the + /// underlying data structure. + fn add(self, other: Duration) -> Instant { + self.checked_add(other) + .expect("overflow when adding duration to instant") + } +} + +impl AddAssign for Instant { + fn add_assign(&mut self, other: Duration) { + *self = *self + other; + } +} + +impl Sub for Instant { + type Output = Instant; + + fn sub(self, other: Duration) -> Instant { + self.checked_sub(other) + .expect("overflow when subtracting duration from instant") + } +} + +impl SubAssign for Instant { + fn sub_assign(&mut self, other: Duration) { + *self = *self - other; + } +} + +impl Sub for Instant { + type Output = Duration; + + /// Returns the amount of time elapsed from another instant to this one, + /// or zero duration if that instant is later than this one. + fn sub(self, other: Instant) -> Duration { + self.duration_since(other) + } +}