From 573dc8335ed5707a942f62f738bdd74034b9c8ff Mon Sep 17 00:00:00 2001 From: Rob Blanckaert Date: Mon, 24 Mar 2025 10:48:38 -0700 Subject: [PATCH 001/301] More Github actions fixes --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 255d7c94..c7d5b20e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -146,7 +146,6 @@ jobs: uses: actions/download-artifact@v4 with: name: v86 - path: build - name: Display structure of downloaded files run: ls -R @@ -157,7 +156,7 @@ jobs: result-encoding: string script: | const fs = require("fs"); - const pkg = {...require("./build/package.json"), version: '0.' + String(process.env.GITHUB_RUN_NUMBER) + '.0'} + const pkg = {...require("./package.json"), version: '0.' + String(process.env.GITHUB_RUN_NUMBER) + '.0'} console.log(pkg); fs.writeFileSync("package.json", JSON.stringify(pkg, null, " "), "utf8"); @@ -171,5 +170,6 @@ jobs: artifacts: "build/libv86*.js,build/libv86*.js.map,build/*.mjs,build/v86*.wasm" prerelease: true - - run: mv build/Readme.md build/LICENSE . - run: npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} From 59ffdcb2ddf10a4305ecf34c468663923f15524d Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 25 Mar 2025 01:05:15 +0700 Subject: [PATCH 002/301] npm version number from git describe --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c7d5b20e..3ad8f305 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -156,7 +156,9 @@ jobs: result-encoding: string script: | const fs = require("fs"); - const pkg = {...require("./package.json"), version: '0.' + String(process.env.GITHUB_RUN_NUMBER) + '.0'} + const { execFileSync } = require("child_process") + const version = execFileSync("git", ["describe", "--tags"], { encoding: "utf8" }).trim().replace("-", "."); + const pkg = { ...require("./package.json"), version }; console.log(pkg); fs.writeFileSync("package.json", JSON.stringify(pkg, null, " "), "utf8"); From 4990c43930bb7bb0b4ec858b05bdbe213df9b6ce Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 25 Mar 2025 01:43:29 +0700 Subject: [PATCH 003/301] update package.json version earlier --- .github/workflows/ci.yml | 15 +++------------ Makefile | 3 +++ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ad8f305..b3fe19dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -115,6 +115,9 @@ jobs: - name: Run expect tests run: make expect-tests + - name: Update package.json version + run: make update-package-json-version + - name: Upload the artifact uses: actions/upload-artifact@v4 with: @@ -150,18 +153,6 @@ jobs: - name: Display structure of downloaded files run: ls -R - - name: Set package.json version - uses: actions/github-script@v7 - with: - result-encoding: string - script: | - const fs = require("fs"); - const { execFileSync } = require("child_process") - const version = execFileSync("git", ["describe", "--tags"], { encoding: "utf8" }).trim().replace("-", "."); - const pkg = { ...require("./package.json"), version }; - console.log(pkg); - fs.writeFileSync("package.json", JSON.stringify(pkg, null, " "), "utf8"); - - name: Release to GitHub uses: ncipollo/release-action@v1 with: diff --git a/Makefile b/Makefile index 00367671..35b4be0f 100644 --- a/Makefile +++ b/Makefile @@ -385,3 +385,6 @@ build/xterm.js: curl https://cdn.jsdelivr.net/npm/xterm@5.2.1/lib/xterm.min.js > build/xterm.js curl https://cdn.jsdelivr.net/npm/xterm@5.2.1/lib/xterm.js.map > build/xterm.js.map curl https://cdn.jsdelivr.net/npm/xterm@5.2.1/css/xterm.css > build/xterm.css + +update-package-json-version: + jq --arg version "$$(git describe --tags | sed 's/-/./' | tr - +)" '.version = $$version' package.json | sponge package.json From bf8a9a97245d6c33a4a2e6e4f964e2571334558f Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 25 Mar 2025 02:02:19 +0700 Subject: [PATCH 004/301] why no sponge in github actions --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 35b4be0f..8026d5da 100644 --- a/Makefile +++ b/Makefile @@ -387,4 +387,5 @@ build/xterm.js: curl https://cdn.jsdelivr.net/npm/xterm@5.2.1/css/xterm.css > build/xterm.css update-package-json-version: - jq --arg version "$$(git describe --tags | sed 's/-/./' | tr - +)" '.version = $$version' package.json | sponge package.json + jq --arg version "$$(git describe --tags | sed 's/-/./' | tr - +)" '.version = $$version' package.json > package.json.tmp + mv package.json.tmp package.json From 970de79d2b0d1a439b2e8c897d160d17ee09e757 Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 25 Mar 2025 02:43:58 +0700 Subject: [PATCH 005/301] fetch history so we can use git describe --tags later --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3fe19dc..c9668d13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,9 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + fetch-depth: 0 + filter: tree:0 - name: Setup Node.js version uses: actions/setup-node@v4 From 78835581acc65ba77a0aaeb0c7c407002cf42ca9 Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 25 Mar 2025 02:59:30 +0700 Subject: [PATCH 006/301] ignore 'latest' tag --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8026d5da..263da6d7 100644 --- a/Makefile +++ b/Makefile @@ -387,5 +387,6 @@ build/xterm.js: curl https://cdn.jsdelivr.net/npm/xterm@5.2.1/css/xterm.css > build/xterm.css update-package-json-version: - jq --arg version "$$(git describe --tags | sed 's/-/./' | tr - +)" '.version = $$version' package.json > package.json.tmp + git describe --tags --exclude latest | sed 's/-/./' | tr - + | tee build/version + jq --arg version "$$(cat build/version)" '.version = $$version' package.json > package.json.tmp mv package.json.tmp package.json From 62ca883ca288c2048d520f33e4863c6c38b52e2c Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 25 Mar 2025 12:20:46 +0700 Subject: [PATCH 007/301] NODE_AUTH_TOKEN -> NPM_TOKEN --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9668d13..db2f9cdb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -168,4 +168,4 @@ jobs: - run: npm publish --provenance --access public env: - NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} + NPM_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} From d24565479db396756da07519dfafd2906fa86860 Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 25 Mar 2025 12:47:39 +0700 Subject: [PATCH 008/301] setup-node is needed for npm publish --- .github/workflows/ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index db2f9cdb..9f7e2db4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -156,6 +156,11 @@ jobs: - name: Display structure of downloaded files run: ls -R + - uses: actions/setup-node@v4 + with: + node-version: '20.x' + registry-url: 'https://registry.npmjs.org' + - name: Release to GitHub uses: ncipollo/release-action@v1 with: @@ -168,4 +173,4 @@ jobs: - run: npm publish --provenance --access public env: - NPM_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} From 1f9a5fbc68149cb93483f6a78e9afd0c421c0a34 Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 25 Mar 2025 13:19:59 +0700 Subject: [PATCH 009/301] ci --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f7e2db4..602a006f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -140,6 +140,9 @@ jobs: needs: test if: github.ref == 'refs/heads/master' + permissions: + id-token: write + steps: - name: Delete old release and tag uses: dev-drprasad/delete-tag-and-release@v1.0.1 From ff56eed36ba8363d2b9bc72e453313e2d1b7ce42 Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 25 Mar 2025 16:07:15 +0700 Subject: [PATCH 010/301] ci 2 --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 602a006f..c78ba938 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -142,6 +142,7 @@ jobs: permissions: id-token: write + contents: write steps: - name: Delete old release and tag From 30be975d6aad42d55f60436d20b27353bcee669f Mon Sep 17 00:00:00 2001 From: Rob Blanckaert Date: Fri, 4 Apr 2025 17:51:29 -0700 Subject: [PATCH 011/301] Fix this.avail_last_idx in state images that are made for older v86 versions --- src/virtio.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/virtio.js b/src/virtio.js index 2cb6aa8a..e7bd6709 100644 --- a/src/virtio.js +++ b/src/virtio.js @@ -1071,6 +1071,7 @@ VirtQueue.prototype.get_state = function() state[6] = this.avail_last_idx; state[7] = this.used_addr; state[8] = this.num_staged_replies; + state[9] = 1; return state; }; @@ -1088,6 +1089,7 @@ VirtQueue.prototype.set_state = function(state) this.num_staged_replies = state[8]; this.mask = this.size - 1; + this.fix_wrapping = state[9] !== 1; }; VirtQueue.prototype.reset = function() @@ -1126,6 +1128,10 @@ VirtQueue.prototype.set_size = function(size) VirtQueue.prototype.count_requests = function() { dbg_assert(this.avail_addr, "VirtQueue addresses must be configured before use"); + if(this.fix_wrapping) { + this.fix_wrapping = false; + this.avail_last_idx = (this.avail_get_idx() & ~this.mask) + (this.avail_last_idx & this.mask); + } return (this.avail_get_idx() - this.avail_last_idx) & 0xFFFF; }; From a9219613af197281447815021657fcdc6c26f53b Mon Sep 17 00:00:00 2001 From: Rob Blanckaert Date: Tue, 18 Mar 2025 00:29:11 -0700 Subject: [PATCH 012/301] Switch to es6 modules. --- .github/workflows/ci.yml | 2 +- Makefile | 32 +++-- debug.html | 56 +------- eslint.config.mjs | 53 ++++++- examples/nodejs.js | 7 +- examples/nodejs_state.js | 6 +- examples/worker.js | 2 + gen/generate_analyzer.js | 15 +- gen/generate_interpreter.js | 15 +- gen/generate_jit.js | 16 ++- gen/rust_ast.js | 8 +- gen/util.js | 27 ++-- gen/x86_table.js | 5 +- lib/9p.js | 47 +++--- lib/filesystem.js | 39 +++-- lib/jor1k.js | 13 +- lib/marshall.js | 8 +- nodejs-loader.mjs | 98 +------------ package.json | 5 +- src/acpi.js | 12 +- src/apic.js | 18 ++- src/browser/dummy_screen.js | 4 +- src/browser/fake_network.js | 34 +++-- src/browser/fetch_network.js | 18 ++- src/browser/filestorage.js | 12 +- src/browser/inbrowser_network.js | 6 +- src/browser/keyboard.js | 5 +- src/browser/main.js | 44 ++++-- src/browser/mouse.js | 8 +- src/browser/network.js | 5 +- src/browser/print_stats.js | 8 +- src/browser/screen.js | 5 +- src/browser/serial.js | 9 +- src/browser/speaker.js | 15 +- src/browser/starter.js | 71 +++++++-- src/browser/wisp_network.js | 15 +- src/browser/worker_bus.js | 12 +- src/buffer.js | 50 +++---- src/bus.js | 6 +- src/cjs.js | 27 ++++ src/config.js | 31 ++-- src/const.js | 48 +++---- src/cpu.js | 167 ++++++++++++++-------- src/debug.js | 17 ++- src/dma.js | 9 +- src/elf.js | 5 +- src/floppy.js | 16 ++- src/ide.js | 11 +- src/io.js | 17 ++- src/ioapic.js | 55 ++++--- src/kernel.js | 6 +- src/lib.js | 158 ++++++++++---------- src/log.js | 24 ++-- src/main.js | 7 +- src/memory.js | 4 + src/ne2k.js | 13 +- src/pci.js | 12 +- src/pit.js | 13 +- src/ps2.js | 11 +- src/rtc.js | 95 ++++++------ src/sb16.js | 22 ++- src/state.js | 4 + src/uart.js | 10 +- src/vga.js | 17 ++- src/virtio.js | 22 ++- src/virtio_balloon.js | 11 +- src/virtio_console.js | 11 +- src/virtio_net.js | 10 +- tests/api/clean-shutdown.js | 6 +- tests/api/floppy-insert-eject.js | 9 +- tests/api/pic.js | 6 +- tests/api/reboot.js | 9 +- tests/api/reset.js | 7 +- tests/api/serial.js | 11 +- tests/api/state.js | 8 +- tests/api/test.js | 6 +- tests/benchmark/arch-bytemark.js | 7 +- tests/benchmark/arch-python.js | 7 +- tests/benchmark/linux-boot.js | 13 +- tests/benchmark/snapshot.js | 10 +- tests/devices/fetch_network.js | 8 +- tests/devices/virtio_9p.js | 8 +- tests/devices/virtio_balloon.js | 6 +- tests/devices/virtio_console.js | 8 +- tests/devices/wisp_network.js | 7 +- tests/expect/run.js | 20 ++- tests/full/run.js | 18 ++- tests/jit-paging/run.js | 7 +- tests/kvm-unit-tests/{run.js => run.mjs} | 9 +- tests/nasm/create_tests.js | 23 +-- tests/nasm/gen_fixtures.js | 13 +- tests/nasm/rand.js | 5 +- tests/nasm/run.js | 24 ++-- tests/qemu/run-qemu.js | 13 +- tests/qemu/run.js | 2 +- tests/rust/verify-wasmgen-dummy-output.js | 12 +- tools/docker/alpine/build-state.js | 9 +- 97 files changed, 1206 insertions(+), 749 deletions(-) create mode 100644 src/cjs.js rename tests/kvm-unit-tests/{run.js => run.mjs} (83%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c78ba938..c83b8aad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,7 +68,7 @@ jobs: run: (cd tests/kvm-unit-tests && ./configure && make x86/realmode.flat) - name: Run kvm-unit-test - run: tests/kvm-unit-tests/run.js tests/kvm-unit-tests/x86/realmode.flat + run: tests/kvm-unit-tests/run.mjs tests/kvm-unit-tests/x86/realmode.flat - name: Fetch namsmtests cache uses: actions/cache@v4 diff --git a/Makefile b/Makefile index 263da6d7..74d887f3 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,7 @@ CARGO_FLAGS_SAFE=\ CARGO_FLAGS=$(CARGO_FLAGS_SAFE) -C target-feature=+bulk-memory -C target-feature=+multivalue -C target-feature=+simd128 -CORE_FILES=const.js config.js io.js main.js lib.js buffer.js ide.js pci.js floppy.js \ +CORE_FILES=cjs.js const.js config.js io.js main.js lib.js buffer.js ide.js pci.js floppy.js \ memory.js dma.js pit.js vga.js ps2.js rtc.js uart.js \ acpi.js apic.js ioapic.js \ state.js ne2k.js sb16.js virtio.js virtio_console.js virtio_net.js virtio_balloon.js \ @@ -151,7 +151,7 @@ build/libv86.mjs: $(CLOSURE) src/*.js lib/*.js src/browser/*.js $(CLOSURE_FLAGS)\ --compilation_level SIMPLE\ --jscomp_off=missingProperties\ - --output_wrapper ';let module = {exports:{}}; %output%; export default module.exports.V86;'\ + --output_wrapper ';let module = {exports:{}}; %output%; export default module.exports.V86; export let {V86, CPU} = module.exports;'\ --js $(CORE_FILES)\ --js $(BROWSER_FILES)\ --js $(LIB_FILES)\ @@ -257,6 +257,7 @@ clean: -rm build/libv86.js -rm build/libv86.mjs -rm build/libv86-debug.js + -rm build/libv86-debug.mjs -rm build/v86_all.js -rm build/v86.wasm -rm build/v86-debug.wasm @@ -299,45 +300,45 @@ tests: build/libv86-debug.js build/v86-debug.wasm build/integration-test-fs/fs.j tests-release: build/libv86.js build/v86.wasm build/integration-test-fs/fs.json TEST_RELEASE_BUILD=1 ./tests/full/run.js -nasmtests: build/libv86-debug.js build/v86-debug.wasm +nasmtests: build/libv86-debug.mjs build/v86-debug.wasm $(NASM_TEST_DIR)/create_tests.js $(NASM_TEST_DIR)/gen_fixtures.js $(NASM_TEST_DIR)/run.js -nasmtests-force-jit: build/libv86-debug.js build/v86-debug.wasm +nasmtests-force-jit: build/libv86-debug.mjs build/v86-debug.wasm $(NASM_TEST_DIR)/create_tests.js $(NASM_TEST_DIR)/gen_fixtures.js $(NASM_TEST_DIR)/run.js --force-jit -jitpagingtests: build/libv86-debug.js build/v86-debug.wasm +jitpagingtests: build/libv86-debug.mjs build/v86-debug.wasm $(MAKE) -C tests/jit-paging test-jit ./tests/jit-paging/run.js -qemutests: build/libv86-debug.js build/v86-debug.wasm +qemutests: build/libv86-debug.mjs build/v86-debug.wasm $(MAKE) -C tests/qemu test-i386 LOG_LEVEL=3 ./tests/qemu/run.js build/qemu-test-result ./tests/qemu/run-qemu.js > build/qemu-test-reference diff build/qemu-test-result build/qemu-test-reference -qemutests-release: build/libv86.js build/v86.wasm +qemutests-release: build/libv86.mjs build/v86.wasm $(MAKE) -C tests/qemu test-i386 TEST_RELEASE_BUILD=1 time ./tests/qemu/run.js build/qemu-test-result ./tests/qemu/run-qemu.js > build/qemu-test-reference diff build/qemu-test-result build/qemu-test-reference -kvm-unit-test: build/libv86-debug.js build/v86-debug.wasm +kvm-unit-test: build/libv86-debug.mjs build/v86-debug.wasm (cd tests/kvm-unit-tests && ./configure && make x86/realmode.flat) - tests/kvm-unit-tests/run.js tests/kvm-unit-tests/x86/realmode.flat + tests/kvm-unit-tests/run.mjs tests/kvm-unit-tests/x86/realmode.flat -kvm-unit-test-release: build/libv86.js build/v86.wasm +kvm-unit-test-release: build/libv86.mjs build/v86.wasm (cd tests/kvm-unit-tests && ./configure && make x86/realmode.flat) - TEST_RELEASE_BUILD=1 tests/kvm-unit-tests/run.js tests/kvm-unit-tests/x86/realmode.flat + TEST_RELEASE_BUILD=1 tests/kvm-unit-tests/run.mjs tests/kvm-unit-tests/x86/realmode.flat -expect-tests: build/libv86-debug.js build/v86-debug.wasm build/libwabt.js +expect-tests: build/libv86-debug.mjs build/v86-debug.wasm build/libwabt.cjs make -C tests/expect/tests ./tests/expect/run.js -devices-test: build/libv86-debug.js build/v86-debug.wasm +devices-test: build/libv86-debug.mjs build/v86-debug.wasm ./tests/devices/virtio_9p.js ./tests/devices/virtio_console.js ./tests/devices/fetch_network.js @@ -352,7 +353,7 @@ rust-test: $(RUST_FILES) rust-test-intensive: QUICKCHECK_TESTS=100000000 make rust-test -api-tests: build/libv86-debug.js build/v86-debug.wasm +api-tests: build/libv86-debug.mjs build/v86-debug.wasm ./tests/api/clean-shutdown.js ./tests/api/state.js ./tests/api/reset.js @@ -375,10 +376,11 @@ build/capstone-x86.min.js: mkdir -p build wget -nv -P build https://github.com/AlexAltea/capstone.js/releases/download/v3.0.5-rc1/capstone-x86.min.js -build/libwabt.js: +build/libwabt.cjs: mkdir -p build wget -nv -P build https://github.com/WebAssembly/wabt/archive/1.0.6.zip unzip -j -d build/ build/1.0.6.zip wabt-1.0.6/demo/libwabt.js + mv build/libwabt.js build/libwabt.cjs rm build/1.0.6.zip build/xterm.js: diff --git a/debug.html b/debug.html index 88105a4a..e752317e 100644 --- a/debug.html +++ b/debug.html @@ -4,58 +4,12 @@ v86 (debug) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + diff --git a/eslint.config.mjs b/eslint.config.mjs index 7f21000a..47f86752 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,5 +1,56 @@ + export default [ { + "languageOptions": { + "globals": { + "process": "readonly", + "window": "writable", + "navigator": "writable", + "location": "writable", + "document": "readonly", + "console": "readonly", + "crypto": "readonly", + "alert": "readonly", + "performance": "readonly", + "URL": "readonly", + "WebAssembly": "readonly", + + "setTimeout": "readonly", + "setInterval": "readonly", + "clearTimeout": "readonly", + "clearInterval": "readonly", + "requestAnimationFrame": "readonly", + "cancelAnimationFrame": "readonly", + + "Buffer": "readonly", + "FileReader": "readonly", + "TextEncoder": "readonly", + "TextDecoder": "readonly", + "fetch": "readonly", + "Headers": "readonly", + "Response": "readonly", + "WebSocket": "readonly", + "Blob": "readonly", + "Blob": "readonly", + "File": "readonly", + "XMLHttpRequest": "readonly", + "URLSearchParams": "readonly", + "ImageData": "readonly", + "Image": "readonly", + "OffscreenCanvas": "readonly", + "BroadcastChannel": "readonly", + + "AudioContext": "readonly", + "AudioWorkletProcessor": "readonly", + "webkitAudioContext": "readonly", + "AudioWorkletNode": "readonly", + "Worker": "readonly", + "postMessage": "readonly", + "importScripts": "readonly", + + "DEBUG": "writable" + } + }, rules: { "eol-last": "error", //"no-extra-parens": "error", @@ -69,7 +120,7 @@ export default [ "no-shadow-restricted-names": "error", "no-sparse-arrays": "error", "no-this-before-super": "error", - //"no-undef": "error", + "no-undef": "error", "no-unexpected-multiline": "error", //"no-unreachable": "error", "no-unsafe-finally": "error", diff --git a/examples/nodejs.js b/examples/nodejs.js index c2ec8246..98b4fd8b 100755 --- a/examples/nodejs.js +++ b/examples/nodejs.js @@ -1,8 +1,11 @@ #!/usr/bin/env node "use strict"; -var fs = require("fs"); -var V86 = require("../build/libv86.js").V86; +import fs from "node:fs"; +import url from "node:url"; +import { V86 } from "../build/libv86.js"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); function readfile(path) { diff --git a/examples/nodejs_state.js b/examples/nodejs_state.js index 229f2c14..63f5df25 100755 --- a/examples/nodejs_state.js +++ b/examples/nodejs_state.js @@ -1,8 +1,10 @@ #!/usr/bin/env node "use strict"; -var fs = require("fs"); -var V86 = require("../build/libv86.js").V86; +import fs from "node:fs"; +import url from "node:url"; +var V86 = await import("../build/libv86.js").V86; +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); function readfile(path) { diff --git a/examples/worker.js b/examples/worker.js index 90760064..b438d49e 100644 --- a/examples/worker.js +++ b/examples/worker.js @@ -1,5 +1,7 @@ importScripts("../build/libv86.js"); +/* global V86 */ + var emulator = new V86({ wasm_path: "../build/v86.wasm", memory_size: 32 * 1024 * 1024, diff --git a/gen/generate_analyzer.js b/gen/generate_analyzer.js index de31a8c3..103e2a31 100755 --- a/gen/generate_analyzer.js +++ b/gen/generate_analyzer.js @@ -1,13 +1,16 @@ #!/usr/bin/env node "use strict"; -const assert = require("assert").strict; -const fs = require("fs"); -const path = require("path"); -const x86_table = require("./x86_table"); -const rust_ast = require("./rust_ast"); -const { hex, mkdirpSync, get_switch_value, get_switch_exist, finalize_table_rust } = require("./util"); +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import url from "node:url"; +import x86_table from "./x86_table.js"; +const rust_ast = await import("./rust_ast.js"); +const { hex, mkdirpSync, get_switch_value, get_switch_exist, finalize_table_rust } = await import("./util.js"); + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const OUT_DIR = path.join(__dirname, "..", "src/rust/gen/"); mkdirpSync(OUT_DIR); diff --git a/gen/generate_interpreter.js b/gen/generate_interpreter.js index 8c09eddf..8015d0da 100755 --- a/gen/generate_interpreter.js +++ b/gen/generate_interpreter.js @@ -1,13 +1,16 @@ #!/usr/bin/env node "use strict"; -const assert = require("assert").strict; -const fs = require("fs"); -const path = require("path"); -const x86_table = require("./x86_table"); -const rust_ast = require("./rust_ast"); -const { hex, mkdirpSync, get_switch_value, get_switch_exist, finalize_table_rust } = require("./util"); +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import url from "node:url"; +import x86_table from "./x86_table.js"; +const rust_ast = await import("./rust_ast.js"); +const { hex, mkdirpSync, get_switch_value, get_switch_exist, finalize_table_rust } = await import("./util.js"); + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const OUT_DIR = path.join(__dirname, "..", "src/rust/gen/"); mkdirpSync(OUT_DIR); diff --git a/gen/generate_jit.js b/gen/generate_jit.js index 104f0598..b237a368 100755 --- a/gen/generate_jit.js +++ b/gen/generate_jit.js @@ -1,15 +1,17 @@ #!/usr/bin/env node "use strict"; -const assert = require("assert").strict; -const fs = require("fs"); -const path = require("path"); -const x86_table = require("./x86_table"); -const rust_ast = require("./rust_ast"); -const { hex, mkdirpSync, get_switch_value, get_switch_exist, finalize_table_rust } = require("./util"); +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import url from "node:url"; +import x86_table from "./x86_table.js"; +const rust_ast = await import("./rust_ast.js"); +const { hex, mkdirpSync, get_switch_value, get_switch_exist, finalize_table_rust } = await import("./util.js"); + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const OUT_DIR = path.join(__dirname, "..", "src/rust/gen/"); - mkdirpSync(OUT_DIR); const table_arg = get_switch_value("--table"); diff --git a/gen/rust_ast.js b/gen/rust_ast.js index fb4f2e98..7d50cd55 100644 --- a/gen/rust_ast.js +++ b/gen/rust_ast.js @@ -1,13 +1,13 @@ "use strict"; -const assert = require("assert").strict; +import assert from "node:assert/strict"; function indent(lines, how_much) { return lines.map(line => " ".repeat(how_much) + line); } -function print_syntax_tree(statements) +export function print_syntax_tree(statements) { let code = []; @@ -77,7 +77,3 @@ function print_syntax_tree(statements) return code; } - -module.exports = { - print_syntax_tree, -}; diff --git a/gen/util.js b/gen/util.js index 9325428c..a01a8eda 100644 --- a/gen/util.js +++ b/gen/util.js @@ -1,14 +1,13 @@ "use strict"; -const assert = require("assert"); -const fs = require("fs"); -const path = require("path"); -const process = require("process"); -const child_process = require("child_process"); +import fs from "node:fs"; +import path from "node:path"; +import process from "node:process"; + const CYAN_FMT = "\x1b[36m%s\x1b[0m"; -function hex(n, pad) +export function hex(n, pad) { pad = pad || 0; let s = n.toString(16).toUpperCase(); @@ -16,12 +15,12 @@ function hex(n, pad) return s; } -function mkdirpSync(dir) +export function mkdirpSync(dir) { fs.mkdirSync(dir, { recursive: true }); } -function get_switch_value(arg_switch) +export function get_switch_value(arg_switch) { const argv = process.argv; const switch_i = argv.indexOf(arg_switch); @@ -33,22 +32,14 @@ function get_switch_value(arg_switch) return null; } -function get_switch_exist(arg_switch) +export function get_switch_exist(arg_switch) { return process.argv.includes(arg_switch); } -function finalize_table_rust(out_dir, name, contents) +export function finalize_table_rust(out_dir, name, contents) { const file_path = path.join(out_dir, name); fs.writeFileSync(file_path, contents); console.log(CYAN_FMT, `[+] Wrote table ${name}.`); } - -module.exports = { - hex, - mkdirpSync, - get_switch_value, - get_switch_exist, - finalize_table_rust, -}; diff --git a/gen/x86_table.js b/gen/x86_table.js index e6663a0c..5325b4f6 100644 --- a/gen/x86_table.js +++ b/gen/x86_table.js @@ -1,6 +1,6 @@ "use strict"; -const { hex } = require("./util"); +const { hex } = await import("./util.js"); // http://ref.x86asm.net/coder32.html @@ -875,4 +875,5 @@ encodings.sort((e1, e2) => { return o1 - o2 || e1.fixed_g - e2.fixed_g; }); -module.exports = Object.freeze(encodings.map(entry => Object.freeze(entry))); +const result = Object.freeze(encodings.map(entry => Object.freeze(entry))); +export default result; diff --git a/lib/9p.js b/lib/9p.js index 27b39836..c988d43d 100644 --- a/lib/9p.js +++ b/lib/9p.js @@ -6,6 +6,21 @@ "use strict"; +import { TRACK_FILENAMES } from "../src/config.js"; +import { VirtIO, VIRTIO_F_VERSION_1, VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_RING_INDIRECT_DESC } from "../src/virtio.js"; +import { S_IFREG, S_IFDIR, STATUS_UNLINKED } from "./filesystem.js"; +import { hex8, message } from "./jor1k.js"; +import { marshall, texten } from "./marshall.js"; +import { range } from "../src/lib.js"; +import { dbg_assert } from "../src/log.js"; + + +// For Types Only +import { CPU } from "../src/cpu.js"; +import { BusConnector } from "../src/bus.js"; +import { FS } from "./filesystem.js"; + + // Feature bit (bit position) for mount tag. const VIRTIO_9P_F_MOUNT_TAG = 0; // Assumed max tag length in bytes. @@ -16,13 +31,13 @@ const MAX_REPLYBUFFER_SIZE = 16 * 1024 * 1024; // TODO // flush -var EPERM = 1; /* Operation not permitted */ -var ENOENT = 2; /* No such file or directory */ -var EEXIST = 17; /* File exists */ -var EINVAL = 22; /* Invalid argument */ -var EOPNOTSUPP = 95; /* Operation is not supported */ -var ENOTEMPTY = 39; /* Directory not empty */ -var EPROTO = 71; /* Protocol error */ +export const EPERM = 1; /* Operation not permitted */ +export const ENOENT = 2; /* No such file or directory */ +export const EEXIST = 17; /* File exists */ +export const EINVAL = 22; /* Invalid argument */ +export const EOPNOTSUPP = 95; /* Operation is not supported */ +export const ENOTEMPTY = 39; /* Directory not empty */ +export const EPROTO = 71; /* Protocol error */ var P9_SETATTR_MODE = 0x00000001; var P9_SETATTR_UID = 0x00000002; @@ -49,18 +64,18 @@ var P9_STAT_MODE_SETUID = 0x00080000; var P9_STAT_MODE_SETGID = 0x00040000; var P9_STAT_MODE_SETVTX = 0x00010000; -const P9_LOCK_TYPE_RDLCK = 0; -const P9_LOCK_TYPE_WRLCK = 1; -const P9_LOCK_TYPE_UNLCK = 2; +export const P9_LOCK_TYPE_RDLCK = 0; +export const P9_LOCK_TYPE_WRLCK = 1; +export const P9_LOCK_TYPE_UNLCK = 2; const P9_LOCK_TYPES = ["shared", "exclusive", "unlock"]; const P9_LOCK_FLAGS_BLOCK = 1; const P9_LOCK_FLAGS_RECLAIM = 2; -const P9_LOCK_SUCCESS = 0; -const P9_LOCK_BLOCKED = 1; -const P9_LOCK_ERROR = 2; -const P9_LOCK_GRACE = 3; +export const P9_LOCK_SUCCESS = 0; +export const P9_LOCK_BLOCKED = 1; +export const P9_LOCK_ERROR = 2; +export const P9_LOCK_GRACE = 3; var FID_NONE = -1; var FID_INODE = 1; @@ -72,7 +87,7 @@ var FID_XATTR = 2; * @param {FS} filesystem * @param {CPU} cpu */ -function Virtio9p(filesystem, cpu, bus) { +export function Virtio9p(filesystem, cpu, bus) { /** @type {FS} */ this.fs = filesystem; @@ -156,7 +171,7 @@ function Virtio9p(filesystem, cpu, bus) { read: () => this.configspace_taglen, write: data => { /* read only */ }, }, - ].concat(v86util.range(VIRTIO_9P_MAX_TAGLEN).map(index => + ].concat(range(VIRTIO_9P_MAX_TAGLEN).map(index => ({ bytes: 1, name: "mount tag name " + index, diff --git a/lib/filesystem.js b/lib/filesystem.js index 46856e01..486f53d6 100644 --- a/lib/filesystem.js +++ b/lib/filesystem.js @@ -5,14 +5,25 @@ "use strict"; -var S_IRWXUGO = 0x1FF; -var S_IFMT = 0xF000; -var S_IFSOCK = 0xC000; -var S_IFLNK = 0xA000; -var S_IFREG = 0x8000; -var S_IFBLK = 0x6000; -var S_IFDIR = 0x4000; -var S_IFCHR = 0x2000; +import { LOG_9P } from "../src/const.js"; +import { h } from "../src/lib.js"; +import { dbg_assert, dbg_log } from "../src/log.js"; +import { marshall, texten } from "../lib/marshall.js"; +import { message } from "./jor1k.js"; +import { EEXIST, ENOTEMPTY, ENOENT, EPERM } from "./9p.js"; +import { P9_LOCK_SUCCESS, P9_LOCK_BLOCKED, P9_LOCK_TYPE_UNLCK, P9_LOCK_TYPE_WRLCK, P9_LOCK_TYPE_RDLCK } from "./9p.js"; + +// For Types Only +import { FileStorageInterface } from "../src/browser/filestorage.js"; + +export const S_IRWXUGO = 0x1FF; +export const S_IFMT = 0xF000; +export const S_IFSOCK = 0xC000; +export const S_IFLNK = 0xA000; +export const S_IFREG = 0x8000; +export const S_IFBLK = 0x6000; +export const S_IFDIR = 0x4000; +export const S_IFCHR = 0x2000; //var S_IFIFO 0010000 //var S_ISUID 0004000 @@ -24,11 +35,11 @@ var O_WRONLY = 0x0001; // open for writing only var O_RDWR = 0x0002; // open for reading and writing var O_ACCMODE = 0x0003; // mask for above modes -var STATUS_INVALID = -0x1; -var STATUS_OK = 0x0; -var STATUS_ON_STORAGE = 0x2; -var STATUS_UNLINKED = 0x4; -var STATUS_FORWARDING = 0x5; +export const STATUS_INVALID = -0x1; +export const STATUS_OK = 0x0; +export const STATUS_ON_STORAGE = 0x2; +export const STATUS_UNLINKED = 0x4; +export const STATUS_FORWARDING = 0x5; /** @const */ var JSONFS_VERSION = 3; @@ -49,7 +60,7 @@ var STATUS_FORWARDING = 0x5; * @param {!FileStorageInterface} storage * @param {{ last_qidnumber: number }=} qidcounter Another fs's qidcounter to synchronise with. */ -function FS(storage, qidcounter) { +export function FS(storage, qidcounter) { /** @type {Array.} */ this.inodes = []; this.events = []; diff --git a/lib/jor1k.js b/lib/jor1k.js index 1b27112b..6114e13a 100644 --- a/lib/jor1k.js +++ b/lib/jor1k.js @@ -1,5 +1,10 @@ "use strict"; +import { LOG_9P } from "../src/const.js"; +import { h } from "../src/lib.js"; +import { dbg_log } from "../src/log.js"; + + // jor1k compatibility var VIRTIO_MAGIC_REG = 0x0; @@ -29,12 +34,12 @@ var VRING_DESC_F_WRITE = 2; /* This marks a buffer as write-only (otherwise var VRING_DESC_F_INDIRECT = 4; /* This means the buffer contains a list of buffer descriptors. */ -function hex8(n) +export function hex8(n) { return h(n); } -var message = {}; +export var message = {}; /** @param {...string} log */ message.Debug = function(log) @@ -92,7 +97,7 @@ else LoadBinaryResource = function(url, OnSuccess, OnError) { //console.log(url); - require("fs")["readFile"](url, function(err, data) + import("node:" + "fs").then(fs => fs["readFile"](url, function(err, data) { if(err) { @@ -102,6 +107,6 @@ else { OnSuccess(data.buffer); } - }); + })); }; } diff --git a/lib/marshall.js b/lib/marshall.js index 17fc0c46..926ba5af 100644 --- a/lib/marshall.js +++ b/lib/marshall.js @@ -5,10 +5,12 @@ "use strict"; -var marshall = {}; +import { message } from "./jor1k.js"; -const textde = new TextDecoder(); -const texten = new TextEncoder(); +export var marshall = {}; + +export const textde = new TextDecoder(); +export const texten = new TextEncoder(); // Inserts data from an array to a byte aligned struct in memory marshall.Marshall = function(typelist, input, struct, offset) { diff --git a/nodejs-loader.mjs b/nodejs-loader.mjs index 1694d9d9..8e05d6e5 100644 --- a/nodejs-loader.mjs +++ b/nodejs-loader.mjs @@ -1,97 +1,5 @@ -import vm from "node:vm"; -import url from "node:url"; -import fs from "node:fs"; -import path from "node:path"; -import crypto from "node:crypto"; -import perf_hooks from "node:perf_hooks"; -const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); - -let files = [ - "src/const.js", - "src/config.js", - "src/log.js", - "src/cpu.js", - "src/debug.js", - "src/io.js", - "src/main.js", - "src/lib.js", - "src/buffer.js", - "src/ide.js", - "src/pci.js", - "src/floppy.js", - "src/memory.js", - "src/dma.js", - "src/pit.js", - "src/vga.js", - "src/ps2.js", - - "src/rtc.js", - "src/uart.js", - - "src/acpi.js", - "src/apic.js", - "src/ioapic.js", - - "src/state.js", - "src/ne2k.js", - "src/sb16.js", - "src/virtio.js", - "src/virtio_console.js", - "src/virtio_net.js", - "src/virtio_balloon.js", - "src/bus.js", - - "src/debug.js", - "src/elf.js", - "src/kernel.js", - - "lib/9p.js", - "lib/filesystem.js", - "lib/jor1k.js", - "lib/marshall.js", - - "src/browser/screen.js", - "src/browser/keyboard.js", - "src/browser/mouse.js", - "src/browser/speaker.js", - "src/browser/serial.js", - "src/browser/network.js", - "src/browser/fake_network.js", - "src/browser/fetch_network.js", - "src/browser/starter.js", - "src/browser/worker_bus.js", - "src/browser/dummy_screen.js", - "src/browser/print_stats.js", - "src/browser/filestorage.js" -]; - - -let globals = Object.create(globalThis); -let v86 = {}; - -let ctx = vm.createContext(globals); -globals.DEBUG = false; -globals.module = {exports:v86}; -Object.defineProperty(globals, "crypto", {value: crypto}); -globals.require = (what) => { - return ({ - perf_hooks, - fs - })[what]; -}; - -for( let f of files ) { - vm.runInContext(fs.readFileSync(path.join(__dirname, f), "utf8"), ctx, { - filename: f - }); -} - -export let { - FetchNetworkAdapter, - MemoryFileStorage, - ServerFileStorageWrapper, -} = globals; - -export default globals.V86; +globalThis.DEBUG = true; +var V86 = await import("./src/browser/starter.js"); +export default V86.V86; diff --git a/package.json b/package.json index 733a00f4..fc4415b5 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "build/*.mjs", "build/v86*.wasm" ], - "main": "build/libv86.js", - "repository": "github:copy/v86" + "main": "build/libv86.mjs", + "repository": "github:copy/v86", + "type": "module" } diff --git a/src/acpi.js b/src/acpi.js index daace1df..00106e48 100644 --- a/src/acpi.js +++ b/src/acpi.js @@ -2,14 +2,22 @@ // http://www.uefi.org/sites/default/files/resources/ACPI_6_1.pdf +import { v86 } from "./main.js"; +import { LOG_ACPI } from "../src/const.js"; +import { h } from "./lib.js"; +import { dbg_log, dbg_assert } from "./log.js"; + +// For Types Only +import { CPU } from "./cpu.js"; + /** @const */ -var PMTIMER_FREQ_SECONDS = 3579545; +export const PMTIMER_FREQ_SECONDS = 3579545; /** * @constructor * @param {CPU} cpu */ -function ACPI(cpu) +export function ACPI(cpu) { /** @type {CPU} */ this.cpu = cpu; diff --git a/src/apic.js b/src/apic.js index df2561ec..564face7 100644 --- a/src/apic.js +++ b/src/apic.js @@ -2,9 +2,18 @@ // See Intel's System Programming Guide +import { v86 } from "./main.js"; +import { LOG_APIC } from "../src/const.js"; +import { APIC_TIMER_FREQ } from "./config.js"; +import { h, int_log2 } from "./lib.js"; +import { dbg_assert, dbg_log, dbg_trace } from "./log.js"; +import { IOAPIC_CONFIG_MASKED, IOAPIC_DELIVERY_INIT, IOAPIC_DELIVERY_NMI, IOAPIC_DELIVERY_FIXED } from "./ioapic.js"; + +// For Types Only +import { CPU } from "./cpu.js"; /** @const */ -var APIC_LOG_VERBOSE = false; +export const APIC_LOG_VERBOSE = false; /** @const */ var APIC_ADDRESS = 0xFEE00000; @@ -23,7 +32,7 @@ var APIC_TIMER_MODE_TSC = 2 << 17; /** @const */ -var DELIVERY_MODES = [ +export const DELIVERY_MODES = [ "Fixed (0)", "Lowest Prio (1)", "SMI (2)", @@ -35,13 +44,14 @@ var DELIVERY_MODES = [ ]; /** @const */ -var DESTINATION_MODES = ["physical", "logical"]; +export const DESTINATION_MODES = ["physical", "logical"]; /** * @constructor * @param {CPU} cpu */ +export function APIC(cpu) { /** @type {CPU} */ @@ -640,7 +650,7 @@ APIC.prototype.register_get_highest_bit = function(v) if(word) { - return v86util.int_log2(word >>> 0) | i << 5; + return int_log2(word >>> 0) | i << 5; } } diff --git a/src/browser/dummy_screen.js b/src/browser/dummy_screen.js index 405dd818..6039d961 100644 --- a/src/browser/dummy_screen.js +++ b/src/browser/dummy_screen.js @@ -1,9 +1,11 @@ "use strict"; +import { dbg_assert } from "../log.js"; + /** * @constructor */ -function DummyScreenAdapter() +export function DummyScreenAdapter() { var graphic_image_data, diff --git a/src/browser/fake_network.js b/src/browser/fake_network.js index eee37e6c..8b1290e3 100644 --- a/src/browser/fake_network.js +++ b/src/browser/fake_network.js @@ -1,5 +1,9 @@ "use strict"; +import { LOG_FETCH } from "../const.js"; +import { h } from "../lib.js"; +import { dbg_assert, dbg_log } from "../log.js"; + // https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml const ETHERTYPE_IPV4 = 0x0800; const ETHERTYPE_ARP = 0x0806; @@ -23,17 +27,17 @@ const V86_ASCII = [118, 56, 54]; * * State TIME_WAIT is not needed, we can skip it and transition directly to CLOSED instead. */ -const TCP_STATE_CLOSED = "closed"; -const TCP_STATE_SYN_RECEIVED = "syn-received"; -const TCP_STATE_SYN_SENT = "syn-sent"; -const TCP_STATE_SYN_PROBE = "syn-probe"; +export const TCP_STATE_CLOSED = "closed"; +export const TCP_STATE_SYN_RECEIVED = "syn-received"; +export const TCP_STATE_SYN_SENT = "syn-sent"; +export const TCP_STATE_SYN_PROBE = "syn-probe"; //const TCP_STATE_LISTEN = "listen"; -const TCP_STATE_ESTABLISHED = "established"; -const TCP_STATE_FIN_WAIT_1 = "fin-wait-1"; -const TCP_STATE_CLOSE_WAIT = "close-wait"; -const TCP_STATE_FIN_WAIT_2 = "fin-wait-2"; -const TCP_STATE_LAST_ACK = "last-ack"; -const TCP_STATE_CLOSING = "closing"; +export const TCP_STATE_ESTABLISHED = "established"; +export const TCP_STATE_FIN_WAIT_1 = "fin-wait-1"; +export const TCP_STATE_CLOSE_WAIT = "close-wait"; +export const TCP_STATE_FIN_WAIT_2 = "fin-wait-2"; +export const TCP_STATE_LAST_ACK = "last-ack"; +export const TCP_STATE_CLOSING = "closing"; //const TCP_STATE_TIME_WAIT = "time-wait"; // source: RFC6335, 6. Port Number Ranges @@ -158,7 +162,7 @@ class GrowableRingbuffer } } -function create_eth_encoder_buf() +export function create_eth_encoder_buf() { const eth_frame = new Uint8Array(ETH_FRAME_SIZE); const buffer = eth_frame.buffer; @@ -445,7 +449,7 @@ function handle_fake_dhcp(packet, adapter) { adapter.receive(make_packet(adapter.eth_encoder_buf, reply)); } -function handle_fake_networking(data, adapter) { +export function handle_fake_networking(data, adapter) { let packet = {}; parse_eth(data, packet); @@ -974,7 +978,7 @@ function write_tcp(spec, out) { return total_length; } -function fake_tcp_connect(dport, adapter) +export function fake_tcp_connect(dport, adapter) { const vm_ip_str = adapter.vm_ip.join("."); const router_ip_str = adapter.router_ip.join("."); @@ -1003,7 +1007,7 @@ function fake_tcp_connect(dport, adapter) return conn; } -function fake_tcp_probe(dport, adapter) { +export function fake_tcp_probe(dport, adapter) { return new Promise((res, rej) => { let handle = fake_tcp_connect(dport, adapter); handle.state = TCP_STATE_SYN_PROBE; @@ -1014,7 +1018,7 @@ function fake_tcp_probe(dport, adapter) { /** * @constructor */ -function TCPConnection() +export function TCPConnection() { this.state = TCP_STATE_CLOSED; this.net = null; // The adapter is stored here diff --git a/src/browser/fetch_network.js b/src/browser/fetch_network.js index ecfdc92d..250d144b 100644 --- a/src/browser/fetch_network.js +++ b/src/browser/fetch_network.js @@ -1,5 +1,21 @@ "use strict"; +import { LOG_FETCH } from "../const.js"; +import { h } from "../lib.js"; +import { dbg_log } from "../log.js"; + +import { + create_eth_encoder_buf, + handle_fake_networking, + TCPConnection, + TCP_STATE_SYN_RECEIVED, + fake_tcp_connect, + fake_tcp_probe +} from "./fake_network.js"; + +// For Types Only +import { BusConnector } from "../bus.js"; + /** * @constructor * @@ -7,7 +23,7 @@ * @param {*=} config * @export */ -function FetchNetworkAdapter(bus, config) +export function FetchNetworkAdapter(bus, config) { config = config || {}; this.bus = bus; diff --git a/src/browser/filestorage.js b/src/browser/filestorage.js index 773cb69e..2cfd8fdd 100644 --- a/src/browser/filestorage.js +++ b/src/browser/filestorage.js @@ -1,7 +1,11 @@ "use strict"; + +import { dbg_assert } from "../log.js"; +import { load_file } from "../lib.js"; + /** @interface */ -function FileStorageInterface() {} +export function FileStorageInterface() {} /** * Read a portion of a file. @@ -31,7 +35,7 @@ FileStorageInterface.prototype.uncache = function(sha256sum) {}; * @constructor * @implements {FileStorageInterface} */ -function MemoryFileStorage() +export function MemoryFileStorage() { /** * From sha256sum to file data. @@ -83,7 +87,7 @@ MemoryFileStorage.prototype.uncache = function(sha256sum) * @param {FileStorageInterface} file_storage * @param {string} baseurl */ -function ServerFileStorageWrapper(file_storage, baseurl) +export function ServerFileStorageWrapper(file_storage, baseurl) { dbg_assert(baseurl, "ServerMemoryFileStorage: baseurl should not be empty"); @@ -104,7 +108,7 @@ ServerFileStorageWrapper.prototype.load_from_server = function(sha256sum) { return new Promise((resolve, reject) => { - v86util.load_file(this.baseurl + sha256sum, { done: async buffer => + load_file(this.baseurl + sha256sum, { done: async buffer => { const data = new Uint8Array(buffer); await this.cache(sha256sum, data); diff --git a/src/browser/inbrowser_network.js b/src/browser/inbrowser_network.js index 90541430..9144eef1 100644 --- a/src/browser/inbrowser_network.js +++ b/src/browser/inbrowser_network.js @@ -1,5 +1,9 @@ "use strict"; + +// For Types Only +import { BusConnector } from "../bus.js"; + /** * Network adapter "inbrowser" which connects the emulated NIC * to a shared in-browser BroadcastChannel. @@ -16,7 +20,7 @@ * @param {BusConnector} bus * @param {*=} config */ -function InBrowserNetworkAdapter(bus, config) +export function InBrowserNetworkAdapter(bus, config) { const id = config.id || 0; diff --git a/src/browser/keyboard.js b/src/browser/keyboard.js index 0ef6e073..1f47bf2e 100644 --- a/src/browser/keyboard.js +++ b/src/browser/keyboard.js @@ -1,5 +1,8 @@ "use strict"; +// For Types Only +import { BusConnector } from "../bus.js"; + /** @const */ var SHIFT_SCAN_CODE = 0x2A; @@ -14,7 +17,7 @@ const PLATFOM_WINDOWS = typeof window !== "undefined" && window.navigator.platfo * * @param {BusConnector} bus */ -function KeyboardAdapter(bus) +export function KeyboardAdapter(bus) { var /** diff --git a/src/browser/main.js b/src/browser/main.js index b1d5bbb3..0ac1db1a 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1,7 +1,14 @@ "use strict"; -(function() -{ +import { V86 } from "./starter.js"; +import { LOG_NAMES } from "../const.js"; +import { LOG_LEVEL, setLogLevel } from "../config.js"; +import { print_stats } from "./print_stats.js"; +import { SyncFileBuffer } from "../buffer.js"; +import { pad0, pads, hex_dump, dump_file, download, round_up_to_next_power_of_2 } from "../lib.js"; +import { log_data } from "../log.js"; + + const ON_LOCALHOST = !location.hostname.endsWith("copy.sh"); const DEFAULT_NETWORKING_PROXIES = ["wss://relay.widgetry.org/", "ws://localhost:8080/"]; @@ -29,13 +36,13 @@ } else if(time < 3600) { - return (time / 60 | 0) + "m " + v86util.pad0(time % 60, 2) + "s"; + return (time / 60 | 0) + "m " + pad0(time % 60, 2) + "s"; } else { return (time / 3600 | 0) + "h " + - v86util.pad0((time / 60 | 0) % 60, 2) + "m " + - v86util.pad0(time % 60, 2) + "s"; + pad0((time / 60 | 0) % 60, 2) + "m " + + pad0(time % 60, 2) + "s"; } } @@ -1659,7 +1666,7 @@ } input.mask = mask; - label.append(input, v86util.pads(name, 4) + " "); + label.append(input, pads(name, 4) + " "); log_levels.appendChild(label); if(i === Math.floor(LOG_NAMES.length / 2)) @@ -1675,11 +1682,11 @@ if(target.checked) { - LOG_LEVEL |= mask; + setLogLevel(LOG_LEVEL | mask); } else { - LOG_LEVEL &= ~mask; + setLogLevel(LOG_LEVEL & ~mask); } target.blur(); @@ -1765,7 +1772,7 @@ if(chunk_size >= 0) { chunk_size = Math.min(4 * 1024 * 1024, Math.max(512, chunk_size)); - chunk_size = v86util.round_up_to_next_power_of_2(chunk_size); + chunk_size = round_up_to_next_power_of_2(chunk_size); } else { @@ -2384,6 +2391,9 @@ // $("memory_dump_dmp").blur(); //}; + /** + * @this HTMLElement + */ $("capture_network_traffic").onclick = function() { this.value = "0 packets"; @@ -2427,6 +2437,9 @@ $("load_state").blur(); }; + /** + * @this HTMLElement + */ $("load_state_input").onchange = async function() { var file = this.files[0]; @@ -2501,6 +2514,9 @@ $("alttab").blur(); }; + /** + * @this HTMLElement + */ $("scale").onchange = function() { var n = parseFloat(this.value); @@ -2627,11 +2643,14 @@ { $("filesystem_panel").style.display = "block"; + /** + * @this HTMLElement + */ $("filesystem_send_file").onchange = function() { Array.prototype.forEach.call(this.files, function(file) { - var loader = new v86util.SyncFileBuffer(file); + var loader = new SyncFileBuffer(file); loader.onload = function() { loader.get_buffer(async function(buffer) @@ -2646,6 +2665,9 @@ this.blur(); }; + /** + * @this HTMLElement + */ $("filesystem_get_file").onkeypress = async function(e) { if(e.which !== 13) @@ -2731,5 +2753,3 @@ window.history.pushState({ search }, "", search); } } - -})(); diff --git a/src/browser/mouse.js b/src/browser/mouse.js index d4f5c9cb..eb071ae2 100644 --- a/src/browser/mouse.js +++ b/src/browser/mouse.js @@ -1,11 +1,17 @@ "use strict"; + +import { dbg_log } from "../log.js"; + +// For Types Only +import { BusConnector } from "../bus.js"; + /** * @constructor * * @param {BusConnector} bus */ -function MouseAdapter(bus, screen_container) +export function MouseAdapter(bus, screen_container) { /** @const */ var SPEED_FACTOR = 0.15; diff --git a/src/browser/network.js b/src/browser/network.js index 22d58f26..4ee59d6d 100644 --- a/src/browser/network.js +++ b/src/browser/network.js @@ -1,5 +1,8 @@ "use strict"; +// For Types Only +import { BusConnector } from "../bus.js"; + /** * An ethernet-through-websocket adapter, to be used with * https://github.com/benjamincburns/websockproxy @@ -12,7 +15,7 @@ * @param {BusConnector} bus * @param {number} [id=0] id */ -function NetworkAdapter(url, bus, id) +export function NetworkAdapter(url, bus, id) { this.bus = bus; this.socket = undefined; diff --git a/src/browser/print_stats.js b/src/browser/print_stats.js index 4a5f3dc7..033cbe48 100644 --- a/src/browser/print_stats.js +++ b/src/browser/print_stats.js @@ -1,9 +1,11 @@ "use strict"; +import { pads } from "../lib.js"; + /** * @export */ -const print_stats = { +export const print_stats = { stats_to_string: function(cpu) { return print_stats.print_misc_stats(cpu) + @@ -251,7 +253,7 @@ const print_stats = { for(let i = 0; i < 0x100; i++) { - text += i.toString(16).padStart(2, "0") + ":" + v86util.pads(Math.round(per_opcode[i] / factor), pad_length); + text += i.toString(16).padStart(2, "0") + ":" + pads(Math.round(per_opcode[i] / factor), pad_length); if(i % 16 === 15) text += "\n"; @@ -264,7 +266,7 @@ const print_stats = { for(let i = 0; i < 0x100; i++) { - text += (i & 0xFF).toString(16).padStart(2, "0") + ":" + v86util.pads(Math.round(per_opcode0f[i] / factor), pad_length); + text += (i & 0xFF).toString(16).padStart(2, "0") + ":" + pads(Math.round(per_opcode0f[i] / factor), pad_length); if(i % 16 === 15) text += "\n"; diff --git a/src/browser/screen.js b/src/browser/screen.js index d207d443..3b963e67 100644 --- a/src/browser/screen.js +++ b/src/browser/screen.js @@ -1,11 +1,14 @@ "use strict"; +import { DEBUG_SCREEN_LAYERS } from "../config.js"; +import { dbg_assert } from "../log.js"; + /** * Adapter to use visual screen in browsers (in contrast to node) * @constructor * @param {Object} options */ -function ScreenAdapter(options, screen_fill_buffer) +export function ScreenAdapter(options, screen_fill_buffer) { const screen_container = options.container; this.screen_fill_buffer = screen_fill_buffer; diff --git a/src/browser/serial.js b/src/browser/serial.js index c5c59422..4d804195 100644 --- a/src/browser/serial.js +++ b/src/browser/serial.js @@ -1,11 +1,16 @@ "use strict"; +import { dbg_assert, dbg_log } from "../log.js"; + +// For Types Only +import { BusConnector } from "../bus.js"; + /** * @constructor * * @param {BusConnector} bus */ -function SerialAdapter(element, bus) +export function SerialAdapter(element, bus) { var serial = this; @@ -216,7 +221,7 @@ function SerialRecordingAdapter(bus) * @constructor * @param {BusConnector} bus */ -function SerialAdapterXtermJS(element, bus) +export function SerialAdapterXtermJS(element, bus) { this.element = element; diff --git a/src/browser/speaker.js b/src/browser/speaker.js index 44ab448c..2d29c10e 100644 --- a/src/browser/speaker.js +++ b/src/browser/speaker.js @@ -1,5 +1,18 @@ "use strict"; +import { + MIXER_CHANNEL_BOTH, MIXER_CHANNEL_LEFT, MIXER_CHANNEL_RIGHT, + MIXER_SRC_PCSPEAKER, MIXER_SRC_DAC, MIXER_SRC_MASTER, +} from "../const.js"; +import { dbg_assert, dbg_log } from "../log.js"; +import { OSCILLATOR_FREQ } from "../pit.js"; +import { dump_file } from "../lib.js"; + +// For Types Only +import { BusConnector } from "../bus.js"; + +/* global registerProcessor, sampleRate */ + /** @const */ var DAC_QUEUE_RESERVE = 0.2; @@ -10,7 +23,7 @@ var AUDIOBUFFER_MINIMUM_SAMPLING_RATE = 8000; * @constructor * @param {!BusConnector} bus */ -function SpeakerAdapter(bus) +export function SpeakerAdapter(bus) { if(typeof window === "undefined") { diff --git a/src/browser/starter.js b/src/browser/starter.js index 22591bd5..d7ad9a23 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -1,5 +1,36 @@ "use strict"; +import { v86 } from "../main.js"; +import { LOG_CPU, WASM_TABLE_OFFSET, WASM_TABLE_SIZE } from "../const.js"; +import { setLogLevel } from "../config.js"; +import { get_rand_int, load_file, read_sized_string_from_mem } from "../lib.js"; +import { dbg_assert, dbg_trace, dbg_log } from "../log.js"; +import { print_stats } from "./print_stats.js"; +import { Bus } from "../bus.js"; +import { BOOT_ORDER_FD_FIRST, BOOT_ORDER_HD_FIRST, BOOT_ORDER_CD_FIRST } from "../rtc.js"; + +import { SpeakerAdapter } from "./speaker.js"; +import { NetworkAdapter } from "./network.js"; +import { FetchNetworkAdapter } from "./fetch_network.js"; +import { WispNetworkAdapter } from "./wisp_network.js"; +import { KeyboardAdapter } from "./keyboard.js"; +import { MouseAdapter } from "./mouse.js"; +import { ScreenAdapter } from "./screen.js"; +import { DummyScreenAdapter } from "./dummy_screen.js"; +import { SerialAdapter, SerialAdapterXtermJS } from "./serial.js"; +import { InBrowserNetworkAdapter } from "./inbrowser_network.js"; + +import { MemoryFileStorage, ServerFileStorageWrapper } from "./filestorage.js"; +import { SyncBuffer, buffer_from_object } from "../buffer.js"; +import { FS } from "../../lib/filesystem.js"; +import { EEXIST, ENOENT } from "../../lib/9p.js"; + + +// Decorates CPU +import "../debug.js"; +import "../memory.js"; +import "../state.js"; + /** * Constructor for emulator instances. * @@ -107,12 +138,12 @@ * @constructor * @export */ -function V86(options) +export function V86(options) { if(typeof options.log_level === "number") { // XXX: Shared between all emulator instances - LOG_LEVEL = options.log_level; + setLogLevel(options.log_level); } //var worker = new Worker("src/browser/worker.js"); @@ -136,7 +167,7 @@ function V86(options) "cpu_event_halt": () => { this.emulator_bus.send("cpu-event-halt"); }, "abort": function() { dbg_assert(false); }, "microtick": v86.microtick, - "get_rand_int": function() { return v86util.get_rand_int(); }, + "get_rand_int": function() { return get_rand_int(); }, "apic_acknowledge_irq": function() { return cpu.devices.apic.acknowledge_irq(); }, "stop_idling": function() { return cpu.stop_idling(); }, @@ -159,11 +190,11 @@ function V86(options) }, "log_from_wasm": function(offset, len) { - const str = v86util.read_sized_string_from_mem(wasm_memory, offset, len); + const str = read_sized_string_from_mem(wasm_memory, offset, len); dbg_log(str, LOG_CPU); }, "console_log_from_wasm": function(offset, len) { - const str = v86util.read_sized_string_from_mem(wasm_memory, offset, len); + const str = read_sized_string_from_mem(wasm_memory, offset, len); console.error(str); }, "dbg_trace_from_wasm": function() { @@ -185,6 +216,8 @@ function V86(options) { wasm_fn = env => { + /* global __dirname */ + return new Promise(resolve => { let v86_bin = DEBUG ? "v86-debug.wasm" : "v86.wasm"; let v86_bin_fallback = "v86-fallback.wasm"; @@ -207,7 +240,7 @@ function V86(options) v86_bin_fallback = "build/" + v86_bin_fallback; } - v86util.load_file(v86_bin, { + load_file(v86_bin, { done: async bytes => { try @@ -218,7 +251,7 @@ function V86(options) } catch(err) { - v86util.load_file(v86_bin_fallback, { + load_file(v86_bin_fallback, { done: async bytes => { const { instance } = await WebAssembly.instantiate(bytes, env); this.wasm_source = bytes; @@ -471,7 +504,7 @@ V86.prototype.continue_init = async function(emulator, options) { files_to_load.push({ name, - loadable: v86util.buffer_from_object(file, this.zstd_decompress_worker.bind(this)), + loadable: buffer_from_object(file, this.zstd_decompress_worker.bind(this)), }); } }; @@ -552,7 +585,7 @@ V86.prototype.continue_init = async function(emulator, options) } else { - v86util.load_file(f.url, { + load_file(f.url, { done: function(result) { if(f.url.endsWith(".zst") && f.name !== "initial_state") @@ -561,7 +594,7 @@ V86.prototype.continue_init = async function(emulator, options) result = this.zstd_decompress(f.size, new Uint8Array(result)); } - put_on_settings.call(this, f.name, f.as_json ? result : new v86util.SyncBuffer(result)); + put_on_settings.call(this, f.name, f.as_json ? result : new SyncBuffer(result)); cont(index + 1); }.bind(this), progress: function progress(e) @@ -618,8 +651,8 @@ V86.prototype.continue_init = async function(emulator, options) settings.fs9p.read_file(initrd_path), settings.fs9p.read_file(bzimage_path), ]); - put_on_settings.call(this, "initrd", new v86util.SyncBuffer(initrd.buffer)); - put_on_settings.call(this, "bzimage", new v86util.SyncBuffer(bzimage.buffer)); + put_on_settings.call(this, "initrd", new SyncBuffer(initrd.buffer)); + put_on_settings.call(this, "bzimage", new SyncBuffer(bzimage.buffer)); } } else @@ -947,16 +980,16 @@ V86.prototype.set_fda = async function(file) { if(file.url && !file.async) { - v86util.load_file(file.url, { + load_file(file.url, { done: result => { - this.v86.cpu.devices.fdc.set_fda(new v86util.SyncBuffer(result)); + this.v86.cpu.devices.fdc.set_fda(new SyncBuffer(result)); }, }); } else { - const image = v86util.buffer_from_object(file, this.zstd_decompress_worker.bind(this)); + const image = buffer_from_object(file, this.zstd_decompress_worker.bind(this)); image.onload = () => { this.v86.cpu.devices.fdc.set_fda(image); @@ -1470,3 +1503,11 @@ function FileNotFoundError(message) this.message = message || "File not found"; } FileNotFoundError.prototype = Error.prototype; + +/* global module */ + +if(typeof module !== "undefined" && typeof module.exports !== "undefined") +{ + module.exports["V86"] = V86; + module.exports["print_stats"] = print_stats; +} diff --git a/src/browser/wisp_network.js b/src/browser/wisp_network.js index 2d07783f..99240b45 100644 --- a/src/browser/wisp_network.js +++ b/src/browser/wisp_network.js @@ -1,5 +1,18 @@ "use strict"; +import { LOG_NET } from "../const.js"; +import { dbg_log } from "../log.js"; + +import { + create_eth_encoder_buf, + handle_fake_networking, + TCPConnection, + TCP_STATE_SYN_RECEIVED, +} from "./fake_network.js"; + +// For Types Only +import { BusConnector } from "../bus.js"; + /** * @constructor * @@ -7,7 +20,7 @@ * @param {BusConnector} bus * @param {*=} config */ -function WispNetworkAdapter(wisp_url, bus, config) +export function WispNetworkAdapter(wisp_url, bus, config) { this.register_ws(wisp_url); this.last_stream = 1; diff --git a/src/browser/worker_bus.js b/src/browser/worker_bus.js index 23577231..3564d7c9 100644 --- a/src/browser/worker_bus.js +++ b/src/browser/worker_bus.js @@ -1,9 +1,9 @@ "use strict"; -var WorkerBus = {}; +import { dbg_assert } from "../log.js"; /** @constructor */ -WorkerBus.Connector = function(pair) +export var Connector = function(pair) { this.listeners = {}; this.pair = pair; @@ -22,7 +22,7 @@ WorkerBus.Connector = function(pair) }; -WorkerBus.Connector.prototype.register = function(name, fn, this_value) +Connector.prototype.register = function(name, fn, this_value) { var listeners = this.listeners[name]; @@ -44,7 +44,7 @@ WorkerBus.Connector.prototype.register = function(name, fn, this_value) * @param {*=} value * @param {*=} transfer_list */ -WorkerBus.Connector.prototype.send = function(name, value, transfer_list) +Connector.prototype.send = function(name, value, transfer_list) { dbg_assert(arguments.length >= 1); @@ -57,7 +57,7 @@ WorkerBus.Connector.prototype.send = function(name, value, transfer_list) }; -WorkerBus.init = function(worker) +export var init = function(worker) { - return new WorkerBus.Connector(worker); + return new Connector(worker); }; diff --git a/src/buffer.js b/src/buffer.js index 3d4382c2..403ad42a 100644 --- a/src/buffer.js +++ b/src/buffer.js @@ -1,14 +1,8 @@ "use strict"; -(function() -{ - v86util.SyncBuffer = SyncBuffer; - v86util.AsyncXHRBuffer = AsyncXHRBuffer; - v86util.AsyncXHRPartfileBuffer = AsyncXHRPartfileBuffer; - v86util.AsyncFileBuffer = AsyncFileBuffer; - v86util.SyncFileBuffer = SyncFileBuffer; - - v86util.buffer_from_object = buffer_from_object; +import { CPU } from "./cpu.js"; +import { load_file } from "./lib.js"; +import { dbg_assert, dbg_log } from "./log.js"; // The smallest size the emulated hardware can emit const BLOCK_SIZE = 256; @@ -19,7 +13,7 @@ * Synchronous access to ArrayBuffer * @constructor */ - function SyncBuffer(buffer) + export function SyncBuffer(buffer) { dbg_assert(buffer instanceof ArrayBuffer); @@ -208,7 +202,7 @@ requested_length = Math.ceil((offset - requested_start + len) / this.fixed_chunk_size) * this.fixed_chunk_size; } - v86util.load_file(this.filename, { + load_file(this.filename, { done: function done(buffer) { var block = new Uint8Array(buffer); @@ -383,7 +377,7 @@ * @param {number|undefined} fixed_chunk_size * @param {boolean|undefined} partfile_alt_format */ - function AsyncXHRPartfileBuffer(filename, size, fixed_chunk_size, partfile_alt_format, zstd_decompress) + export function AsyncXHRPartfileBuffer(filename, size, fixed_chunk_size, partfile_alt_format, zstd_decompress) { const parts = filename.match(/\.[^\.]+(\.zst)?$/); @@ -484,7 +478,7 @@ } else { - v86util.load_file(part_filename, { + load_file(part_filename, { done: async function done(buffer) { let block = new Uint8Array(buffer); @@ -512,7 +506,7 @@ { const part_filename = this.basename + offset + "-" + (offset + len) + this.extension; - v86util.load_file(part_filename, { + load_file(part_filename, { done: function done(buffer) { dbg_assert(buffer.byteLength === len); @@ -537,7 +531,7 @@ * * @constructor */ - function SyncFileBuffer(file) + export function SyncFileBuffer(file) { this.file = file; this.byteLength = file.size; @@ -608,7 +602,7 @@ * * @constructor */ - function AsyncFileBuffer(file) + export function AsyncFileBuffer(file) { this.file = file; this.byteLength = file.size; @@ -703,11 +697,12 @@ return file; }; + var determine_size; if(typeof XMLHttpRequest === "undefined") { - var determine_size = function(path, cb) + determine_size = function(path, cb) { - require("fs")["stat"](path, (err, stats) => + import("node:" + "fs").then(fs => fs["stat"](path, (err, stats) => { if(err) { @@ -717,14 +712,14 @@ { cb(null, stats.size); } - }); + })); }; } else { - var determine_size = function(url, cb) + determine_size = function(url, cb) { - v86util.load_file(url, { + load_file(url, { done: (buffer, http) => { var header = http.getResponseHeader("Content-Range") || ""; @@ -748,13 +743,13 @@ }; } - function buffer_from_object(obj, zstd_decompress_worker) + export function buffer_from_object(obj, zstd_decompress_worker) { // TODO: accept Uint8Array, ArrayBuffer, File, url rather than { url } if(obj.buffer instanceof ArrayBuffer) { - return new v86util.SyncBuffer(obj.buffer); + return new SyncBuffer(obj.buffer); } else if(typeof File !== "undefined" && obj.buffer instanceof File) { @@ -776,11 +771,11 @@ if(is_async) { - return new v86util.AsyncFileBuffer(obj.buffer); + return new AsyncFileBuffer(obj.buffer); } else { - return new v86util.SyncFileBuffer(obj.buffer); + return new SyncFileBuffer(obj.buffer); } } else if(obj.url) @@ -789,11 +784,11 @@ if(obj.use_parts) { - return new v86util.AsyncXHRPartfileBuffer(obj.url, obj.size, obj.fixed_chunk_size, false, zstd_decompress_worker); + return new AsyncXHRPartfileBuffer(obj.url, obj.size, obj.fixed_chunk_size, false, zstd_decompress_worker); } else { - return new v86util.AsyncXHRBuffer(obj.url, obj.size, obj.fixed_chunk_size); + return new AsyncXHRBuffer(obj.url, obj.size, obj.fixed_chunk_size); } } else @@ -801,4 +796,3 @@ dbg_log("Ignored file: url=" + obj.url + " buffer=" + obj.buffer); } } -})(); diff --git a/src/bus.js b/src/bus.js index da718756..07ce172b 100644 --- a/src/bus.js +++ b/src/bus.js @@ -1,9 +1,11 @@ "use strict"; -var Bus = {}; +import { dbg_assert } from "./log.js"; + +export var Bus = {}; /** @constructor */ -function BusConnector() +export function BusConnector() { this.listeners = {}; this.pair = undefined; diff --git a/src/cjs.js b/src/cjs.js new file mode 100644 index 00000000..845072c4 --- /dev/null +++ b/src/cjs.js @@ -0,0 +1,27 @@ +"use strict"; + +/* global module, self */ + +var goog = goog || {}; +goog.exportSymbol = function(name, sym) { + if(typeof module !== "undefined" && typeof module.exports !== "undefined") + { + module.exports[name] = sym; + } + else if(typeof window !== "undefined") + { + window[name] = sym; + } + else if(typeof importScripts === "function") + { + // web worker + self[name] = sym; + } +}; +goog.exportProperty = function() {}; + +/** + * @define {boolean} + * Overridden for production by closure compiler + */ + var DEBUG = true; diff --git a/src/config.js b/src/config.js index 888a6fc5..a0ae8e12 100644 --- a/src/config.js +++ b/src/config.js @@ -3,51 +3,54 @@ * Compile time configuration, some only relevant for debug mode */ -/** - * @define {boolean} - * Overridden for production by closure compiler - */ -var DEBUG = true; +import { + LOG_ALL, LOG_PS2, LOG_PIT, LOG_9P, LOG_PIC, LOG_DMA, LOG_NET, LOG_FLOPPY, LOG_DISK, + LOG_SERIAL, LOG_VGA, LOG_SB16, LOG_VIRTIO +} from "./const.js"; /** @const */ -var LOG_TO_FILE = false; +export var LOG_TO_FILE = false; /** * @const * Enables logging all IO port reads and writes. Very verbose */ -var LOG_ALL_IO = false; +export var LOG_ALL_IO = false; /** * @const */ -var DUMP_GENERATED_WASM = false; +export var DUMP_GENERATED_WASM = false; /** * @const */ -var DUMP_UNCOMPILED_ASSEMBLY = false; +export var DUMP_UNCOMPILED_ASSEMBLY = false; /** * @const * More accurate filenames in 9p debug messages at the cost of performance. */ -var TRACK_FILENAMES = false; +export var TRACK_FILENAMES = false; -var LOG_LEVEL = LOG_ALL & ~LOG_PS2 & ~LOG_PIT & ~LOG_VIRTIO & ~LOG_9P & ~LOG_PIC & +export var LOG_LEVEL = LOG_ALL & ~LOG_PS2 & ~LOG_PIT & ~LOG_VIRTIO & ~LOG_9P & ~LOG_PIC & ~LOG_DMA & ~LOG_SERIAL & ~LOG_NET & ~LOG_FLOPPY & ~LOG_DISK & ~LOG_VGA & ~LOG_SB16; + +export function setLogLevel(level) { + LOG_LEVEL = level; +} /** * @const * Draws entire buffer and visualizes the layers that would be drawn */ -var DEBUG_SCREEN_LAYERS = DEBUG && false; +export var DEBUG_SCREEN_LAYERS = DEBUG && false; /** * @const * How many ticks the TSC does per millisecond */ -var TSC_RATE = 1 * 1000 * 1000; +export var TSC_RATE = 1 * 1000 * 1000; /** @const */ -var APIC_TIMER_FREQ = TSC_RATE; +export var APIC_TIMER_FREQ = TSC_RATE; diff --git a/src/const.js b/src/const.js index 775dada4..8d60ab33 100644 --- a/src/const.js +++ b/src/const.js @@ -1,6 +1,6 @@ "use strict"; -var +export const /** @const */ LOG_ALL = -1, /** @const */ LOG_NONE = 0, @@ -35,7 +35,7 @@ var * @const * @type {Array>} */ -var LOG_NAMES = [ +export const LOG_NAMES = [ [1, ""], [LOG_CPU, "CPU"], [LOG_DISK, "DISK"], @@ -62,7 +62,7 @@ var LOG_NAMES = [ [LOG_FETCH, "FETC"], ]; -var +export const // flags register bitflags /** @const */ FLAG_CARRY = 1, @@ -108,7 +108,7 @@ FLAGS_DEFAULT = 1 << 1, /** @const */ REG_LDTR = 7; // local descriptor table register -var +export const /** * The minimum number of bytes that can be memory-mapped * by one device. @@ -122,45 +122,45 @@ var MMAP_MAX = 0x100000000; /** @const */ -var CR0_PG = 1 << 31; +export const CR0_PG = 1 << 31; /** @const */ -var CR4_PAE = 1 << 5; +export const CR4_PAE = 1 << 5; // https://github.com/qemu/seabios/blob/14221cd86eadba82255fdc55ed174d401c7a0a04/src/fw/paravirt.c#L205-L219 -/** @const */ var FW_CFG_SIGNATURE = 0x00; -/** @const */ var FW_CFG_ID = 0x01; -/** @const */ var FW_CFG_RAM_SIZE = 0x03; -/** @const */ var FW_CFG_NB_CPUS = 0x05; -/** @const */ var FW_CFG_MAX_CPUS = 0x0F; -/** @const */ var FW_CFG_NUMA = 0x0D; -/** @const */ var FW_CFG_FILE_DIR = 0x19; +/** @const */ export const FW_CFG_SIGNATURE = 0x00; +/** @const */ export const FW_CFG_ID = 0x01; +/** @const */ export const FW_CFG_RAM_SIZE = 0x03; +/** @const */ export const FW_CFG_NB_CPUS = 0x05; +/** @const */ export const FW_CFG_MAX_CPUS = 0x0F; +/** @const */ export const FW_CFG_NUMA = 0x0D; +/** @const */ export const FW_CFG_FILE_DIR = 0x19; -/** @const */ var FW_CFG_CUSTOM_START = 0x8000; +/** @const */ export const FW_CFG_CUSTOM_START = 0x8000; // This value is specific to v86, choosen to hopefully not collide with other indexes -/** @const */ var FW_CFG_FILE_START = 0xC000; +/** @const */ export const FW_CFG_FILE_START = 0xC000; -/** @const */ var FW_CFG_SIGNATURE_QEMU = 0x554D4551; +/** @const */ export const FW_CFG_SIGNATURE_QEMU = 0x554D4551; // See same constant in jit.rs /** @const */ -var WASM_TABLE_SIZE = 900; +export const WASM_TABLE_SIZE = 900; /** @const */ -var WASM_TABLE_OFFSET = 1024; +export const WASM_TABLE_OFFSET = 1024; /** @const */ -var MIXER_CHANNEL_LEFT = 0; +export const MIXER_CHANNEL_LEFT = 0; /** @const */ -var MIXER_CHANNEL_RIGHT = 1; +export const MIXER_CHANNEL_RIGHT = 1; /** @const */ -var MIXER_CHANNEL_BOTH = 2; +export const MIXER_CHANNEL_BOTH = 2; /** @const */ -var MIXER_SRC_MASTER = 0; +export const MIXER_SRC_MASTER = 0; /** @const */ -var MIXER_SRC_PCSPEAKER = 1; +export const MIXER_SRC_PCSPEAKER = 1; /** @const */ -var MIXER_SRC_DAC = 2; +export const MIXER_SRC_DAC = 2; diff --git a/src/cpu.js b/src/cpu.js index 70e18cd1..29b6089f 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -1,5 +1,58 @@ "use strict"; +import { + LOG_CPU, REG_CS, + FW_CFG_SIGNATURE, FW_CFG_SIGNATURE_QEMU, + WASM_TABLE_SIZE, WASM_TABLE_OFFSET, FW_CFG_ID, + FW_CFG_RAM_SIZE, FW_CFG_NB_CPUS, FW_CFG_MAX_CPUS, + FW_CFG_NUMA, FW_CFG_FILE_DIR, FW_CFG_FILE_START, + FW_CFG_CUSTOM_START, REG_EAX, REG_EBX, FLAGS_DEFAULT, + MMAP_BLOCK_BITS, MMAP_BLOCK_SIZE, MMAP_MAX +} from "./const.js"; +import { DUMP_GENERATED_WASM, DUMP_UNCOMPILED_ASSEMBLY } from "./config.js"; +import { h, view, Bitmap } from "./lib.js"; +import { dbg_assert, dbg_log } from "./log.js"; + +import { SB16 } from "./sb16.js"; +import { IOAPIC } from "./ioapic.js"; +import { APIC, APIC_LOG_VERBOSE } from "./apic.js"; +import { ACPI } from "./acpi.js"; +import { PIT } from "./pit.js"; +import { DMA } from "./dma.js"; +import { UART } from "./uart.js"; +import { Ne2k } from "./ne2k.js"; +import { IO } from "./io.js"; +import { VirtioConsole } from "./virtio_console.js"; +import { PCI } from "./pci.js"; +import { PS2 } from "./ps2.js"; +import { read_elf } from "./elf.js"; + +import { FloppyController } from "./floppy.js"; +import { IDEDevice } from "./ide.js"; +import { VirtioNet } from "./virtio_net.js"; +import { VGAScreen } from "./vga.js"; +import { VirtioBalloon } from "./virtio_balloon.js"; +import { Virtio9p } from "../lib/9p.js"; + +import { load_kernel } from "./kernel.js"; + + +import { + RTC, + CMOS_EQUIPMENT_INFO, CMOS_BIOS_SMP_COUNT, + CMOS_MEM_HIGHMEM_HIGH, CMOS_MEM_HIGHMEM_MID, CMOS_MEM_HIGHMEM_LOW, + CMOS_DISK_DATA, CMOS_BIOS_DISKTRANSFLAG, CMOS_FLOPPY_DRIVE_TYPE, + BOOT_ORDER_CD_FIRST, CMOS_BIOS_BOOTFLAG1, CMOS_BIOS_BOOTFLAG2, + CMOS_MEM_BASE_LOW, CMOS_MEM_BASE_HIGH, + CMOS_MEM_OLD_EXT_LOW, CMOS_MEM_OLD_EXT_HIGH, CMOS_MEM_EXTMEM_LOW, + CMOS_MEM_EXTMEM_HIGH, CMOS_MEM_EXTMEM2_LOW, CMOS_MEM_EXTMEM2_HIGH +} from "./rtc.js"; + + +// For Types Only + +import { BusConnector } from "./bus.js"; + // Resources: // https://pdos.csail.mit.edu/6.828/2006/readings/i386/toc.htm // https://www-ssl.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html @@ -7,7 +60,7 @@ /** @constructor */ -function CPU(bus, wm, stop_idling) +export function CPU(bus, wm, stop_idling) { this.stop_idling = stop_idling; this.wm = wm; @@ -18,91 +71,91 @@ function CPU(bus, wm, stop_idling) this.wasm_memory = memory; - this.memory_size = v86util.view(Uint32Array, memory, 812, 1); + this.memory_size = view(Uint32Array, memory, 812, 1); this.mem8 = new Uint8Array(0); this.mem32s = new Int32Array(this.mem8.buffer); - this.segment_is_null = v86util.view(Uint8Array, memory, 724, 8); - this.segment_offsets = v86util.view(Int32Array, memory, 736, 8); - this.segment_limits = v86util.view(Uint32Array, memory, 768, 8); - this.segment_access_bytes = v86util.view(Uint8Array, memory, 512, 8); + this.segment_is_null = view(Uint8Array, memory, 724, 8); + this.segment_offsets = view(Int32Array, memory, 736, 8); + this.segment_limits = view(Uint32Array, memory, 768, 8); + this.segment_access_bytes = view(Uint8Array, memory, 512, 8); /** * Wheter or not in protected mode */ - this.protected_mode = v86util.view(Int32Array, memory, 800, 1); + this.protected_mode = view(Int32Array, memory, 800, 1); - this.idtr_size = v86util.view(Int32Array, memory, 564, 1); - this.idtr_offset = v86util.view(Int32Array, memory, 568, 1); + this.idtr_size = view(Int32Array, memory, 564, 1); + this.idtr_offset = view(Int32Array, memory, 568, 1); /** * global descriptor table register */ - this.gdtr_size = v86util.view(Int32Array, memory, 572, 1); - this.gdtr_offset = v86util.view(Int32Array, memory, 576, 1); + this.gdtr_size = view(Int32Array, memory, 572, 1); + this.gdtr_offset = view(Int32Array, memory, 576, 1); - this.tss_size_32 = v86util.view(Int32Array, memory, 1128, 1); + this.tss_size_32 = view(Int32Array, memory, 1128, 1); /* * whether or not a page fault occured */ - this.page_fault = v86util.view(Uint32Array, memory, 540, 8); + this.page_fault = view(Uint32Array, memory, 540, 8); - this.cr = v86util.view(Int32Array, memory, 580, 8); + this.cr = view(Int32Array, memory, 580, 8); // current privilege level - this.cpl = v86util.view(Uint8Array, memory, 612, 1); + this.cpl = view(Uint8Array, memory, 612, 1); // current operand/address size - this.is_32 = v86util.view(Int32Array, memory, 804, 1); + this.is_32 = view(Int32Array, memory, 804, 1); - this.stack_size_32 = v86util.view(Int32Array, memory, 808, 1); + this.stack_size_32 = view(Int32Array, memory, 808, 1); /** * Was the last instruction a hlt? */ - this.in_hlt = v86util.view(Uint8Array, memory, 616, 1); + this.in_hlt = view(Uint8Array, memory, 616, 1); - this.last_virt_eip = v86util.view(Int32Array, memory, 620, 1); - this.eip_phys = v86util.view(Int32Array, memory, 624, 1); + this.last_virt_eip = view(Int32Array, memory, 620, 1); + this.eip_phys = view(Int32Array, memory, 624, 1); - this.sysenter_cs = v86util.view(Int32Array, memory, 636, 1); + this.sysenter_cs = view(Int32Array, memory, 636, 1); - this.sysenter_esp = v86util.view(Int32Array, memory, 640, 1); + this.sysenter_esp = view(Int32Array, memory, 640, 1); - this.sysenter_eip = v86util.view(Int32Array, memory, 644, 1); + this.sysenter_eip = view(Int32Array, memory, 644, 1); - this.prefixes = v86util.view(Int32Array, memory, 648, 1); + this.prefixes = view(Int32Array, memory, 648, 1); - this.flags = v86util.view(Int32Array, memory, 120, 1); + this.flags = view(Int32Array, memory, 120, 1); /** * bitmap of flags which are not updated in the flags variable * changed by arithmetic instructions, so only relevant to arithmetic flags */ - this.flags_changed = v86util.view(Int32Array, memory, 100, 1); + this.flags_changed = view(Int32Array, memory, 100, 1); /** * enough infos about the last arithmetic operation to compute eflags */ - this.last_op_size = v86util.view(Int32Array, memory, 96, 1); - this.last_op1 = v86util.view(Int32Array, memory, 104, 1); - this.last_result = v86util.view(Int32Array, memory, 112, 1); + this.last_op_size = view(Int32Array, memory, 96, 1); + this.last_op1 = view(Int32Array, memory, 104, 1); + this.last_result = view(Int32Array, memory, 112, 1); - this.current_tsc = v86util.view(Uint32Array, memory, 960, 2); // 64 bit + this.current_tsc = view(Uint32Array, memory, 960, 2); // 64 bit /** @type {!Object} */ this.devices = {}; - this.instruction_pointer = v86util.view(Int32Array, memory, 556, 1); - this.previous_ip = v86util.view(Int32Array, memory, 560, 1); + this.instruction_pointer = view(Int32Array, memory, 556, 1); + this.previous_ip = view(Int32Array, memory, 560, 1); // configured by guest - this.apic_enabled = v86util.view(Uint8Array, memory, 548, 1); + this.apic_enabled = view(Uint8Array, memory, 548, 1); // configured when the emulator starts (changes bios initialisation) - this.acpi_enabled = v86util.view(Uint8Array, memory, 552, 1); + this.acpi_enabled = view(Uint8Array, memory, 552, 1); // managed in io.js /** @const */ this.memory_map_read8 = []; @@ -119,47 +172,47 @@ function CPU(bus, wm, stop_idling) vga: null, }; - this.instruction_counter = v86util.view(Uint32Array, memory, 664, 1); + this.instruction_counter = view(Uint32Array, memory, 664, 1); // registers - this.reg32 = v86util.view(Int32Array, memory, 64, 8); + this.reg32 = view(Int32Array, memory, 64, 8); - this.fpu_st = v86util.view(Int32Array, memory, 1152, 4 * 8); + this.fpu_st = view(Int32Array, memory, 1152, 4 * 8); - this.fpu_stack_empty = v86util.view(Uint8Array, memory, 816, 1); + this.fpu_stack_empty = view(Uint8Array, memory, 816, 1); this.fpu_stack_empty[0] = 0xFF; - this.fpu_stack_ptr = v86util.view(Uint8Array, memory, 1032, 1); + this.fpu_stack_ptr = view(Uint8Array, memory, 1032, 1); this.fpu_stack_ptr[0] = 0; - this.fpu_control_word = v86util.view(Uint16Array, memory, 1036, 1); + this.fpu_control_word = view(Uint16Array, memory, 1036, 1); this.fpu_control_word[0] = 0x37F; - this.fpu_status_word = v86util.view(Uint16Array, memory, 1040, 1); + this.fpu_status_word = view(Uint16Array, memory, 1040, 1); this.fpu_status_word[0] = 0; - this.fpu_ip = v86util.view(Int32Array, memory, 1048, 1); + this.fpu_ip = view(Int32Array, memory, 1048, 1); this.fpu_ip[0] = 0; - this.fpu_ip_selector = v86util.view(Int32Array, memory, 1052, 1); + this.fpu_ip_selector = view(Int32Array, memory, 1052, 1); this.fpu_ip_selector[0] = 0; - this.fpu_opcode = v86util.view(Int32Array, memory, 1044, 1); + this.fpu_opcode = view(Int32Array, memory, 1044, 1); this.fpu_opcode[0] = 0; - this.fpu_dp = v86util.view(Int32Array, memory, 1056, 1); + this.fpu_dp = view(Int32Array, memory, 1056, 1); this.fpu_dp[0] = 0; - this.fpu_dp_selector = v86util.view(Int32Array, memory, 1060, 1); + this.fpu_dp_selector = view(Int32Array, memory, 1060, 1); this.fpu_dp_selector[0] = 0; - this.reg_xmm32s = v86util.view(Int32Array, memory, 832, 8 * 4); + this.reg_xmm32s = view(Int32Array, memory, 832, 8 * 4); - this.mxcsr = v86util.view(Int32Array, memory, 824, 1); + this.mxcsr = view(Int32Array, memory, 824, 1); // segment registers, tr and ldtr - this.sreg = v86util.view(Uint16Array, memory, 668, 8); + this.sreg = view(Uint16Array, memory, 668, 8); // debug registers - this.dreg = v86util.view(Int32Array, memory, 684, 8); + this.dreg = view(Int32Array, memory, 684, 8); - this.reg_pdpte = v86util.view(Int32Array, memory, 968, 8); + this.reg_pdpte = view(Int32Array, memory, 968, 8); - this.svga_dirty_bitmap_min_offset = v86util.view(Uint32Array, memory, 716, 1); - this.svga_dirty_bitmap_max_offset = v86util.view(Uint32Array, memory, 720, 1); + this.svga_dirty_bitmap_min_offset = view(Uint32Array, memory, 716, 1); + this.svga_dirty_bitmap_max_offset = view(Uint32Array, memory, 720, 1); this.fw_value = []; this.fw_pointer = 0; @@ -573,7 +626,7 @@ CPU.prototype.set_state = function(state) this.fpu_dp_selector[0] = state[74]; this.fpu_opcode[0] = state[75]; - const bitmap = new v86util.Bitmap(state[78].buffer); + const bitmap = new Bitmap(state[78].buffer); const packed_memory = state[77]; this.unpack_memory(bitmap, packed_memory); @@ -636,7 +689,7 @@ CPU.prototype.pack_memory = function() } } - const bitmap = new v86util.Bitmap(page_count); + const bitmap = new Bitmap(page_count); const packed_memory = new Uint8Array(nonzero_pages.length << 12); for(const [i, page] of nonzero_pages.entries()) @@ -720,8 +773,8 @@ CPU.prototype.create_memory = function(size, minimum_size) const memory_offset = this.allocate_memory(size); - this.mem8 = v86util.view(Uint8Array, this.wasm_memory, memory_offset, size); - this.mem32s = v86util.view(Uint32Array, this.wasm_memory, memory_offset, size >> 2); + this.mem8 = view(Uint8Array, this.wasm_memory, memory_offset, size); + this.mem32s = view(Uint32Array, this.wasm_memory, memory_offset, size >> 2); }; /** diff --git a/src/debug.js b/src/debug.js index a694d8ae..9292d661 100644 --- a/src/debug.js +++ b/src/debug.js @@ -1,5 +1,16 @@ "use strict"; +import { + LOG_BIOS, LOG_CPU, + REG_ESP, REG_EBP, REG_ESI, REG_EAX, REG_EBX, REG_ECX, REG_EDX, REG_EDI, + REG_CS, REG_DS, REG_ES, REG_FS, REG_GS, REG_SS, CR0_PG, CR4_PAE, REG_LDTR, + FLAG_VM, FLAG_INTERRUPT, FLAG_CARRY, FLAG_ADJUST, FLAG_ZERO, FLAG_SIGN, FLAG_TRAP, + FLAG_DIRECTION, FLAG_OVERFLOW, FLAG_PARITY +} from "./const.js"; +import { h, pads } from "./lib.js"; +import { dbg_log } from "./log.js"; +import { CPU } from "./cpu.js"; + CPU.prototype.debug_init = function() { var cpu = this; @@ -560,6 +571,7 @@ CPU.prototype.debug_init = function() { if(cs === undefined) { + /* global require */ if(typeof require === "function") { cs = require("./capstone-x86.min.js"); @@ -588,7 +600,7 @@ CPU.prototype.debug_init = function() instructions.forEach(function (instr) { dbg_log(h(instr.address >>> 0) + ": " + - v86util.pads(instr.bytes.map(x => h(x, 2).slice(-2)).join(" "), 20) + " " + + pads(instr.bytes.map(x => h(x, 2).slice(-2)).join(" "), 20) + " " + instr.mnemonic + " " + instr.op_str); }); dbg_log(""); @@ -616,11 +628,12 @@ CPU.prototype.debug_init = function() debug.dump_wasm = function(buffer) { + /* global require */ if(wabt === undefined) { if(typeof require === "function") { - wabt = require("./libwabt.js"); + wabt = require("./libwabt.cjs"); } else { diff --git a/src/dma.js b/src/dma.js index 4283bded..6ad6d710 100644 --- a/src/dma.js +++ b/src/dma.js @@ -1,10 +1,17 @@ "use strict"; +import { LOG_DMA } from "./const.js"; +import { h } from "./lib.js"; +import { dbg_log } from "./log.js"; + +// For Types Only +import { CPU } from "./cpu.js"; + /** * @constructor * @param {CPU} cpu */ -function DMA(cpu) +export function DMA(cpu) { /** @const @type {CPU} */ this.cpu = cpu; diff --git a/src/elf.js b/src/elf.js index b2d9b3d7..50d561aa 100644 --- a/src/elf.js +++ b/src/elf.js @@ -1,5 +1,8 @@ "use strict"; +import { LOG_LEVEL } from "./config.js"; +import { dbg_log } from "./log.js"; + // A minimal elf parser for loading 32 bit, x86, little endian, executable elf files const ELF_MAGIC = 0x464C457F; @@ -95,7 +98,7 @@ function create_struct(struct) } /** @param {ArrayBuffer} buffer */ -function read_elf(buffer) +export function read_elf(buffer) { const view = new DataView(buffer); diff --git a/src/floppy.js b/src/floppy.js index 3fdde453..302e29b5 100644 --- a/src/floppy.js +++ b/src/floppy.js @@ -3,6 +3,18 @@ // https://www.isdaman.com/alsos/hardware/fdc/floppy.htm // https://wiki.osdev.org/Floppy_Disk_Controller +import { LOG_FLOPPY } from "./const.js"; +import { h } from "./lib.js"; +import { dbg_assert, dbg_log } from "./log.js"; +import { CMOS_FLOPPY_DRIVE_TYPE } from "./rtc.js"; +import { SyncBuffer } from "./buffer.js"; + + +// For Types Only +import { CPU } from "./cpu.js"; +import { DMA } from "./dma.js"; +import { IO } from "./io.js"; + const DIR_DOOR = 0x80; const ST1_NID = 1 << 0; const ST1_NDAT = 1 << 2; @@ -12,7 +24,7 @@ const ST1_NDAT = 1 << 2; * * @param {CPU} cpu */ -function FloppyController(cpu, fda_image, fdb_image) +export function FloppyController(cpu, fda_image, fdb_image) { /** @const @type {IO|undefined} */ this.io = cpu.io; @@ -110,7 +122,7 @@ FloppyController.prototype.set_fda = function(fda_image) dbg_assert(fda_image.buffer && fda_image.buffer instanceof ArrayBuffer); const new_image = new Uint8Array(floppy_size); new_image.set(new Uint8Array(fda_image.buffer)); - fda_image = new v86util.SyncBuffer(new_image.buffer); + fda_image = new SyncBuffer(new_image.buffer); dbg_log("Warning: Unkown floppy size: " + fda_image.byteLength + ", assuming " + floppy_size); } diff --git a/src/ide.js b/src/ide.js index 24691156..ea4c97e7 100644 --- a/src/ide.js +++ b/src/ide.js @@ -1,5 +1,14 @@ "use strict"; +import { LOG_DISK } from "./const.js"; +import { h } from "./lib.js"; +import { dbg_assert, dbg_log } from "./log.js"; +import { CMOS_BIOS_DISKTRANSFLAG, CMOS_DISK_DATA, CMOS_DISK_DRIVE1_CYL } from "./rtc.js"; + +// For Types Only +import { CPU } from "./cpu.js"; +import { BusConnector } from "./bus.js"; + /** @const */ var CDROM_SECTOR_SIZE = 2048; /** @const */ @@ -12,7 +21,7 @@ var HD_SECTOR_SIZE = 512; * @param {number} nr * @param {BusConnector} bus * */ -function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus) +export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus) { this.master = new IDEInterface(this, cpu, master_buffer, is_cd, nr, 0, bus); this.slave = new IDEInterface(this, cpu, slave_buffer, false, nr, 1, bus); diff --git a/src/io.js b/src/io.js index 1d0f1125..29ad137b 100644 --- a/src/io.js +++ b/src/io.js @@ -1,5 +1,14 @@ "use strict"; +import { LOG_IO, MMAP_BLOCK_BITS, MMAP_BLOCK_SIZE, MMAP_MAX } from "./const.js"; +import { LOG_ALL_IO } from "./config.js"; +import { h } from "./lib.js"; +import { dbg_assert, dbg_log } from "./log.js"; + + +// For Types Only +import { CPU } from "./cpu.js"; + /** * The ISA IO bus * Devices register their ports here @@ -7,7 +16,7 @@ * @constructor * @param {CPU} cpu */ -function IO(cpu) +export function IO(cpu) { /** @const */ this.ports = []; @@ -87,9 +96,9 @@ IO.prototype.empty_port_write = function(x) /** * @param {number} port_addr * @param {Object} device - * @param {function():number=} r8 - * @param {function():number=} r16 - * @param {function():number=} r32 + * @param {function(number):number=} r8 + * @param {function(number):number=} r16 + * @param {function(number):number=} r32 */ IO.prototype.register_read = function(port_addr, device, r8, r16, r32) { diff --git a/src/ioapic.js b/src/ioapic.js index 793ff853..ab51b675 100644 --- a/src/ioapic.js +++ b/src/ioapic.js @@ -2,55 +2,64 @@ // http://download.intel.com/design/chipsets/datashts/29056601.pdf -/** @const */ -var IOAPIC_ADDRESS = 0xFEC00000; +import { LOG_APIC, MMAP_BLOCK_SIZE } from "./const.js"; +import { h } from "./lib.js"; +import { dbg_assert, dbg_log } from "./log.js"; +import { DELIVERY_MODES, DESTINATION_MODES, APIC_LOG_VERBOSE } from "./apic.js"; -/** @const */ -var IOREGSEL = 0; - -/** @const */ -var IOWIN = 0x10; - -/** @const */ -var IOAPIC_IRQ_COUNT = 24; - -/** @const */ -var IOAPIC_ID = 0; // must match value in seabios +// For Types Only +import { CPU } from "./cpu.js"; /** @const */ -var IOAPIC_CONFIG_TRIGGER_MODE_LEVEL = 1 << 15; +export const IOAPIC_ADDRESS = 0xFEC00000; /** @const */ -var IOAPIC_CONFIG_MASKED = 1 << 16; +export const IOREGSEL = 0; /** @const */ -var IOAPIC_CONFIG_DELIVS = 1 << 12; +export const IOWIN = 0x10; /** @const */ -var IOAPIC_CONFIG_REMOTE_IRR = 1 << 14; +export const IOAPIC_IRQ_COUNT = 24; /** @const */ -var IOAPIC_CONFIG_READONLY_MASK = IOAPIC_CONFIG_REMOTE_IRR | IOAPIC_CONFIG_DELIVS | 0xFFFE0000; +export const IOAPIC_ID = 0; // must match value in seabios + /** @const */ -var IOAPIC_DELIVERY_FIXED = 0; +export const IOAPIC_CONFIG_TRIGGER_MODE_LEVEL = 1 << 15; /** @const */ -var IOAPIC_DELIVERY_LOWEST_PRIORITY = 1; +export const IOAPIC_CONFIG_MASKED = 1 << 16; /** @const */ -var IOAPIC_DELIVERY_NMI = 4; +export const IOAPIC_CONFIG_DELIVS = 1 << 12; /** @const */ -var IOAPIC_DELIVERY_INIT = 5; +export const IOAPIC_CONFIG_REMOTE_IRR = 1 << 14; + +/** @const */ +export const IOAPIC_CONFIG_READONLY_MASK = IOAPIC_CONFIG_REMOTE_IRR | IOAPIC_CONFIG_DELIVS | 0xFFFE0000; + +/** @const */ +export const IOAPIC_DELIVERY_FIXED = 0; + +/** @const */ +export const IOAPIC_DELIVERY_LOWEST_PRIORITY = 1; + +/** @const */ +export const IOAPIC_DELIVERY_NMI = 4; + +/** @const */ +export const IOAPIC_DELIVERY_INIT = 5; /** * @constructor * @param {CPU} cpu */ -function IOAPIC(cpu) +export function IOAPIC(cpu) { /** @type {CPU} */ this.cpu = cpu; diff --git a/src/kernel.js b/src/kernel.js index 16948540..0fdd0995 100644 --- a/src/kernel.js +++ b/src/kernel.js @@ -1,5 +1,9 @@ "use strict"; +import { h } from "./lib.js"; +import { dbg_assert, dbg_log } from "./log.js"; + + // https://www.kernel.org/doc/Documentation/x86/boot.txt const LINUX_BOOT_HDR_SETUP_SECTS = 0x1F1; @@ -37,7 +41,7 @@ const LINUX_BOOT_HDR_LOADFLAGS_KEEP_SEGMENTS = 1 << 6; const LINUX_BOOT_HDR_LOADFLAGS_CAN_USE_HEAPS = 1 << 7; -function load_kernel(mem8, bzimage, initrd, cmdline) +export function load_kernel(mem8, bzimage, initrd, cmdline) { dbg_log("Trying to load kernel of size " + bzimage.byteLength); diff --git a/src/lib.js b/src/lib.js index 682c1031..441390ef 100644 --- a/src/lib.js +++ b/src/lib.js @@ -1,52 +1,34 @@ "use strict"; -var goog = goog || {}; -goog.exportSymbol = function(name, sym) { - if(typeof module !== "undefined" && typeof module.exports !== "undefined") - { - module.exports[name] = sym; - } - else if(typeof window !== "undefined") - { - window[name] = sym; - } - else if(typeof importScripts === "function") - { - // web worker - self[name] = sym; - } -}; -goog.exportProperty = function() {}; - -var v86util = v86util || {}; +import { dbg_assert } from "./log.js"; // pad string with spaces on the right -v86util.pads = function(str, len) +export function pads(str, len) { str = (str || str === 0) ? str + "" : ""; return str.padEnd(len, " "); -}; +} // pad string with zeros on the left -v86util.pad0 = function(str, len) +export function pad0(str, len) { str = (str || str === 0) ? str + "" : ""; return str.padStart(len, "0"); -}; +} // generates array given size with zeros -v86util.zeros = function(size) +export function zeros(size) { return Array(size).fill(0); -}; +} // generates [0, 1, 2, ..., size-1] -v86util.range = function(size) +export function range(size) { return Array.from(Array(size).keys()); -}; +} -v86util.view = function(constructor, memory, offset, length) +export var view = function(constructor, memory, offset, length) { dbg_assert(offset >= 0); return new Proxy({}, @@ -78,7 +60,7 @@ v86util.view = function(constructor, memory, offset, length) * @param {number=} len * @return {string} */ -function h(n, len) +export function h(n, len) { if(!n) { @@ -89,14 +71,14 @@ function h(n, len) var str = n.toString(16); } - return "0x" + v86util.pad0(str.toUpperCase(), len || 1); + return "0x" + pad0(str.toUpperCase(), len || 1); } -function hex_dump(buffer) +export function hex_dump(buffer) { function hex(n, len) { - return v86util.pad0(n.toString(16).toUpperCase(), len); + return pad0(n.toString(16).toUpperCase(), len); } const result = []; @@ -144,11 +126,13 @@ function hex_dump(buffer) return "\n" + result.join("\n") + "\n"; } +/* global require */ +export var get_rand_int; if(typeof crypto !== "undefined" && crypto.getRandomValues) { const rand_data = new Int32Array(1); - v86util.get_rand_int = function() + get_rand_int = function() { crypto.getRandomValues(rand_data); return rand_data[0]; @@ -159,34 +143,41 @@ else if(typeof require !== "undefined") /** @type {{ randomBytes: Function }} */ const crypto = require("crypto"); - v86util.get_rand_int = function() + get_rand_int = function() { return crypto.randomBytes(4)["readInt32LE"](0); }; } +else if(typeof process !== "undefined") + { + import("node:" + "crypto").then(crypto => { + get_rand_int = function() + { + return crypto["randomBytes"](4)["readInt32LE"](0); + }; + }); + } else { dbg_assert(false, "Unsupported platform: No cryptographic random values"); } -(function() +export var int_log2; + +if(typeof Math.clz32 === "function" && Math.clz32(0) === 32 && Math.clz32(0x12345) === 15 && Math.clz32(-1) === 0) { - if(typeof Math.clz32 === "function" && Math.clz32(0) === 32 && Math.clz32(0x12345) === 15 && Math.clz32(-1) === 0) + /** + * calculate the integer logarithm base 2 + * @param {number} x + * @return {number} + */ + int_log2 = function(x) { - /** - * calculate the integer logarithm base 2 - * @param {number} x - * @return {number} - */ - v86util.int_log2 = function(x) - { - dbg_assert(x > 0); + dbg_assert(x > 0); - return 31 - Math.clz32(x); - }; - - return; - } + return 31 - Math.clz32(x); + }; +} else { var int_log2_table = new Int8Array(256); @@ -203,7 +194,7 @@ else * @param {number} x * @return {number} */ - v86util.int_log2 = function(x) + int_log2 = function(x) { x >>>= 0; dbg_assert(x > 0); @@ -236,28 +227,28 @@ else } } }; -})(); +} -v86util.round_up_to_next_power_of_2 = function(x) +export const round_up_to_next_power_of_2 = function(x) { dbg_assert(x >= 0); - return x <= 1 ? 1 : 1 << 1 + v86util.int_log2(x - 1); + return x <= 1 ? 1 : 1 << 1 + int_log2(x - 1); }; if(DEBUG) { - dbg_assert(v86util.int_log2(1) === 0); - dbg_assert(v86util.int_log2(2) === 1); - dbg_assert(v86util.int_log2(7) === 2); - dbg_assert(v86util.int_log2(8) === 3); - dbg_assert(v86util.int_log2(123456789) === 26); + dbg_assert(int_log2(1) === 0); + dbg_assert(int_log2(2) === 1); + dbg_assert(int_log2(7) === 2); + dbg_assert(int_log2(8) === 3); + dbg_assert(int_log2(123456789) === 26); - dbg_assert(v86util.round_up_to_next_power_of_2(0) === 1); - dbg_assert(v86util.round_up_to_next_power_of_2(1) === 1); - dbg_assert(v86util.round_up_to_next_power_of_2(2) === 2); - dbg_assert(v86util.round_up_to_next_power_of_2(7) === 8); - dbg_assert(v86util.round_up_to_next_power_of_2(8) === 8); - dbg_assert(v86util.round_up_to_next_power_of_2(123456789) === 134217728); + dbg_assert(round_up_to_next_power_of_2(0) === 1); + dbg_assert(round_up_to_next_power_of_2(1) === 1); + dbg_assert(round_up_to_next_power_of_2(2) === 2); + dbg_assert(round_up_to_next_power_of_2(7) === 8); + dbg_assert(round_up_to_next_power_of_2(8) === 8); + dbg_assert(round_up_to_next_power_of_2(123456789) === 134217728); } /** @@ -266,7 +257,7 @@ if(DEBUG) * Queue wrapper around Uint8Array * Used by devices such as the PS2 controller */ -function ByteQueue(size) +export function ByteQueue(size) { var data = new Uint8Array(size), start, @@ -337,7 +328,7 @@ function ByteQueue(size) * Queue wrapper around Float32Array * Used by devices such as the sound blaster sound card */ -function FloatQueue(size) +export function FloatQueue(size) { this.size = size; this.data = new Float32Array(size); @@ -465,7 +456,7 @@ CircularQueue.prototype.set = function(new_data) this.index = 0; }; -function dump_file(ab, name) +export function dump_file(ab, name) { if(!Array.isArray(ab)) { @@ -476,7 +467,7 @@ function dump_file(ab, name) download(blob, name); } -function download(file_or_blob, name) +export function download(file_or_blob, name) { var a = document.createElement("a"); a["download"] = name; @@ -502,7 +493,7 @@ function download(file_or_blob, name) * A simple 1d bitmap * @constructor */ -v86util.Bitmap = function(length_or_buffer) +export var Bitmap = function(length_or_buffer) { if(typeof length_or_buffer === "number") { @@ -514,11 +505,11 @@ v86util.Bitmap = function(length_or_buffer) } else { - dbg_assert(false, "v86util.Bitmap: Invalid argument"); + dbg_assert(false, "Bitmap: Invalid argument"); } }; -v86util.Bitmap.prototype.set = function(index, value) +Bitmap.prototype.set = function(index, value) { const bit_index = index & 7; const byte_index = index >> 3; @@ -528,7 +519,7 @@ v86util.Bitmap.prototype.set = function(index, value) value ? this.view[byte_index] | bit_mask : this.view[byte_index] & ~bit_mask; }; -v86util.Bitmap.prototype.get = function(index) +Bitmap.prototype.get = function(index) { const bit_index = index & 7; const byte_index = index >> 3; @@ -536,19 +527,20 @@ v86util.Bitmap.prototype.get = function(index) return this.view[byte_index] >> bit_index & 1; }; -v86util.Bitmap.prototype.get_buffer = function() +Bitmap.prototype.get_buffer = function() { return this.view.buffer; }; +export var load_file; if(typeof XMLHttpRequest === "undefined") { - v86util.load_file = load_file_nodejs; + load_file = load_file_nodejs; } else { - v86util.load_file = load_file; + load_file = _load_file; } /** @@ -556,7 +548,7 @@ else * @param {Object} options * @param {number=} n_tries */ -function load_file(filename, options, n_tries) +function _load_file(filename, options, n_tries) { var http = new XMLHttpRequest(); @@ -656,13 +648,11 @@ function load_file(filename, options, n_tries) function load_file_nodejs(filename, options) { - const fs = require("fs"); - if(options.range) { dbg_assert(!options.as_json); - fs["open"](filename, "r", (err, fd) => + import("node:" + "fs").then(fs => fs["open"](filename, "r", (err, fd) => { if(err) throw err; @@ -680,7 +670,7 @@ function load_file_nodejs(filename, options) if(err) throw err; }); }); - }); + })); } else { @@ -688,7 +678,7 @@ function load_file_nodejs(filename, options) encoding: options.as_json ? "utf-8" : null, }; - fs["readFile"](filename, o, function(err, data) + import("node:" + "fs").then(fs => fs["readFile"](filename, o, function(err, data) { if(err) { @@ -709,14 +699,14 @@ function load_file_nodejs(filename, options) options.done(result); } - }); + })); } } // Reads len characters at offset from Memory object mem as a JS string -v86util.read_sized_string_from_mem = function read_sized_string_from_mem(mem, offset, len) +export function read_sized_string_from_mem(mem, offset, len) { offset >>>= 0; len >>>= 0; return String.fromCharCode(...new Uint8Array(mem.buffer, offset, len)); -}; +} diff --git a/src/log.js b/src/log.js index 277316a2..d170266a 100644 --- a/src/log.js +++ b/src/log.js @@ -1,6 +1,10 @@ "use strict"; -var log_data = []; +import { LOG_NAMES } from "./const.js"; +import { LOG_TO_FILE, LOG_LEVEL } from "./config.js"; +import { pad0, pads } from "./lib.js"; + +export var log_data = []; function do_the_log(message) { @@ -18,7 +22,7 @@ function do_the_log(message) * @type {function((string|number), number=)} * @const */ -var dbg_log = (function() +export var dbg_log = (function() { if(!DEBUG) { @@ -47,7 +51,7 @@ var dbg_log = (function() if(level & LOG_LEVEL) { var level_name = dbg_names[level] || "", - message = "[" + v86util.pads(level_name, 4) + "] " + stuff; + message = "[" + pads(level_name, 4) + "] " + stuff; if(message === log_last_message) { @@ -60,10 +64,10 @@ var dbg_log = (function() } var now = new Date(); - var time_str = v86util.pad0(now.getHours(), 2) + ":" + - v86util.pad0(now.getMinutes(), 2) + ":" + - v86util.pad0(now.getSeconds(), 2) + "+" + - v86util.pad0(now.getMilliseconds(), 3) + " "; + var time_str = pad0(now.getHours(), 2) + ":" + + pad0(now.getMinutes(), 2) + ":" + + pad0(now.getSeconds(), 2) + "+" + + pad0(now.getMilliseconds(), 3) + " "; if(log_message_repetitions) { @@ -90,7 +94,7 @@ var dbg_log = (function() /** * @param {number=} level */ -function dbg_trace(level) +export function dbg_trace(level) { if(!DEBUG) return; @@ -102,7 +106,7 @@ function dbg_trace(level) * @param {string=} msg * @param {number=} level */ -function dbg_assert(cond, msg, level) +export function dbg_assert(cond, msg, level) { if(!DEBUG) return; @@ -113,7 +117,7 @@ function dbg_assert(cond, msg, level) } -function dbg_assert_failed(msg) +export function dbg_assert_failed(msg) { debugger; console.trace(); diff --git a/src/main.js b/src/main.js index 34a2f255..140197f3 100644 --- a/src/main.js +++ b/src/main.js @@ -1,10 +1,12 @@ "use strict"; +import { CPU } from "./cpu.js"; + /** * @constructor * @param {Object=} wasm */ -function v86(bus, wasm) +export function v86(bus, wasm) { /** @type {boolean} */ this.running = false; @@ -98,6 +100,7 @@ if(typeof process !== "undefined") { v86.prototype.yield = function(t, tick) { + /* global global */ if(t < 1) { global.setImmediate(tick => this.yield_callback(tick), tick); @@ -205,7 +208,7 @@ v86.prototype.restore_state = function(state) return this.cpu.restore_state(state); }; - +/* global require */ if(typeof performance === "object" && performance.now) { v86.microtick = performance.now.bind(performance); diff --git a/src/memory.js b/src/memory.js index 60445e4f..521ec189 100644 --- a/src/memory.js +++ b/src/memory.js @@ -1,5 +1,9 @@ "use strict"; +import { MMAP_BLOCK_BITS } from "./const.js"; +import { CPU } from "./cpu.js"; +import { dbg_assert } from "./log.js"; + CPU.prototype.mmap_read8 = function(addr) { diff --git a/src/ne2k.js b/src/ne2k.js index 5a944af7..6f5a94b3 100644 --- a/src/ne2k.js +++ b/src/ne2k.js @@ -1,5 +1,14 @@ "use strict"; +import { LOG_NET } from "./const.js"; +import { h, hex_dump } from "./lib.js"; +import { dbg_assert, dbg_log } from "./log.js"; + +// For Types Only +import { CPU } from "./cpu.js"; +import { PCI } from "./pci.js"; +import { BusConnector } from "./bus.js"; + // http://www.ethernut.de/pdf/8019asds.pdf const NE2K_LOG_VERBOSE = false; @@ -226,7 +235,7 @@ function translate_mac_address(packet, search_mac, replacement_mac) } } -function format_mac(mac) +export function format_mac(mac) { return [ mac[0].toString(16).padStart(2, "0"), @@ -288,7 +297,7 @@ function dump_packet(packet, prefix) * @param {Boolean} mac_address_translation * @param {number} [id=0] id */ -function Ne2k(cpu, bus, preserve_mac_from_state_image, mac_address_translation, id) +export function Ne2k(cpu, bus, preserve_mac_from_state_image, mac_address_translation, id) { /** @const @type {CPU} */ this.cpu = cpu; diff --git a/src/pci.js b/src/pci.js index f708f7c7..de0c16f2 100644 --- a/src/pci.js +++ b/src/pci.js @@ -1,8 +1,16 @@ "use strict"; +import { LOG_PCI } from "./const.js"; +import { h } from "./lib.js"; +import { dbg_assert, dbg_log } from "./log.js"; + +// For Types Only +import { CPU } from "./cpu.js"; + + // http://wiki.osdev.org/PCI -var +export const /** @const */ PCI_CONFIG_ADDRESS = 0xCF8, /** @const */ PCI_CONFIG_DATA = 0xCFC; @@ -10,7 +18,7 @@ var * @constructor * @param {CPU} cpu */ -function PCI(cpu) +export function PCI(cpu) { this.pci_addr = new Uint8Array(4); this.pci_value = new Uint8Array(4); diff --git a/src/pit.js b/src/pit.js index f7813c15..272b36c7 100644 --- a/src/pit.js +++ b/src/pit.js @@ -1,17 +1,26 @@ "use strict"; +import { v86 } from "./main.js"; +import { LOG_PIT } from "./const.js"; +import { h } from "./lib.js"; +import { dbg_log } from "./log.js"; + +// For Types Only +import { CPU } from "./cpu.js"; + + /** * @const * In kHz */ -var OSCILLATOR_FREQ = 1193.1816666; // 1.193182 MHz +export const OSCILLATOR_FREQ = 1193.1816666; // 1.193182 MHz /** * @constructor * * Programmable Interval Timer */ -function PIT(cpu, bus) +export function PIT(cpu, bus) { /** @const @type {CPU} */ this.cpu = cpu; diff --git a/src/ps2.js b/src/ps2.js index bfee804b..3e8b96d0 100644 --- a/src/ps2.js +++ b/src/ps2.js @@ -1,5 +1,14 @@ "use strict"; +import { LOG_PS2 } from "./const.js"; +import { h } from "./lib.js"; +import { dbg_log } from "./log.js"; + +// For Types Only +import { CPU } from "./cpu.js"; +import { BusConnector } from "./bus.js"; +import { ByteQueue } from "./lib.js"; + /** @const */ const PS2_LOG_VERBOSE = false; @@ -8,7 +17,7 @@ const PS2_LOG_VERBOSE = false; * @param {CPU} cpu * @param {BusConnector} bus */ -function PS2(cpu, bus) +export function PS2(cpu, bus) { /** @const @type {CPU} */ this.cpu = cpu; diff --git a/src/rtc.js b/src/rtc.js index e4c03feb..e0fd43f8 100644 --- a/src/rtc.js +++ b/src/rtc.js @@ -1,56 +1,67 @@ "use strict"; -/** @const */ var CMOS_RTC_SECONDS = 0x00; -/** @const */ var CMOS_RTC_SECONDS_ALARM = 0x01; -/** @const */ var CMOS_RTC_MINUTES = 0x02; -/** @const */ var CMOS_RTC_MINUTES_ALARM = 0x03; -/** @const */ var CMOS_RTC_HOURS = 0x04; -/** @const */ var CMOS_RTC_HOURS_ALARM = 0x05; -/** @const */ var CMOS_RTC_DAY_WEEK = 0x06; -/** @const */ var CMOS_RTC_DAY_MONTH = 0x07; -/** @const */ var CMOS_RTC_MONTH = 0x08; -/** @const */ var CMOS_RTC_YEAR = 0x09; -/** @const */ var CMOS_STATUS_A = 0x0a; -/** @const */ var CMOS_STATUS_B = 0x0b; -/** @const */ var CMOS_STATUS_C = 0x0c; -/** @const */ var CMOS_STATUS_D = 0x0d; -/** @const */ var CMOS_RESET_CODE = 0x0f; +import { v86 } from "./main.js"; +import { LOG_RTC } from "./const.js"; +import { h } from "./lib.js"; +import { dbg_assert, dbg_log } from "./log.js"; -/** @const */ var CMOS_FLOPPY_DRIVE_TYPE = 0x10; -/** @const */ var CMOS_DISK_DATA = 0x12; -/** @const */ var CMOS_EQUIPMENT_INFO = 0x14; -/** @const */ var CMOS_MEM_BASE_LOW = 0x15; -/** @const */ var CMOS_MEM_BASE_HIGH = 0x16; -/** @const */ var CMOS_MEM_OLD_EXT_LOW = 0x17; -/** @const */ var CMOS_MEM_OLD_EXT_HIGH = 0x18; -/** @const */ var CMOS_DISK_DRIVE1_TYPE = 0x19; -/** @const */ var CMOS_DISK_DRIVE2_TYPE = 0x1a; -/** @const */ var CMOS_DISK_DRIVE1_CYL = 0x1b; -/** @const */ var CMOS_DISK_DRIVE2_CYL = 0x24; -/** @const */ var CMOS_MEM_EXTMEM_LOW = 0x30; -/** @const */ var CMOS_MEM_EXTMEM_HIGH = 0x31; -/** @const */ var CMOS_CENTURY = 0x32; -/** @const */ var CMOS_MEM_EXTMEM2_LOW = 0x34; -/** @const */ var CMOS_MEM_EXTMEM2_HIGH = 0x35; -/** @const */ var CMOS_CENTURY2 = 0x37; -/** @const */ var CMOS_BIOS_BOOTFLAG1 = 0x38; -/** @const */ var CMOS_BIOS_DISKTRANSFLAG = 0x39; -/** @const */ var CMOS_BIOS_BOOTFLAG2 = 0x3d; -/** @const */ var CMOS_MEM_HIGHMEM_LOW = 0x5b; -/** @const */ var CMOS_MEM_HIGHMEM_MID = 0x5c; -/** @const */ var CMOS_MEM_HIGHMEM_HIGH = 0x5d; -/** @const */ var CMOS_BIOS_SMP_COUNT = 0x5f; +// For Types Only +import { CPU } from "./cpu.js"; +import { DMA } from "./dma.js"; + + +/** @const */ export const CMOS_RTC_SECONDS = 0x00; +/** @const */ export const CMOS_RTC_SECONDS_ALARM = 0x01; +/** @const */ export const CMOS_RTC_MINUTES = 0x02; +/** @const */ export const CMOS_RTC_MINUTES_ALARM = 0x03; +/** @const */ export const CMOS_RTC_HOURS = 0x04; +/** @const */ export const CMOS_RTC_HOURS_ALARM = 0x05; +/** @const */ export const CMOS_RTC_DAY_WEEK = 0x06; +/** @const */ export const CMOS_RTC_DAY_MONTH = 0x07; +/** @const */ export const CMOS_RTC_MONTH = 0x08; +/** @const */ export const CMOS_RTC_YEAR = 0x09; +/** @const */ export const CMOS_STATUS_A = 0x0a; +/** @const */ export const CMOS_STATUS_B = 0x0b; +/** @const */ export const CMOS_STATUS_C = 0x0c; +/** @const */ export const CMOS_STATUS_D = 0x0d; +/** @const */ export const CMOS_RESET_CODE = 0x0f; + +/** @const */ export const CMOS_FLOPPY_DRIVE_TYPE = 0x10; +/** @const */ export const CMOS_DISK_DATA = 0x12; +/** @const */ export const CMOS_EQUIPMENT_INFO = 0x14; +/** @const */ export const CMOS_MEM_BASE_LOW = 0x15; +/** @const */ export const CMOS_MEM_BASE_HIGH = 0x16; +/** @const */ export const CMOS_MEM_OLD_EXT_LOW = 0x17; +/** @const */ export const CMOS_MEM_OLD_EXT_HIGH = 0x18; +/** @const */ export const CMOS_DISK_DRIVE1_TYPE = 0x19; +/** @const */ export const CMOS_DISK_DRIVE2_TYPE = 0x1a; +/** @const */ export const CMOS_DISK_DRIVE1_CYL = 0x1b; +/** @const */ export const CMOS_DISK_DRIVE2_CYL = 0x24; +/** @const */ export const CMOS_MEM_EXTMEM_LOW = 0x30; +/** @const */ export const CMOS_MEM_EXTMEM_HIGH = 0x31; +/** @const */ export const CMOS_CENTURY = 0x32; +/** @const */ export const CMOS_MEM_EXTMEM2_LOW = 0x34; +/** @const */ export const CMOS_MEM_EXTMEM2_HIGH = 0x35; +/** @const */ export const CMOS_CENTURY2 = 0x37; +/** @const */ export const CMOS_BIOS_BOOTFLAG1 = 0x38; +/** @const */ export const CMOS_BIOS_DISKTRANSFLAG = 0x39; +/** @const */ export const CMOS_BIOS_BOOTFLAG2 = 0x3d; +/** @const */ export const CMOS_MEM_HIGHMEM_LOW = 0x5b; +/** @const */ export const CMOS_MEM_HIGHMEM_MID = 0x5c; +/** @const */ export const CMOS_MEM_HIGHMEM_HIGH = 0x5d; +/** @const */ export const CMOS_BIOS_SMP_COUNT = 0x5f; // see CPU.prototype.fill_cmos -const BOOT_ORDER_CD_FIRST = 0x123; -const BOOT_ORDER_HD_FIRST = 0x312; -const BOOT_ORDER_FD_FIRST = 0x321; +export const BOOT_ORDER_CD_FIRST = 0x123; +export const BOOT_ORDER_HD_FIRST = 0x312; +export const BOOT_ORDER_FD_FIRST = 0x321; /** * RTC (real time clock) and CMOS * @constructor * @param {CPU} cpu */ +export function RTC(cpu) { /** @const @type {CPU} */ diff --git a/src/sb16.js b/src/sb16.js index 43007644..0c2bc263 100644 --- a/src/sb16.js +++ b/src/sb16.js @@ -1,5 +1,21 @@ "use strict"; +import { + LOG_SB16, + MIXER_CHANNEL_BOTH, MIXER_CHANNEL_LEFT, MIXER_CHANNEL_RIGHT, + MIXER_SRC_PCSPEAKER, MIXER_SRC_DAC, MIXER_SRC_MASTER, +} from "./const.js"; +import { h } from "./lib.js"; +import { dbg_log } from "./log.js"; +import { SyncBuffer } from "./buffer.js"; + +// For Types Only +import { CPU } from "./cpu.js"; +import { DMA } from "./dma.js"; +import { IO } from "./io.js"; +import { BusConnector } from "./bus.js"; +import { ByteQueue, FloatQueue } from "./lib.js"; + // Useful documentation, articles, and source codes for reference: // =============================================================== // @@ -92,7 +108,7 @@ var FM_HANDLERS = []; * @param {CPU} cpu * @param {BusConnector} bus */ -function SB16(cpu, bus) +export function SB16(cpu, bus) { /** @const @type {CPU} */ this.cpu = cpu; @@ -150,7 +166,7 @@ function SB16(cpu, bus) this.dma_buffer_uint8 = new Uint8Array(this.dma_buffer); this.dma_buffer_int16 = new Int16Array(this.dma_buffer); this.dma_buffer_uint16 = new Uint16Array(this.dma_buffer); - this.dma_syncbuffer = new v86util.SyncBuffer(this.dma_buffer); + this.dma_syncbuffer = new SyncBuffer(this.dma_buffer); this.dma_waiting_transfer = false; this.dma_paused = false; this.sampling_rate = 22050; @@ -399,7 +415,7 @@ SB16.prototype.set_state = function(state) this.dma_buffer_int8 = new Int8Array(this.dma_buffer); this.dma_buffer_int16 = new Int16Array(this.dma_buffer); this.dma_buffer_uint16 = new Uint16Array(this.dma_buffer); - this.dma_syncbuffer = new v86util.SyncBuffer(this.dma_buffer); + this.dma_syncbuffer = new SyncBuffer(this.dma_buffer); if(this.dma_paused) { diff --git a/src/state.js b/src/state.js index 133d5872..524cf1af 100644 --- a/src/state.js +++ b/src/state.js @@ -1,5 +1,9 @@ "use strict"; +import { h } from "./lib.js"; +import { dbg_assert, dbg_log } from "./log.js"; +import { CPU } from "./cpu.js"; + /** @const */ var STATE_VERSION = 6; diff --git a/src/uart.js b/src/uart.js index a85c5c19..c8078861 100644 --- a/src/uart.js +++ b/src/uart.js @@ -1,5 +1,13 @@ "use strict"; +import { LOG_SERIAL } from "./const.js"; +import { h } from "./lib.js"; +import { dbg_log } from "./log.js"; + +// For Types Only +import { CPU } from "./cpu.js"; +import { BusConnector } from "./bus.js"; + /* * Serial ports * http://wiki.osdev.org/UART @@ -44,7 +52,7 @@ var DLAB = 0x80; * @param {number} port * @param {BusConnector} bus */ -function UART(cpu, port, bus) +export function UART(cpu, port, bus) { /** @const @type {BusConnector} */ this.bus = bus; diff --git a/src/vga.js b/src/vga.js index 0d85da06..61b0d605 100644 --- a/src/vga.js +++ b/src/vga.js @@ -1,5 +1,16 @@ "use strict"; +import { LOG_VGA } from "./const.js"; +import { h } from "./lib.js"; +import { dbg_assert, dbg_log } from "./log.js"; + +// For Types Only +import { CPU } from "./cpu.js"; +import { ScreenAdapter } from "./browser/screen.js"; +import { BusConnector } from "./bus.js"; +import { DummyScreenAdapter } from "./browser/dummy_screen.js"; +import { round_up_to_next_power_of_2, view } from "./lib.js"; + // Always 64k const VGA_BANK_SIZE = 64 * 1024; @@ -51,7 +62,7 @@ const VGA_HOST_MEMORY_SPACE_SIZE = Uint32Array.from([ * @param {ScreenAdapter|DummyScreenAdapter} screen * @param {number} vga_memory_size */ -function VGAScreen(cpu, bus, screen, vga_memory_size) +export function VGAScreen(cpu, bus, screen, vga_memory_size) { this.cpu = cpu; @@ -216,7 +227,7 @@ function VGAScreen(cpu, bus, screen, vga_memory_size) else { // required for pci code - this.vga_memory_size = v86util.round_up_to_next_power_of_2(this.vga_memory_size); + this.vga_memory_size = round_up_to_next_power_of_2(this.vga_memory_size); } dbg_log("effective vga memory size: " + this.vga_memory_size, LOG_VGA); @@ -356,7 +367,7 @@ function VGAScreen(cpu, bus, screen, vga_memory_size) const vga_offset = cpu.svga_allocate_memory(this.vga_memory_size) >>> 0; - this.svga_memory = v86util.view(Uint8Array, cpu.wasm_memory, vga_offset, this.vga_memory_size); + this.svga_memory = view(Uint8Array, cpu.wasm_memory, vga_offset, this.vga_memory_size); this.diff_addr_min = this.vga_memory_size; this.diff_addr_max = 0; diff --git a/src/virtio.js b/src/virtio.js index e7bd6709..26be9836 100644 --- a/src/virtio.js +++ b/src/virtio.js @@ -1,5 +1,13 @@ "use strict"; +import { LOG_VIRTIO } from "./const.js"; +import { h, zeros, int_log2 } from "./lib.js"; +import { dbg_assert, dbg_log } from "./log.js"; + +// For Types Only +import { CPU } from "./cpu.js"; +import { PCI } from "./pci.js"; + // http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html const VIRTIO_PCI_VENDOR_ID = 0x1AF4; @@ -32,9 +40,9 @@ const VIRTIO_ISR_DEVICE_CFG = 2; // Feature bits (bit positions). -const VIRTIO_F_RING_INDIRECT_DESC = 28; -const VIRTIO_F_RING_EVENT_IDX = 29; -const VIRTIO_F_VERSION_1 = 32; +export const VIRTIO_F_RING_INDIRECT_DESC = 28; +export const VIRTIO_F_RING_EVENT_IDX = 29; +export const VIRTIO_F_VERSION_1 = 32; // Queue struct sizes. @@ -153,7 +161,7 @@ var VirtIO_Options; * @param {CPU} cpu * @param {VirtIO_Options} options */ -function VirtIO(cpu, options) +export function VirtIO(cpu, options) { const io = cpu.io; @@ -224,7 +232,7 @@ function VirtIO(cpu, options) ]; // Prevent sparse arrays by preallocating. - this.pci_space = this.pci_space.concat(v86util.zeros(256 - this.pci_space.length)); + this.pci_space = this.pci_space.concat(zeros(256 - this.pci_space.length)); // Remaining PCI space is appended by capabilities further below. this.pci_id = options.pci_id; @@ -480,7 +488,7 @@ VirtIO.prototype.create_common_capability = function(options) dbg_log("Warning: dev<" + this.name +"> " + "Given queue size was not a power of 2. " + "Rounding up to next power of 2.", LOG_VIRTIO); - data = 1 << (v86util.int_log2(data - 1) + 1); + data = 1 << (int_log2(data - 1) + 1); } if(data > this.queue_selected.size_supported) { @@ -727,7 +735,7 @@ VirtIO.prototype.init_capabilities = function(capabilities) // Round up to next power of 2, // Minimum 16 bytes for its size to be detectable in general (esp. mmio). - bar_size = bar_size < 16 ? 16 : 1 << (v86util.int_log2(bar_size - 1) + 1); + bar_size = bar_size < 16 ? 16 : 1 << (int_log2(bar_size - 1) + 1); dbg_assert((cap.port & (bar_size - 1)) === 0, "VirtIO device<" + this.name + "> capability port should be aligned to pci bar size"); diff --git a/src/virtio_balloon.js b/src/virtio_balloon.js index 5880b55e..07ff3763 100644 --- a/src/virtio_balloon.js +++ b/src/virtio_balloon.js @@ -2,6 +2,15 @@ // https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-2900003 +import { LOG_PCI } from "./const.js"; +import { dbg_log } from "./log.js"; +import { VirtIO, VIRTIO_F_VERSION_1 } from "./virtio.js"; +import { marshall } from "../lib/marshall.js"; + +// For Types Only +import { CPU } from "./cpu.js"; +import { BusConnector } from "./bus.js"; + const VIRTIO_BALLOON_F_MUST_TELL_HOST = 0; const VIRTIO_BALLOON_F_STATS_VQ = 1; const VIRTIO_BALLOON_F_DEFLATE_ON_OOM = 2; @@ -25,7 +34,7 @@ const STAT_NAMES = [ * @param {CPU} cpu * @param {BusConnector} bus */ -function VirtioBalloon(cpu, bus) +export function VirtioBalloon(cpu, bus) { /** @const @type {BusConnector} */ this.bus = bus; diff --git a/src/virtio_console.js b/src/virtio_console.js index 4cae265e..c55c783e 100644 --- a/src/virtio_console.js +++ b/src/virtio_console.js @@ -1,7 +1,16 @@ "use strict"; +import { dbg_assert } from "./log.js"; +import { VirtIO, VIRTIO_F_VERSION_1 } from "./virtio.js"; +import { marshall } from "../lib/marshall.js"; + +// For Types Only +import { CPU } from "./cpu.js"; +import { BusConnector } from "./bus.js"; + // https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-2900003 + const VIRTIO_CONSOLE_DEVICE_READY = 0; const VIRTIO_CONSOLE_DEVICE_ADD = 1; const VIRTIO_CONSOLE_DEVICE_REMOVE = 2; @@ -20,7 +29,7 @@ const VIRTIO_CONSOLE_F_EMERG_WRITE = 2; * * @param {CPU} cpu */ -function VirtioConsole(cpu, bus) +export function VirtioConsole(cpu, bus) { /** @const @type {BusConnector} */ this.bus = bus; diff --git a/src/virtio_net.js b/src/virtio_net.js index 886aa763..2d0829f7 100644 --- a/src/virtio_net.js +++ b/src/virtio_net.js @@ -2,6 +2,14 @@ // https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-2900003 +import { dbg_assert } from "./log.js"; +import { VirtIO, VIRTIO_F_VERSION_1 } from "./virtio.js"; +import { format_mac } from "./ne2k.js"; +import { marshall } from "../lib/marshall.js"; + +// For Types Only +import { CPU } from "./cpu.js"; +import { BusConnector } from "./bus.js"; const VIRTIO_NET_F_MAC = 5; const VIRTIO_NET_F_CTRL_VQ = 17; @@ -19,7 +27,7 @@ const VIRTIO_NET_CTRL_MAC_ADDR_SET = 1; * @param {BusConnector} bus * @param {Boolean} preserve_mac_from_state_image */ -function VirtioNet(cpu, bus, preserve_mac_from_state_image) +export function VirtioNet(cpu, bus, preserve_mac_from_state_image) { /** @const @type {BusConnector} */ this.bus = bus; diff --git a/tests/api/clean-shutdown.js b/tests/api/clean-shutdown.js index f645d822..a9b080f2 100755 --- a/tests/api/clean-shutdown.js +++ b/tests/api/clean-shutdown.js @@ -1,13 +1,15 @@ #!/usr/bin/env node "use strict"; +import url from "node:url"; +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + // This test checks that calling emulator.destroy() will remove all event // listeners, so that the nodejs process cleanly and automatically exits. const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; -const fs = require("fs"); -var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86; +var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); process.on("unhandledRejection", exn => { throw exn; }); diff --git a/tests/api/floppy-insert-eject.js b/tests/api/floppy-insert-eject.js index f6536e6c..176609be 100755 --- a/tests/api/floppy-insert-eject.js +++ b/tests/api/floppy-insert-eject.js @@ -1,11 +1,14 @@ #!/usr/bin/env node "use strict"; +import {setTimeout as pause} from "timers/promises"; +import url from "node:url"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; -const pause = require("timers/promises").setTimeout; -const fs = require("fs"); -var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86; + +var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); process.on("unhandledRejection", exn => { throw exn; }); diff --git a/tests/api/pic.js b/tests/api/pic.js index a148352f..69131158 100755 --- a/tests/api/pic.js +++ b/tests/api/pic.js @@ -1,10 +1,12 @@ #!/usr/bin/env node "use strict"; +import url from "node:url"; +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; -const fs = require("fs"); -const V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86; +import fs from "node:fs"; +const { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); const root_path = __dirname + "/../.."; diff --git a/tests/api/reboot.js b/tests/api/reboot.js index 03b454d8..2fc33f07 100755 --- a/tests/api/reboot.js +++ b/tests/api/reboot.js @@ -1,10 +1,13 @@ #!/usr/bin/env node "use strict"; +import url from "node:url"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; -const fs = require("fs"); -var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86; +var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); process.on("unhandledRejection", exn => { throw exn; }); @@ -30,7 +33,7 @@ let did_reboot = false; let serial_text = ""; const timeout = setTimeout(() => { - console.log(serial_data); + console.log(serial_text); throw new Error("Timeout"); }, 120 * 1000); diff --git a/tests/api/reset.js b/tests/api/reset.js index 630d2abc..651c7491 100755 --- a/tests/api/reset.js +++ b/tests/api/reset.js @@ -1,12 +1,15 @@ #!/usr/bin/env node "use strict"; +import url from "node:url"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + // This test checks that reset works const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; -const fs = require("fs"); -var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86; +var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); process.on("unhandledRejection", exn => { throw exn; }); diff --git a/tests/api/serial.js b/tests/api/serial.js index d9e1e45e..63728201 100755 --- a/tests/api/serial.js +++ b/tests/api/serial.js @@ -1,12 +1,15 @@ #!/usr/bin/env node "use strict"; +import url from "node:url"; +import assert from "node:assert/strict"; +import crypto from "node:crypto"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; -const assert = require("assert").strict; -const fs = require("fs"); -const crypto = require("crypto"); -var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86; +var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); process.on("unhandledRejection", exn => { throw exn; }); diff --git a/tests/api/state.js b/tests/api/state.js index 152e2136..a6b992bb 100755 --- a/tests/api/state.js +++ b/tests/api/state.js @@ -1,13 +1,15 @@ #!/usr/bin/env node "use strict"; +import url from "node:url"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + process.on("unhandledRejection", exn => { throw exn; }); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; -var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86; -const assert = require("assert").strict; -var fs = require("fs"); +var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); const config_async_cdrom = { bios: { url: __dirname + "/../../bios/seabios.bin" }, diff --git a/tests/api/test.js b/tests/api/test.js index 1b12dcb4..b354ac5b 100755 --- a/tests/api/test.js +++ b/tests/api/test.js @@ -1,10 +1,12 @@ #!/usr/bin/env node "use strict"; +import url from "node:url"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; -const fs = require("fs"); -var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86; +const { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); process.on("unhandledRejection", exn => { throw exn; }); diff --git a/tests/benchmark/arch-bytemark.js b/tests/benchmark/arch-bytemark.js index a9d13c9b..51d815ad 100755 --- a/tests/benchmark/arch-bytemark.js +++ b/tests/benchmark/arch-bytemark.js @@ -1,10 +1,13 @@ #!/usr/bin/env node "use strict"; +import path from "node:path"; +import url from "node:url"; +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + const BENCH_COLLECT_STATS = +process.env.BENCH_COLLECT_STATS; -const { V86, print_stats } = require(`../../build/${BENCH_COLLECT_STATS ? "libv86-debug" : "libv86"}.js`); -const path = require("path"); +const { V86, print_stats } = await import(`../../build/${BENCH_COLLECT_STATS ? "libv86-debug" : "libv86"}.js`); const V86_ROOT = path.join(__dirname, "../.."); diff --git a/tests/benchmark/arch-python.js b/tests/benchmark/arch-python.js index 1d16a104..e587660a 100755 --- a/tests/benchmark/arch-python.js +++ b/tests/benchmark/arch-python.js @@ -1,10 +1,13 @@ #!/usr/bin/env node "use strict"; +import path from "node:path"; +import url from "node:url"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const BENCH_COLLECT_STATS = +process.env.BENCH_COLLECT_STATS; -const { V86, print_stats } = require(`../../build/${BENCH_COLLECT_STATS ? "libv86-debug" : "libv86"}.js`); -const path = require("path"); +const { V86, print_stats } = await import(`../../build/${BENCH_COLLECT_STATS ? "libv86-debug" : "libv86"}.js`); const V86_ROOT = path.join(__dirname, "../.."); diff --git a/tests/benchmark/linux-boot.js b/tests/benchmark/linux-boot.js index 81529d76..883f2dc2 100755 --- a/tests/benchmark/linux-boot.js +++ b/tests/benchmark/linux-boot.js @@ -1,12 +1,15 @@ #!/usr/bin/env node "use strict"; -const BENCH_COLLECT_STATS = +process.env.BENCH_COLLECT_STATS; +import fs from "node:fs"; +import path from "node:path"; +import url from "node:url"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + +const BENCH_COLLECT_STATS = +process.env.BENCH_COLLECT_STATS; +const { V86, print_stats } = await import(`../../build/${BENCH_COLLECT_STATS ? "libv86-debug" : "libv86"}.js`).V86; -const V86 = require(`../../build/${BENCH_COLLECT_STATS ? "libv86-debug" : "libv86"}.js`).V86; -const print_stats = require("../../build/libv86.js").print_stats; -const fs = require("fs"); -const path = require("path"); const V86_ROOT = path.join(__dirname, "../.."); const LOG_SERIAL = true; diff --git a/tests/benchmark/snapshot.js b/tests/benchmark/snapshot.js index ab19cb4e..db53b814 100755 --- a/tests/benchmark/snapshot.js +++ b/tests/benchmark/snapshot.js @@ -1,12 +1,14 @@ #!/usr/bin/env node "use strict"; +import path from "node:path"; +import url from "node:url"; + const BENCH_COLLECT_STATS = +process.env.BENCH_COLLECT_STATS; -const V86 = require(`../../build/${BENCH_COLLECT_STATS ? "libv86-debug" : "libv86"}.js`).V86; -const print_stats = require("../../build/libv86.js").print_stats; -const fs = require("fs"); -const path = require("path"); +let { V86, print_stats } = await import(`../../build/${BENCH_COLLECT_STATS ? "libv86-debug" : "libv86"}.js`); + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const V86_ROOT = path.join(__dirname, "../.."); const LOG_SERIAL = true; diff --git a/tests/devices/fetch_network.js b/tests/devices/fetch_network.js index fc001950..b62b1f37 100755 --- a/tests/devices/fetch_network.js +++ b/tests/devices/fetch_network.js @@ -1,14 +1,16 @@ #!/usr/bin/env node "use strict"; +import assert from "assert/strict"; +import url from "node:url"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); process.on("unhandledRejection", exn => { throw exn; }); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; const USE_VIRTIO = !!process.env.USE_VIRTIO; -const V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86; - -const assert = require("assert").strict; +const { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); const SHOW_LOGS = false; function wait(time) { diff --git a/tests/devices/virtio_9p.js b/tests/devices/virtio_9p.js index 88bad53b..1dba5edf 100755 --- a/tests/devices/virtio_9p.js +++ b/tests/devices/virtio_9p.js @@ -1,14 +1,16 @@ #!/usr/bin/env node "use strict"; +import url from "node:url"; +import fs from "node:fs"; process.on("unhandledRejection", exn => { throw exn; }); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); +var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); -var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86; -const fs = require("fs"); -const testfsjson = require("./testfs.json"); +const testfsjson = JSON.parse(fs.readFileSync(__dirname + "/testfs.json", "utf-8")); const SHOW_LOGS = false; const STOP_ON_FIRST_FAILURE = false; diff --git a/tests/devices/virtio_balloon.js b/tests/devices/virtio_balloon.js index f53e6eeb..89474aa0 100755 --- a/tests/devices/virtio_balloon.js +++ b/tests/devices/virtio_balloon.js @@ -1,13 +1,15 @@ #!/usr/bin/env node "use strict"; +import assert from "assert/strict"; +import url from "node:url"; +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); process.on("unhandledRejection", exn => { throw exn; }); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; -const V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86; +const { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); -const assert = require("assert").strict; const SHOW_LOGS = false; function wait(time) { diff --git a/tests/devices/virtio_console.js b/tests/devices/virtio_console.js index 91d20b40..c7efefd4 100755 --- a/tests/devices/virtio_console.js +++ b/tests/devices/virtio_console.js @@ -1,13 +1,17 @@ #!/usr/bin/env node "use strict"; +import assert from "assert/strict"; +import fs from "node:fs"; +import url from "node:url"; +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); process.on("unhandledRejection", exn => { throw exn; }); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; const SHOW_LOGS = false; -var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86; -const fs = require("fs"); +var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); + const emulator = new V86({ bios: { url: __dirname + "/../../bios/seabios.bin" }, diff --git a/tests/devices/wisp_network.js b/tests/devices/wisp_network.js index f8bad868..dadd5836 100755 --- a/tests/devices/wisp_network.js +++ b/tests/devices/wisp_network.js @@ -1,13 +1,16 @@ #!/usr/bin/env -S node --experimental-websocket "use strict"; +import assert from "assert/strict"; +import url from "node:url"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); process.on("unhandledRejection", exn => { throw exn; }); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; -const V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86; +const { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); -const assert = require("assert").strict; const SHOW_LOGS = false; const tests = diff --git a/tests/expect/run.js b/tests/expect/run.js index 9b96072b..83888911 100755 --- a/tests/expect/run.js +++ b/tests/expect/run.js @@ -1,17 +1,23 @@ #!/usr/bin/env node "use strict"; +import fs from "node:fs"; +import path from "node:path"; +import assert from "node:assert/strict"; +import url from "node:url"; +import wabtfactory from "../../build/libwabt.cjs"; + + +import { spawnSync } from "node:child_process"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; -const assert = require("assert").strict; -const fs = require("fs"); -const path = require("path"); -const { spawnSync } = require("child_process"); - -const libwabt = require("../../build/libwabt.js")(); +const libwabt = wabtfactory(); try { - var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86; + var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); } catch(e) { console.error(e); diff --git a/tests/full/run.js b/tests/full/run.js index c65986d3..c2e8f319 100755 --- a/tests/full/run.js +++ b/tests/full/run.js @@ -1,6 +1,14 @@ #!/usr/bin/env node "use strict"; +import assert from "node:assert/strict"; +import cluster from "node:cluster"; +import os from "node:os"; +import fs from "node:fs"; +import url from "node:url"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + process.on("unhandledRejection", exn => { throw exn; }); var TIMEOUT_EXTRA_FACTOR = +process.env.TIMEOUT_EXTRA_FACTOR || 1; @@ -14,7 +22,7 @@ const LOG_SCREEN = false; try { - var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86; + var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); } catch(e) { @@ -22,10 +30,6 @@ catch(e) process.exit(1); } -const assert = require("assert").strict; -var cluster = require("cluster"); -var os = require("os"); -var fs = require("fs"); var root_path = __dirname + "/../.."; var SCREEN_WIDTH = 80; @@ -79,7 +83,7 @@ function send_work_to_worker(worker, message) } } -if(cluster.isMaster) +if(cluster.isPrimary) { var tests = [ { @@ -918,7 +922,6 @@ if(cluster.isMaster) var worker = cluster.fork(); worker.on("message", send_work_to_worker.bind(null, worker)); - worker.on("online", send_work_to_worker.bind(null, worker)); worker.on("exit", function(code, signal) { @@ -949,6 +952,7 @@ else process.send("I'm done"); }); }); + process.send("up"); } function bytearray_starts_with(arr, search) diff --git a/tests/jit-paging/run.js b/tests/jit-paging/run.js index 4bdc903c..fc4396a7 100755 --- a/tests/jit-paging/run.js +++ b/tests/jit-paging/run.js @@ -1,12 +1,15 @@ #!/usr/bin/env node "use strict"; +import fs from "node:fs"; +import url from "node:url"; process.on("unhandledRejection", exn => { throw exn; }); +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; -var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86; -var fs = require("fs"); +var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); + var test_executable = new Uint8Array(fs.readFileSync(__dirname + "/test-jit")); diff --git a/tests/kvm-unit-tests/run.js b/tests/kvm-unit-tests/run.mjs similarity index 83% rename from tests/kvm-unit-tests/run.js rename to tests/kvm-unit-tests/run.mjs index ed683b3a..f8f64d46 100755 --- a/tests/kvm-unit-tests/run.js +++ b/tests/kvm-unit-tests/run.mjs @@ -1,12 +1,17 @@ #!/usr/bin/env node "use strict"; +import fs from "node:fs"; +import path from "node:path"; +import url from "node:url"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + process.on("unhandledRejection", exn => { throw exn; }); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; -var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86; -var fs = require("fs"); +var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); function readfile(path) { diff --git a/tests/nasm/create_tests.js b/tests/nasm/create_tests.js index 05d50470..9967955d 100755 --- a/tests/nasm/create_tests.js +++ b/tests/nasm/create_tests.js @@ -1,6 +1,20 @@ #!/usr/bin/env node "use strict"; +import fs from "node:fs"; +import fse from "node:fs/promises"; +import path from "node:path"; +import assert from "node:assert/strict"; +import util from "node:util"; +import url from "node:url"; +import { execFile as execFileAsync } from "node:child_process"; + +import encodings from "../../gen/x86_table.js"; +import Rand from "./rand.js"; + + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + // number of tests per instruction const NUMBER_TESTS = 5; // arithmetic tests @@ -19,14 +33,7 @@ const OF = 1 << 11; const BUILD_DIR = __dirname + "/build/"; const LOG_VERBOSE = false; -const assert = require("assert").strict; -const fs = require("fs"); -const fse = require("fs/promises"); -const path = require("path"); -const encodings = require("../../gen/x86_table.js"); -const util = require("util"); -const execFile = util.promisify(require("child_process").execFile); -const Rand = require("./rand.js"); +const execFile = util.promisify(execFileAsync); const header = fs.readFileSync(path.join(__dirname, "header.inc")); const footer = fs.readFileSync(path.join(__dirname, "footer.inc")); diff --git a/tests/nasm/gen_fixtures.js b/tests/nasm/gen_fixtures.js index 9e5302c8..36922117 100755 --- a/tests/nasm/gen_fixtures.js +++ b/tests/nasm/gen_fixtures.js @@ -1,11 +1,14 @@ #!/usr/bin/env node "use strict"; -const assert = require("assert").strict; -const fs = require("fs"); -const os = require("os"); -const path = require("path"); -const { spawn, spawnSync } = require("child_process"); +import fs from "node:fs"; +import path from "node:path"; +import url from "node:url"; +import assert from "node:assert/strict"; +import os from "node:os"; +import { spawn, spawnSync } from "node:child_process"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const DEBUG = process.env.DEBUG || false; // Maximum number of gdb processes to spawn in parallel diff --git a/tests/nasm/rand.js b/tests/nasm/rand.js index eae47778..66076e49 100644 --- a/tests/nasm/rand.js +++ b/tests/nasm/rand.js @@ -1,5 +1,4 @@ "use strict"; -const assert = require("assert"); // From http://baagoe.com/en/RandomMusings/javascript/ // Johannes Baagøe , 2010 @@ -25,7 +24,7 @@ function Mash() { } // From http://baagoe.com/en/RandomMusings/javascript/ -function KISS07() { +export default function KISS07() { return (function(args) { // George Marsaglia, 2007-06-23 //http://groups.google.com/group/comp.lang.fortran/msg/6edb8ad6ec5421a5 @@ -100,5 +99,3 @@ function KISS07() { }; } (Array.prototype.slice.call(arguments))); } - -module.exports = KISS07; diff --git a/tests/nasm/run.js b/tests/nasm/run.js index 04a5ade9..8926a824 100755 --- a/tests/nasm/run.js +++ b/tests/nasm/run.js @@ -1,6 +1,15 @@ #!/usr/bin/env node "use strict"; +import fs from "node:fs"; +import path from "node:path"; +import url from "node:url"; +import assert from "node:assert/strict"; +import os from "node:os"; +import cluster from "node:cluster"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + process.on("unhandledRejection", exn => { throw exn; }); // Mapping between signals and x86 exceptions: @@ -15,12 +24,6 @@ process.on("unhandledRejection", exn => { throw exn; }); // A #UD might indicate a bug in the test generation -const assert = require("assert").strict; -const fs = require("fs"); -const path = require("path"); -const os = require("os"); -const cluster = require("cluster"); - const MAX_PARALLEL_TESTS = +process.env.MAX_PARALLEL_TESTS || 99; const TEST_NAME = new RegExp(process.env.TEST_NAME || "", "i"); const SINGLE_TEST_TIMEOUT = 10000; @@ -29,6 +32,7 @@ const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; const TEST_DIR = __dirname + "/build/"; const DONE_MSG = "DONE"; const TERMINATE_MSG = "DONE"; +const READY_MSG = "READY"; const BSS = 0x100000; const STACK_TOP = 0x102000; @@ -47,7 +51,7 @@ const FPU_STATUS_MASK = 0xFFFF & ~(1 << 9 | 1 << 5 | 1 << 3 | 1 << 1); // bits t const FP_COMPARISON_SIGNIFICANT_DIGITS = 7; try { - var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86; + var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); } catch(e) { console.error(e); @@ -202,14 +206,12 @@ if(cluster.isMaster) let worker = cluster.fork(); worker.on("message", function(message) { - if(message !== DONE_MSG) { + if(message !== DONE_MSG && message !== READY_MSG) { failed_tests.push(message); } send_work_to_worker(this); }); - worker.on("online", send_work_to_worker.bind(null, worker)); - worker.on("exit", function(code, signal) { if(code !== 0 && code !== null) { console.log("Worker error code:", code); @@ -548,4 +550,6 @@ else { run_test(message); } }); + + process.send(READY_MSG); } diff --git a/tests/qemu/run-qemu.js b/tests/qemu/run-qemu.js index 6e54a45b..ce477bc3 100755 --- a/tests/qemu/run-qemu.js +++ b/tests/qemu/run-qemu.js @@ -1,12 +1,15 @@ #!/usr/bin/env node "use strict"; -const QEMU = "qemu-system-x86_64"; +import fs from "node:fs"; +import path from "node:path"; +import url from "node:url"; +import assert from "node:assert/strict"; +import { spawn, spawnSync } from "node:child_process"; -const assert = require("assert").strict; -const fs = require("fs"); -const { spawn, spawnSync } = require("child_process"); -const path = require("path"); +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + +const QEMU = "qemu-system-x86_64"; const share_dir_9p = fs.mkdtempSync("/tmp/v86-test-qemu-9p"); diff --git a/tests/qemu/run.js b/tests/qemu/run.js index 44d540d3..877d4061 100755 --- a/tests/qemu/run.js +++ b/tests/qemu/run.js @@ -10,7 +10,7 @@ import path from "path"; import fs from "fs"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const { default: { V86 } } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`); +const { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); var test_executable = new Uint8Array(fs.readFileSync(__dirname + "/test-i386")); diff --git a/tests/rust/verify-wasmgen-dummy-output.js b/tests/rust/verify-wasmgen-dummy-output.js index e8612e2a..85494334 100755 --- a/tests/rust/verify-wasmgen-dummy-output.js +++ b/tests/rust/verify-wasmgen-dummy-output.js @@ -1,11 +1,15 @@ #!/usr/bin/env node "use strict"; -process.on("unhandledRejection", exn => { throw exn; }); -const assert = require("assert").strict; -const fs = require("fs"); -const path = require("path"); +import fs from "node:fs"; +import path from "node:path"; +import url from "node:url"; +import assert from "node:assert/strict"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + +process.on("unhandledRejection", exn => { throw exn; }); const DUMMY_MODULE_PATH = path.resolve(__dirname, "../../build/dummy_output.wasm"); const dummy_module = fs.readFileSync(DUMMY_MODULE_PATH); diff --git a/tools/docker/alpine/build-state.js b/tools/docker/alpine/build-state.js index fd866b9b..616622dc 100755 --- a/tools/docker/alpine/build-state.js +++ b/tools/docker/alpine/build-state.js @@ -1,11 +1,14 @@ #!/usr/bin/env node "use strict"; +import path from "node:path"; +import fs from "node:fs"; +import url from "node:url"; + console.log("Don't forget to run `make all` before running this script"); -const path = require("path"); -const fs = require("fs"); -const V86 = require("./../../../build/libv86.js").V86; +const { V86 } = await import("./../../../build/libv86.js"); +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const V86_ROOT = path.join(__dirname, "../../.."); const OUTPUT_FILE = path.join(V86_ROOT, "images/alpine-state.bin"); From ed0ed07a18b6688507606f6b87bf506067ccd6a1 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sun, 6 Apr 2025 18:49:35 +0200 Subject: [PATCH 013/301] Reset PS/2 runtime state when soft-resetting CPU (#1301) - Moved assignment of all member variables from constructor to new method PS2.reset(). - Added call to PS2.reset() in PS2 constructor. --- src/cpu.js | 4 ++++ src/ps2.js | 65 +++++++++++++++++++++++++++++------------------------- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/src/cpu.js b/src/cpu.js index 29b6089f..bb2ae9d2 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -741,6 +741,10 @@ CPU.prototype.reboot_internal = function() { this.devices.virtio_net.reset(); } + if(this.devices.ps2) + { + this.devices.ps2.reset(); + } this.load_bios(); }; diff --git a/src/ps2.js b/src/ps2.js index 3e8b96d0..ac8c2994 100644 --- a/src/ps2.js +++ b/src/ps2.js @@ -25,6 +25,40 @@ export function PS2(cpu, bus) /** @const @type {BusConnector} */ this.bus = bus; + this.reset(); + + this.bus.register("keyboard-code", function(code) + { + this.kbd_send_code(code); + }, this); + + this.bus.register("mouse-click", function(data) + { + this.mouse_send_click(data[0], data[1], data[2]); + }, this); + + this.bus.register("mouse-delta", function(data) + { + this.mouse_send_delta(data[0], data[1]); + }, this); + + this.bus.register("mouse-wheel", function(data) + { + this.wheel_movement -= data[0]; + this.wheel_movement -= data[1] * 2; // X Wheel Movement + this.wheel_movement = Math.min(7, Math.max(-8, this.wheel_movement)); + this.send_mouse_packet(0, 0); + }, this); + + cpu.io.register_read(0x60, this, this.port60_read); + cpu.io.register_read(0x64, this, this.port64_read); + + cpu.io.register_write(0x60, this, this.port60_write); + cpu.io.register_write(0x64, this, this.port64_write); +} + +PS2.prototype.reset = function() +{ /** @type {boolean} */ this.enable_mouse_stream = false; @@ -110,42 +144,13 @@ export function PS2(cpu, bus) /** @type {boolean} */ this.next_byte_is_aux = false; - this.bus.register("keyboard-code", function(code) - { - this.kbd_send_code(code); - }, this); - - this.bus.register("mouse-click", function(data) - { - this.mouse_send_click(data[0], data[1], data[2]); - }, this); - - this.bus.register("mouse-delta", function(data) - { - this.mouse_send_delta(data[0], data[1]); - }, this); - - this.bus.register("mouse-wheel", function(data) - { - this.wheel_movement -= data[0]; - this.wheel_movement -= data[1] * 2; // X Wheel Movement - this.wheel_movement = Math.min(7, Math.max(-8, this.wheel_movement)); - this.send_mouse_packet(0, 0); - }, this); - this.command_register = 1 | 4; // TODO: What should be the initial value? this.controller_output_port = 0; this.read_output_register = false; this.read_command_register = false; this.read_controller_output_port = false; - - cpu.io.register_read(0x60, this, this.port60_read); - cpu.io.register_read(0x64, this, this.port64_read); - - cpu.io.register_write(0x60, this, this.port60_write); - cpu.io.register_write(0x64, this, this.port64_write); -} +}; PS2.prototype.get_state = function() { From f97330d99e04127461cd0a52a134435107a93003 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Tue, 15 Apr 2025 08:42:43 +0300 Subject: [PATCH 014/301] VGA BIOS input (#1307) --- debug.html | 7 ++++++- index.html | 9 +++++++-- src/browser/main.js | 32 +++++++++++++++++--------------- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/debug.html b/debug.html index e752317e..8d6b5038 100644 --- a/debug.html +++ b/debug.html @@ -90,7 +90,7 @@ -
+
@@ -122,6 +122,11 @@
+ + +
+ + Disk images are not uploaded to the server
diff --git a/index.html b/index.html index f4d01f3a..7eb80ee1 100644 --- a/index.html +++ b/index.html @@ -127,7 +127,7 @@ -
+
@@ -153,7 +153,12 @@ -
+
+ + + + +
diff --git a/src/browser/main.js b/src/browser/main.js index 0ac1db1a..c87af9eb 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1852,6 +1852,11 @@ import { log_data } from "../log.js"; { settings.bios = { buffer: bios }; } + const vga_bios = $("vga_bios").files[0]; + if(vga_bios) + { + settings.vga_bios = { buffer: vga_bios }; + } const fda = $("floppy_image").files[0]; if(fda) { @@ -1923,23 +1928,20 @@ import { log_data } from "../log.js"; if(settings.acpi) new_query_args.set("acpi", "1"); } + const BIOSPATH = "bios/"; + if(!settings.bios) { - const BIOSPATH = "bios/"; - - if(settings.use_bochs_bios) - { - var biosfile = "bochs-bios.bin"; - var vgabiosfile = "bochs-vgabios.bin"; - } - else - { - var biosfile = DEBUG ? "seabios-debug.bin" : "seabios.bin"; - var vgabiosfile = DEBUG ? "vgabios-debug.bin" : "vgabios.bin"; - } - - settings.bios = { url: BIOSPATH + biosfile }; - settings.vga_bios = { url: BIOSPATH + vgabiosfile }; + settings.bios = { url: BIOSPATH + (DEBUG ? "seabios-debug.bin" : "seabios.bin") }; + } + if(!settings.vga_bios) + { + settings.vga_bios = { url: BIOSPATH + (DEBUG ? "vgabios-debug.bin" : "vgabios.bin") }; + } + if(settings.use_bochs_bios) + { + settings.bios = { url: BIOSPATH + "bochs-bios.bin" }; + settings.vga_bios = { url: BIOSPATH + "bochs-vgabios.bin" }; } } From c8f33a91e92351a32715b0730275c04fc4aa5621 Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 31 Mar 2025 19:41:55 +0700 Subject: [PATCH 015/301] append version to query string (anti-cache) --- Makefile | 7 +++++-- index.html | 4 ++-- src/browser/main.js | 9 ++++++++- src/browser/starter.js | 4 +--- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 74d887f3..46b52c45 100644 --- a/Makefile +++ b/Makefile @@ -274,8 +274,11 @@ update_version: set -e ;\ COMMIT=`git log --format="%h" -n 1` ;\ DATE=`git log --date="format:%b %e, %Y %H:%m" --format="%cd" -n 1` ;\ - SEARCH='Version: [a-f0-9]\+ ([^(]\+)' ;\ - REPLACE='Version: '$$COMMIT' ('$$DATE')' ;\ + SEARCH='Version: [a-f0-9]\+ ([^(]\+)' ;\ + REPLACE='Version: '$$COMMIT' ('$$DATE')' ;\ + sed -i "s@$$SEARCH@$$REPLACE@g" index.html ;\ + SEARCH='' ;\ + REPLACE='' ;\ sed -i "s@$$SEARCH@$$REPLACE@g" index.html ;\ grep $$COMMIT index.html diff --git a/index.html b/index.html index 7eb80ee1..9557b766 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,7 @@ - +
@@ -324,7 +324,7 @@

-Version: 98e7110c2 (Feb 16, 2021 12:02) +Version: 98e7110c2 (Feb 16, 2021 12:02)
Enable debug diff --git a/src/browser/main.js b/src/browser/main.js index c87af9eb..9bdd80ea 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -16,6 +16,12 @@ import { log_data } from "../log.js"; const DEFAULT_VGA_MEMORY_SIZE = 8; const DEFAULT_BOOT_ORDER = 0; + function query_append() + { + const version = $("version"); + return version ? "?" + version.textContent : ""; + } + function set_title(text) { document.title = text + " - v86" + (DEBUG ? " - debug" : ""); @@ -1437,7 +1443,7 @@ import { log_data } from "../log.js"; { const link = document.createElement("link"); link.rel = "prefetch"; - link.href = "build/v86.wasm"; + link.href = "build/v86.wasm" + query_append(); document.head.appendChild(link); } @@ -1951,6 +1957,7 @@ import { log_data } from "../log.js"; } const emulator = new V86({ + wasm_path: "build/" + (DEBUG ? "v86-debug.wasm" : "v86.wasm") + query_append(), screen: { container: $("screen_container"), use_graphical_text: false, diff --git a/src/browser/starter.js b/src/browser/starter.js index d7ad9a23..3881c6dc 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -225,9 +225,7 @@ export function V86(options) if(options.wasm_path) { v86_bin = options.wasm_path; - const slash = v86_bin.lastIndexOf("/"); - const dir = slash === -1 ? "" : v86_bin.substr(0, slash); - v86_bin_fallback = dir + "/" + v86_bin_fallback; + v86_bin_fallback = v86_bin.replace("v86.wasm", "v86-fallback.wasm"); } else if(typeof window === "undefined" && typeof __dirname === "string") { From 181e1a64422555053dcf3263e1a62e20c8e7abbd Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 31 Mar 2025 20:35:33 +0700 Subject: [PATCH 016/301] net_device_type: take from profile if not specified in query string --- src/browser/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/main.js b/src/browser/main.js index 9bdd80ea..6fed55e1 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1829,7 +1829,7 @@ import { log_data } from "../log.js"; settings.acpi = query_args.has("acpi") ? bool_arg(query_args.get("acpi")) : settings.acpi; settings.use_bochs_bios = query_args.get("bios") === "bochs"; - settings.net_device_type = query_args.get("net_device_type") === "virtio" ? "virtio" : "ne2k"; + settings.net_device_type = query_args.get("net_device_type") || settings.net_device_type; } settings.relay_url = query_args.get("relay_url"); From 1ed0f28218e9d5a8a3700edf3ab2a5565b3203e1 Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 31 Mar 2025 20:40:11 +0700 Subject: [PATCH 017/301] test fiwix --- tests/full/run.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/full/run.js b/tests/full/run.js index c2e8f319..0dba0795 100755 --- a/tests/full/run.js +++ b/tests/full/run.js @@ -813,6 +813,21 @@ if(cluster.isPrimary) "Welcome to Crazierl:", ], }, + { + name: "Fiwix", + skip_if_disk_image_missing: true, + timeout: 2 * 60, + memory_size: 512 * 1024 * 1024, + hda: root_path + "/images/FiwixOS-3.4-i386.img", + expect_graphical_mode: true, + expect_mouse_registered: true, + expected_texts: [ + "(root):~#", + ], + actions: [ + { on_text: "(root):~#", run: "/usr/games/lsdoom\n" }, + ], + }, { name: "Linux with Postgres", skip_if_disk_image_missing: true, From 1445fdf244a86c1d438b80abc0fb217c2b1f23a8 Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 31 Mar 2025 20:41:20 +0700 Subject: [PATCH 018/301] add 9legacy --- src/browser/main.js | 16 ++++++++++++++-- tests/full/run.js | 21 +++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/browser/main.js b/src/browser/main.js index 6fed55e1..17473683 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1087,6 +1087,18 @@ import { log_data } from "../log.js"; name: "9front", homepage: "https://9front.org/", }, + { + id: "9legacy", + memory_size: 512 * 1024 * 1024, + hda: { + url: host + "9legacy.img", + async: false, + size: 16000000, + }, + name: "9legacy", + homepage: "http://www.9legacy.org/", + //net_device_type: "none", + }, { id: "mobius", fda: { @@ -2024,11 +2036,11 @@ import { log_data } from "../log.js"; }, CLEAR_STATS ? 5000 : 1000); } - if(["dsl", "helenos", "android", "android4", "redox", "beos"].includes(profile?.id)) + if(["dsl", "helenos", "android", "android4", "redox", "beos", "9legacy"].includes(profile?.id)) { setTimeout(() => { // hack: Start automatically - emulator.keyboard_send_text("\n"); + emulator.keyboard_send_text(profile.id === "9legacy" ? "1\n" : "\n"); }, 3000); } diff --git a/tests/full/run.js b/tests/full/run.js index 0dba0795..cdc92e94 100755 --- a/tests/full/run.js +++ b/tests/full/run.js @@ -828,6 +828,26 @@ if(cluster.isPrimary) { on_text: "(root):~#", run: "/usr/games/lsdoom\n" }, ], }, + { + name: "9legacy", + use_small_bios: true, // has issues with 256k bios + skip_if_disk_image_missing: true, + net_device: { type: "none" }, // if netdevice is found, waits for dhcp before starting desktop + timeout: 5 * 60, + memory_size: 512 * 1024 * 1024, + hda: root_path + "/images/9legacy.img", + expect_graphical_mode: true, + expect_mouse_registered: true, + expected_texts: [ + "Selection:", + ], + actions: [ + { on_text: "Selection:", run: "1\n" }, + ], + expected_serial_text: [ + "init: starting", + ], + }, { name: "Linux with Postgres", skip_if_disk_image_missing: true, @@ -1073,6 +1093,7 @@ function run_test(test, done) settings.acpi = test.acpi; settings.boot_order = test.boot_order; settings.cpuid_level = test.cpuid_level; + settings.net_device = test.net_device; settings.disable_jit = +process.env.DISABLE_JIT; if(test.expected_texts) From 9abf526271b58215ba888f42c8f687d850a50a05 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sun, 6 Apr 2025 21:51:30 +0700 Subject: [PATCH 019/301] add ipxe & netboot.xyz (#554, #1251) --- src/browser/main.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/browser/main.js b/src/browser/main.js index 17473683..e2033af6 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1411,6 +1411,27 @@ import { log_data } from "../log.js"; }, homepage: "https://os64.blogspot.com/", }, + { + id: "ipxe", + name: "iPXE", + cdrom: { + url: host + "ipxe.iso", + size: 4194304, + async: false, + }, + homepage: "https://ipxe.org/", + }, + { + id: "netboot.xyz", + name: "iPXE", + cdrom: { + url: host + "netboot.xyz.iso", + size: 2398208, + async: false, + }, + homepage: "https://netboot.xyz/", + net_device_type: "virtio", + }, ]; if(DEBUG) From c11805a70a376b3684fa65a37f66838325e47007 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sun, 6 Apr 2025 22:56:06 +0700 Subject: [PATCH 020/301] minor cleanup of imports --- debug.html | 2 +- eslint.config.mjs | 2 -- gen/generate_analyzer.js | 6 ++--- gen/generate_interpreter.js | 6 ++--- gen/generate_jit.js | 7 +++--- gen/util.js | 6 ----- gen/x86_table.js | 2 -- lib/9p.js | 8 +++++-- lib/filesystem.js | 3 ++- lib/jor1k.js | 27 ----------------------- lib/marshall.js | 18 +++++++-------- src/acpi.js | 2 +- src/apic.js | 3 +-- src/browser/inbrowser_network.js | 1 - src/config.js | 6 ----- src/cpu.js | 3 +-- src/io.js | 1 - src/pci.js | 1 - src/rtc.js | 3 +-- src/virtio_balloon.js | 2 +- src/virtio_console.js | 3 +-- src/virtio_net.js | 2 +- tests/expect/run.js | 4 +--- tests/nasm/create_tests.js | 1 - tests/rust/verify-wasmgen-dummy-output.js | 1 - 25 files changed, 35 insertions(+), 85 deletions(-) diff --git a/debug.html b/debug.html index 8d6b5038..e99fc7da 100644 --- a/debug.html +++ b/debug.html @@ -9,7 +9,7 @@ - + diff --git a/eslint.config.mjs b/eslint.config.mjs index 47f86752..d30ddd2c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,4 +1,3 @@ - export default [ { "languageOptions": { @@ -31,7 +30,6 @@ export default [ "Response": "readonly", "WebSocket": "readonly", "Blob": "readonly", - "Blob": "readonly", "File": "readonly", "XMLHttpRequest": "readonly", "URLSearchParams": "readonly", diff --git a/gen/generate_analyzer.js b/gen/generate_analyzer.js index 103e2a31..a75cfe03 100755 --- a/gen/generate_analyzer.js +++ b/gen/generate_analyzer.js @@ -7,13 +7,13 @@ import path from "node:path"; import url from "node:url"; import x86_table from "./x86_table.js"; -const rust_ast = await import("./rust_ast.js"); -const { hex, mkdirpSync, get_switch_value, get_switch_exist, finalize_table_rust } = await import("./util.js"); +import * as rust_ast from "./rust_ast.js"; +import { hex, get_switch_value, get_switch_exist, finalize_table_rust } from "./util.js"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const OUT_DIR = path.join(__dirname, "..", "src/rust/gen/"); -mkdirpSync(OUT_DIR); +fs.mkdirSync(OUT_DIR, { recursive: true }); const table_arg = get_switch_value("--table"); const gen_all = get_switch_exist("--all"); diff --git a/gen/generate_interpreter.js b/gen/generate_interpreter.js index 8015d0da..54d68598 100755 --- a/gen/generate_interpreter.js +++ b/gen/generate_interpreter.js @@ -7,13 +7,13 @@ import path from "node:path"; import url from "node:url"; import x86_table from "./x86_table.js"; -const rust_ast = await import("./rust_ast.js"); -const { hex, mkdirpSync, get_switch_value, get_switch_exist, finalize_table_rust } = await import("./util.js"); +import * as rust_ast from "./rust_ast.js"; +import { hex, get_switch_value, get_switch_exist, finalize_table_rust } from "./util.js"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const OUT_DIR = path.join(__dirname, "..", "src/rust/gen/"); -mkdirpSync(OUT_DIR); +fs.mkdirSync(OUT_DIR, { recursive: true }); const table_arg = get_switch_value("--table"); const gen_all = get_switch_exist("--all"); diff --git a/gen/generate_jit.js b/gen/generate_jit.js index b237a368..e9cdc357 100755 --- a/gen/generate_jit.js +++ b/gen/generate_jit.js @@ -7,12 +7,13 @@ import path from "node:path"; import url from "node:url"; import x86_table from "./x86_table.js"; -const rust_ast = await import("./rust_ast.js"); -const { hex, mkdirpSync, get_switch_value, get_switch_exist, finalize_table_rust } = await import("./util.js"); +import * as rust_ast from "./rust_ast.js"; +import { hex, get_switch_value, get_switch_exist, finalize_table_rust } from "./util.js"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const OUT_DIR = path.join(__dirname, "..", "src/rust/gen/"); -mkdirpSync(OUT_DIR); + +fs.mkdirSync(OUT_DIR, { recursive: true }); const table_arg = get_switch_value("--table"); const gen_all = get_switch_exist("--all"); diff --git a/gen/util.js b/gen/util.js index a01a8eda..ed59ec99 100644 --- a/gen/util.js +++ b/gen/util.js @@ -4,7 +4,6 @@ import fs from "node:fs"; import path from "node:path"; import process from "node:process"; - const CYAN_FMT = "\x1b[36m%s\x1b[0m"; export function hex(n, pad) @@ -15,11 +14,6 @@ export function hex(n, pad) return s; } -export function mkdirpSync(dir) -{ - fs.mkdirSync(dir, { recursive: true }); -} - export function get_switch_value(arg_switch) { const argv = process.argv; diff --git a/gen/x86_table.js b/gen/x86_table.js index 5325b4f6..a360cba9 100644 --- a/gen/x86_table.js +++ b/gen/x86_table.js @@ -1,7 +1,5 @@ "use strict"; -const { hex } = await import("./util.js"); - // http://ref.x86asm.net/coder32.html const zf = 1 << 6; diff --git a/lib/9p.js b/lib/9p.js index c988d43d..6b4febd9 100644 --- a/lib/9p.js +++ b/lib/9p.js @@ -6,11 +6,10 @@ "use strict"; -import { TRACK_FILENAMES } from "../src/config.js"; import { VirtIO, VIRTIO_F_VERSION_1, VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_RING_INDIRECT_DESC } from "../src/virtio.js"; import { S_IFREG, S_IFDIR, STATUS_UNLINKED } from "./filesystem.js"; import { hex8, message } from "./jor1k.js"; -import { marshall, texten } from "./marshall.js"; +import * as marshall from "../lib/marshall.js"; import { range } from "../src/lib.js"; import { dbg_assert } from "../src/log.js"; @@ -20,6 +19,11 @@ import { CPU } from "../src/cpu.js"; import { BusConnector } from "../src/bus.js"; import { FS } from "./filesystem.js"; +/** + * @const + * More accurate filenames in 9p debug messages at the cost of performance. + */ +const TRACK_FILENAMES = false; // Feature bit (bit position) for mount tag. const VIRTIO_9P_F_MOUNT_TAG = 0; diff --git a/lib/filesystem.js b/lib/filesystem.js index 486f53d6..9c03f40f 100644 --- a/lib/filesystem.js +++ b/lib/filesystem.js @@ -8,7 +8,7 @@ import { LOG_9P } from "../src/const.js"; import { h } from "../src/lib.js"; import { dbg_assert, dbg_log } from "../src/log.js"; -import { marshall, texten } from "../lib/marshall.js"; +import * as marshall from "../lib/marshall.js"; import { message } from "./jor1k.js"; import { EEXIST, ENOTEMPTY, ENOENT, EPERM } from "./9p.js"; import { P9_LOCK_SUCCESS, P9_LOCK_BLOCKED, P9_LOCK_TYPE_UNLCK, P9_LOCK_TYPE_WRLCK, P9_LOCK_TYPE_RDLCK } from "./9p.js"; @@ -41,6 +41,7 @@ export const STATUS_ON_STORAGE = 0x2; export const STATUS_UNLINKED = 0x4; export const STATUS_FORWARDING = 0x5; +const texten = new TextEncoder(); /** @const */ var JSONFS_VERSION = 3; diff --git a/lib/jor1k.js b/lib/jor1k.js index 6114e13a..0ad21e41 100644 --- a/lib/jor1k.js +++ b/lib/jor1k.js @@ -7,33 +7,6 @@ import { dbg_log } from "../src/log.js"; // jor1k compatibility -var VIRTIO_MAGIC_REG = 0x0; -var VIRTIO_VERSION_REG = 0x4; -var VIRTIO_DEVICE_REG = 0x8; -var VIRTIO_VENDOR_REG = 0xc; -var VIRTIO_HOSTFEATURES_REG = 0x10; -var VIRTIO_HOSTFEATURESSEL_REG = 0x14; -var VIRTIO_GUESTFEATURES_REG = 0x20; -var VIRTIO_GUESTFEATURESSEL_REG = 0x24; -var VIRTIO_GUEST_PAGE_SIZE_REG = 0x28; -var VIRTIO_QUEUESEL_REG = 0x30; -var VIRTIO_QUEUENUMMAX_REG = 0x34; -var VIRTIO_QUEUENUM_REG = 0x38; -var VIRTIO_QUEUEALIGN_REG = 0x3C; -var VIRTIO_QUEUEPFN_REG = 0x40; -var VIRTIO_QUEUENOTIFY_REG = 0x50; -var VIRTIO_INTERRUPTSTATUS_REG = 0x60; -var VIRTIO_INTERRUPTACK_REG = 0x64; -var VIRTIO_STATUS_REG = 0x70; - -/** @const */ -var VRING_DESC_F_NEXT = 1; /* This marks a buffer as continuing via the next field. */ -/** @const */ -var VRING_DESC_F_WRITE = 2; /* This marks a buffer as write-only (otherwise read-only). */ -/** @const */ -var VRING_DESC_F_INDIRECT = 4; /* This means the buffer contains a list of buffer descriptors. */ - - export function hex8(n) { return h(n); diff --git a/lib/marshall.js b/lib/marshall.js index 926ba5af..2a7ec840 100644 --- a/lib/marshall.js +++ b/lib/marshall.js @@ -7,13 +7,11 @@ import { message } from "./jor1k.js"; -export var marshall = {}; - -export const textde = new TextDecoder(); -export const texten = new TextEncoder(); +const textde = new TextDecoder(); +const texten = new TextEncoder(); // Inserts data from an array to a byte aligned struct in memory -marshall.Marshall = function(typelist, input, struct, offset) { +export function Marshall(typelist, input, struct, offset) { var item; var size = 0; for(var i=0; i < typelist.length; i++) { @@ -63,7 +61,7 @@ marshall.Marshall = function(typelist, input, struct, offset) { struct[lengthoffset+1] = (length >> 8) & 0xFF; break; case "Q": - marshall.Marshall(["b", "w", "d"], [item.type, item.version, item.path], struct, offset); + Marshall(["b", "w", "d"], [item.type, item.version, item.path], struct, offset); offset += 13; size += 13; break; @@ -73,11 +71,11 @@ marshall.Marshall = function(typelist, input, struct, offset) { } } return size; -}; +} // Extracts data from a byte aligned struct in memory to an array -marshall.Unmarshall = function(typelist, struct, state) { +export function Unmarshall(typelist, struct, state) { let offset = state.offset; var output = []; for(var i=0; i < typelist.length; i++) { @@ -114,7 +112,7 @@ marshall.Unmarshall = function(typelist, struct, state) { break; case "Q": state.offset = offset; - const qid = marshall.Unmarshall(["b", "w", "d"], struct, state); + const qid = Unmarshall(["b", "w", "d"], struct, state); offset = state.offset; output.push({ type: qid[0], @@ -129,4 +127,4 @@ marshall.Unmarshall = function(typelist, struct, state) { } state.offset = offset; return output; -}; +} diff --git a/src/acpi.js b/src/acpi.js index 00106e48..dbeab02a 100644 --- a/src/acpi.js +++ b/src/acpi.js @@ -11,7 +11,7 @@ import { dbg_log, dbg_assert } from "./log.js"; import { CPU } from "./cpu.js"; /** @const */ -export const PMTIMER_FREQ_SECONDS = 3579545; +const PMTIMER_FREQ_SECONDS = 3579545; /** * @constructor diff --git a/src/apic.js b/src/apic.js index 564face7..d8760cfc 100644 --- a/src/apic.js +++ b/src/apic.js @@ -51,8 +51,7 @@ export const DESTINATION_MODES = ["physical", "logical"]; * @constructor * @param {CPU} cpu */ -export -function APIC(cpu) +export function APIC(cpu) { /** @type {CPU} */ this.cpu = cpu; diff --git a/src/browser/inbrowser_network.js b/src/browser/inbrowser_network.js index 9144eef1..9ec1ea23 100644 --- a/src/browser/inbrowser_network.js +++ b/src/browser/inbrowser_network.js @@ -1,6 +1,5 @@ "use strict"; - // For Types Only import { BusConnector } from "../bus.js"; diff --git a/src/config.js b/src/config.js index a0ae8e12..894752e3 100644 --- a/src/config.js +++ b/src/config.js @@ -27,12 +27,6 @@ export var DUMP_GENERATED_WASM = false; */ export var DUMP_UNCOMPILED_ASSEMBLY = false; -/** - * @const - * More accurate filenames in 9p debug messages at the cost of performance. - */ -export var TRACK_FILENAMES = false; - export var LOG_LEVEL = LOG_ALL & ~LOG_PS2 & ~LOG_PIT & ~LOG_VIRTIO & ~LOG_9P & ~LOG_PIC & ~LOG_DMA & ~LOG_SERIAL & ~LOG_NET & ~LOG_FLOPPY & ~LOG_DISK & ~LOG_VGA & ~LOG_SB16; diff --git a/src/cpu.js b/src/cpu.js index bb2ae9d2..874de469 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -15,7 +15,7 @@ import { dbg_assert, dbg_log } from "./log.js"; import { SB16 } from "./sb16.js"; import { IOAPIC } from "./ioapic.js"; -import { APIC, APIC_LOG_VERBOSE } from "./apic.js"; +import { APIC } from "./apic.js"; import { ACPI } from "./acpi.js"; import { PIT } from "./pit.js"; import { DMA } from "./dma.js"; @@ -36,7 +36,6 @@ import { Virtio9p } from "../lib/9p.js"; import { load_kernel } from "./kernel.js"; - import { RTC, CMOS_EQUIPMENT_INFO, CMOS_BIOS_SMP_COUNT, diff --git a/src/io.js b/src/io.js index 29ad137b..923aa164 100644 --- a/src/io.js +++ b/src/io.js @@ -5,7 +5,6 @@ import { LOG_ALL_IO } from "./config.js"; import { h } from "./lib.js"; import { dbg_assert, dbg_log } from "./log.js"; - // For Types Only import { CPU } from "./cpu.js"; diff --git a/src/pci.js b/src/pci.js index de0c16f2..da43efa0 100644 --- a/src/pci.js +++ b/src/pci.js @@ -7,7 +7,6 @@ import { dbg_assert, dbg_log } from "./log.js"; // For Types Only import { CPU } from "./cpu.js"; - // http://wiki.osdev.org/PCI export const diff --git a/src/rtc.js b/src/rtc.js index e0fd43f8..404ff4c7 100644 --- a/src/rtc.js +++ b/src/rtc.js @@ -61,8 +61,7 @@ export const BOOT_ORDER_FD_FIRST = 0x321; * @constructor * @param {CPU} cpu */ -export -function RTC(cpu) +export function RTC(cpu) { /** @const @type {CPU} */ this.cpu = cpu; diff --git a/src/virtio_balloon.js b/src/virtio_balloon.js index 07ff3763..59fce932 100644 --- a/src/virtio_balloon.js +++ b/src/virtio_balloon.js @@ -5,7 +5,7 @@ import { LOG_PCI } from "./const.js"; import { dbg_log } from "./log.js"; import { VirtIO, VIRTIO_F_VERSION_1 } from "./virtio.js"; -import { marshall } from "../lib/marshall.js"; +import * as marshall from "../lib/marshall.js"; // For Types Only import { CPU } from "./cpu.js"; diff --git a/src/virtio_console.js b/src/virtio_console.js index c55c783e..177e8529 100644 --- a/src/virtio_console.js +++ b/src/virtio_console.js @@ -2,7 +2,7 @@ import { dbg_assert } from "./log.js"; import { VirtIO, VIRTIO_F_VERSION_1 } from "./virtio.js"; -import { marshall } from "../lib/marshall.js"; +import * as marshall from "../lib/marshall.js"; // For Types Only import { CPU } from "./cpu.js"; @@ -10,7 +10,6 @@ import { BusConnector } from "./bus.js"; // https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-2900003 - const VIRTIO_CONSOLE_DEVICE_READY = 0; const VIRTIO_CONSOLE_DEVICE_ADD = 1; const VIRTIO_CONSOLE_DEVICE_REMOVE = 2; diff --git a/src/virtio_net.js b/src/virtio_net.js index 2d0829f7..495c8284 100644 --- a/src/virtio_net.js +++ b/src/virtio_net.js @@ -5,7 +5,7 @@ import { dbg_assert } from "./log.js"; import { VirtIO, VIRTIO_F_VERSION_1 } from "./virtio.js"; import { format_mac } from "./ne2k.js"; -import { marshall } from "../lib/marshall.js"; +import * as marshall from "../lib/marshall.js"; // For Types Only import { CPU } from "./cpu.js"; diff --git a/tests/expect/run.js b/tests/expect/run.js index 83888911..0bf78c04 100755 --- a/tests/expect/run.js +++ b/tests/expect/run.js @@ -5,10 +5,8 @@ import fs from "node:fs"; import path from "node:path"; import assert from "node:assert/strict"; import url from "node:url"; -import wabtfactory from "../../build/libwabt.cjs"; - - import { spawnSync } from "node:child_process"; +import wabtfactory from "../../build/libwabt.cjs"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); diff --git a/tests/nasm/create_tests.js b/tests/nasm/create_tests.js index 9967955d..e820f31d 100755 --- a/tests/nasm/create_tests.js +++ b/tests/nasm/create_tests.js @@ -12,7 +12,6 @@ import { execFile as execFileAsync } from "node:child_process"; import encodings from "../../gen/x86_table.js"; import Rand from "./rand.js"; - const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); // number of tests per instruction diff --git a/tests/rust/verify-wasmgen-dummy-output.js b/tests/rust/verify-wasmgen-dummy-output.js index 85494334..c7a94308 100755 --- a/tests/rust/verify-wasmgen-dummy-output.js +++ b/tests/rust/verify-wasmgen-dummy-output.js @@ -1,7 +1,6 @@ #!/usr/bin/env node "use strict"; - import fs from "node:fs"; import path from "node:path"; import url from "node:url"; From 47f0bf533cf6e6eb60a486e394726e079a956f64 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sun, 6 Apr 2025 23:06:02 +0700 Subject: [PATCH 021/301] replace jor1k's message.Debug with dbg_log --- Makefile | 2 +- lib/9p.js | 74 ++++++++++++++++++++--------------------- lib/filesystem.js | 29 ++++++++-------- lib/jor1k.js | 85 ----------------------------------------------- lib/marshall.js | 7 ++-- 5 files changed, 54 insertions(+), 143 deletions(-) delete mode 100644 lib/jor1k.js diff --git a/Makefile b/Makefile index 46b52c45..eee99a0d 100644 --- a/Makefile +++ b/Makefile @@ -84,7 +84,7 @@ CORE_FILES=cjs.js const.js config.js io.js main.js lib.js buffer.js ide.js pci.j state.js ne2k.js sb16.js virtio.js virtio_console.js virtio_net.js virtio_balloon.js \ bus.js log.js cpu.js debug.js \ elf.js kernel.js -LIB_FILES=9p.js filesystem.js jor1k.js marshall.js +LIB_FILES=9p.js filesystem.js marshall.js BROWSER_FILES=screen.js keyboard.js mouse.js speaker.js serial.js \ network.js starter.js worker_bus.js dummy_screen.js \ inbrowser_network.js fake_network.js wisp_network.js fetch_network.js \ diff --git a/lib/9p.js b/lib/9p.js index 6b4febd9..e76b52b4 100644 --- a/lib/9p.js +++ b/lib/9p.js @@ -6,13 +6,13 @@ "use strict"; +import { LOG_9P } from "./../src/const.js"; import { VirtIO, VIRTIO_F_VERSION_1, VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_RING_INDIRECT_DESC } from "../src/virtio.js"; import { S_IFREG, S_IFDIR, STATUS_UNLINKED } from "./filesystem.js"; -import { hex8, message } from "./jor1k.js"; import * as marshall from "../lib/marshall.js"; import { range } from "../src/lib.js"; -import { dbg_assert } from "../src/log.js"; - +import { dbg_log, dbg_assert } from "../src/log.js"; +import { h } from "../src/lib.js"; // For Types Only import { CPU } from "../src/cpu.js"; @@ -251,7 +251,7 @@ Virtio9p.prototype.BuildReply = function(id, tag, payloadsize) { dbg_assert(payloadsize >= 0, "9P: Negative payload size"); marshall.Marshall(["w", "b", "h"], [payloadsize+7, id+1, tag], this.replybuffer, 0); if((payloadsize+7) >= this.replybuffer.length) { - message.Debug("Error in 9p: payloadsize exceeds maximum length"); + dbg_log("Error in 9p: payloadsize exceeds maximum length", LOG_9P); } //for(var i=0; i} 0 if success, or -errno if failured. */ FS.prototype.Rename = async function(olddirid, oldname, newdirid, newname) { - // message.Debug("Rename " + oldname + " to " + newname); + // dbg_log("Rename " + oldname + " to " + newname, LOG_9P); if((olddirid === newdirid) && (oldname === newname)) { return 0; } @@ -1048,7 +1045,7 @@ FS.prototype.Unlink = function(parentid, name) { const idx = this.Search(parentid, name); const inode = this.inodes[idx]; const parent_inode = this.inodes[parentid]; - //message.Debug("Unlink " + inode.name); + //dbg_log("Unlink " + inode.name, LOG_9P); // forward if necessary if(this.is_forwarder(parent_inode)) @@ -1181,7 +1178,7 @@ FS.prototype.ChangeSize = async function(idx, newsize) { var inode = this.GetInode(idx); var temp = await this.get_data(idx, 0, inode.size); - //message.Debug("change size to: " + newsize); + //dbg_log("change size to: " + newsize, LOG_9P); if(newsize === inode.size) return; var data = new Uint8Array(newsize); inode.size = newsize; @@ -1308,24 +1305,24 @@ FS.prototype.Check = function() { var inode = this.GetInode(i); if(inode.nlinks < 0) { - message.Debug("Error in filesystem: negative nlinks=" + inode.nlinks + " at id =" + i); + dbg_log("Error in filesystem: negative nlinks=" + inode.nlinks + " at id =" + i, LOG_9P); } if(this.IsDirectory(i)) { const inode = this.GetInode(i); if(this.IsDirectory(i) && this.GetParent(i) < 0) { - message.Debug("Error in filesystem: negative parent id " + i); + dbg_log("Error in filesystem: negative parent id " + i, LOG_9P); } for(const [name, id] of inode.direntries) { if(name.length === 0) { - message.Debug("Error in filesystem: inode with no name and id " + id); + dbg_log("Error in filesystem: inode with no name and id " + id, LOG_9P); } for(const c of name) { if(c < 32) { - message.Debug("Error in filesystem: Unallowed char in filename"); + dbg_log("Error in filesystem: Unallowed char in filename", LOG_9P); } } } diff --git a/lib/jor1k.js b/lib/jor1k.js deleted file mode 100644 index 0ad21e41..00000000 --- a/lib/jor1k.js +++ /dev/null @@ -1,85 +0,0 @@ -"use strict"; - -import { LOG_9P } from "../src/const.js"; -import { h } from "../src/lib.js"; -import { dbg_log } from "../src/log.js"; - - -// jor1k compatibility - -export function hex8(n) -{ - return h(n); -} - -export var message = {}; - -/** @param {...string} log */ -message.Debug = function(log) -{ - dbg_log([].slice.apply(arguments).join(" "), LOG_9P); -}; - -message.Abort = function() -{ - if(DEBUG) - { - throw new Error("message.Abort()"); - } -}; - - -// XXX: Should go through emulator interface -var LoadBinaryResource; - -if(typeof XMLHttpRequest !== "undefined") -{ - LoadBinaryResource = function(url, OnSuccess, OnError) { - var req = new XMLHttpRequest(); - req.open("GET", url, true); - req.responseType = "arraybuffer"; - req.onreadystatechange = function () { - if(req.readyState !== 4) { - return; - } - if((req.status !== 200) && (req.status !== 0)) { - OnError("Error: Could not load file " + url); - return; - } - var arrayBuffer = req.response; - if(arrayBuffer) { - OnSuccess(arrayBuffer); - } else { - OnError("Error: No data received from: " + url); - } - }; - /* - req.onload = function(e) - { - var arrayBuffer = req.response; - if (arrayBuffer) { - OnLoadFunction(arrayBuffer); - } - }; - */ - req.send(null); - }; -} -else -{ - LoadBinaryResource = function(url, OnSuccess, OnError) - { - //console.log(url); - import("node:" + "fs").then(fs => fs["readFile"](url, function(err, data) - { - if(err) - { - OnError(err); - } - else - { - OnSuccess(data.buffer); - } - })); - }; -} diff --git a/lib/marshall.js b/lib/marshall.js index 2a7ec840..b514e55a 100644 --- a/lib/marshall.js +++ b/lib/marshall.js @@ -2,10 +2,9 @@ // ------------------ Marshall --------------------- // ------------------------------------------------- // helper functions for virtio and 9p. - "use strict"; -import { message } from "./jor1k.js"; +import { dbg_log } from "./../src/log.js"; const textde = new TextDecoder(); const texten = new TextEncoder(); @@ -66,7 +65,7 @@ export function Marshall(typelist, input, struct, offset) { size += 13; break; default: - message.Debug("Marshall: Unknown type=" + typelist[i]); + dbg_log("Marshall: Unknown type=" + typelist[i]); break; } } @@ -121,7 +120,7 @@ export function Unmarshall(typelist, struct, state) { }); break; default: - message.Debug("Error in Unmarshall: Unknown type=" + typelist[i]); + dbg_log("Error in Unmarshall: Unknown type=" + typelist[i]); break; } } From 48a71b04a1ef698545b52f5192bc16c0afe88953 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sun, 6 Apr 2025 23:13:18 +0700 Subject: [PATCH 022/301] inlinde debug.js into cpu.js --- Makefile | 2 +- src/browser/starter.js | 1 - src/cpu.js | 675 ++++++++++++++++++++++++++++++++++++++++- src/debug.js | 675 ----------------------------------------- 4 files changed, 672 insertions(+), 681 deletions(-) delete mode 100644 src/debug.js diff --git a/Makefile b/Makefile index eee99a0d..c2d9875d 100644 --- a/Makefile +++ b/Makefile @@ -82,7 +82,7 @@ CORE_FILES=cjs.js const.js config.js io.js main.js lib.js buffer.js ide.js pci.j memory.js dma.js pit.js vga.js ps2.js rtc.js uart.js \ acpi.js apic.js ioapic.js \ state.js ne2k.js sb16.js virtio.js virtio_console.js virtio_net.js virtio_balloon.js \ - bus.js log.js cpu.js debug.js \ + bus.js log.js cpu.js \ elf.js kernel.js LIB_FILES=9p.js filesystem.js marshall.js BROWSER_FILES=screen.js keyboard.js mouse.js speaker.js serial.js \ diff --git a/src/browser/starter.js b/src/browser/starter.js index 3881c6dc..5c0de3c2 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -27,7 +27,6 @@ import { EEXIST, ENOENT } from "../../lib/9p.js"; // Decorates CPU -import "../debug.js"; import "../memory.js"; import "../state.js"; diff --git a/src/cpu.js b/src/cpu.js index 874de469..4f55e064 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -1,16 +1,20 @@ "use strict"; import { - LOG_CPU, REG_CS, + LOG_CPU, LOG_BIOS, FW_CFG_SIGNATURE, FW_CFG_SIGNATURE_QEMU, WASM_TABLE_SIZE, WASM_TABLE_OFFSET, FW_CFG_ID, FW_CFG_RAM_SIZE, FW_CFG_NB_CPUS, FW_CFG_MAX_CPUS, FW_CFG_NUMA, FW_CFG_FILE_DIR, FW_CFG_FILE_START, - FW_CFG_CUSTOM_START, REG_EAX, REG_EBX, FLAGS_DEFAULT, - MMAP_BLOCK_BITS, MMAP_BLOCK_SIZE, MMAP_MAX + FW_CFG_CUSTOM_START, FLAGS_DEFAULT, + MMAP_BLOCK_BITS, MMAP_BLOCK_SIZE, MMAP_MAX, + REG_ESP, REG_EBP, REG_ESI, REG_EAX, REG_EBX, REG_ECX, REG_EDX, REG_EDI, + REG_CS, REG_DS, REG_ES, REG_FS, REG_GS, REG_SS, CR0_PG, CR4_PAE, REG_LDTR, + FLAG_VM, FLAG_INTERRUPT, FLAG_CARRY, FLAG_ADJUST, FLAG_ZERO, FLAG_SIGN, FLAG_TRAP, + FLAG_DIRECTION, FLAG_OVERFLOW, FLAG_PARITY, } from "./const.js"; import { DUMP_GENERATED_WASM, DUMP_UNCOMPILED_ASSEMBLY } from "./config.js"; -import { h, view, Bitmap } from "./lib.js"; +import { h, view, pads, Bitmap } from "./lib.js"; import { dbg_assert, dbg_log } from "./log.js"; import { SB16 } from "./sb16.js"; @@ -1741,3 +1745,666 @@ CPU.prototype.device_lower_irq = function(i) this.devices.ioapic.clear_irq(i); } }; + +CPU.prototype.debug_init = function() +{ + var cpu = this; + var debug = {}; + this.debug = debug; + + debug.init = function() + { + if(!DEBUG) return; + + if(cpu.io) + { + // write seabios debug output to console + var seabios_debug = ""; + + cpu.io.register_write(0x402, this, handle); // seabios + cpu.io.register_write(0x500, this, handle); // vgabios + } + + function handle(out_byte) + { + if(out_byte === 10) + { + dbg_log(seabios_debug, LOG_BIOS); + seabios_debug = ""; + } + else + { + seabios_debug += String.fromCharCode(out_byte); + } + } + }; + + debug.get_regs_short = get_regs_short; + debug.dump_regs = dump_regs_short; + debug.get_state = get_state; + debug.dump_state = dump_state; + debug.dump_stack = dump_stack; + + debug.dump_page_structures = dump_page_structures; + debug.dump_gdt_ldt = dump_gdt_ldt; + debug.dump_idt = dump_idt; + + debug.get_memory_dump = get_memory_dump; + debug.memory_hex_dump = memory_hex_dump; + debug.used_memory_dump = used_memory_dump; + + function dump_stack(start, end) + { + if(!DEBUG) return; + + var esp = cpu.reg32[REG_ESP]; + dbg_log("========= STACK =========="); + + if(end >= start || end === undefined) + { + start = 5; + end = -5; + } + + for(var i = start; i > end; i--) + { + var line = " "; + + if(!i) line = "=> "; + + line += h(i, 2) + " | "; + + dbg_log(line + h(esp + 4 * i, 8) + " | " + h(cpu.read32s(esp + 4 * i) >>> 0)); + } + } + + function get_state(where) + { + if(!DEBUG) return; + + var mode = cpu.protected_mode[0] ? "prot" : "real"; + var vm = (cpu.flags[0] & FLAG_VM) ? 1 : 0; + var flags = cpu.get_eflags(); + var iopl = cpu.getiopl(); + var cpl = cpu.cpl[0]; + var cs_eip = h(cpu.sreg[REG_CS], 4) + ":" + h(cpu.get_real_eip() >>> 0, 8); + var ss_esp = h(cpu.sreg[REG_SS], 4) + ":" + h(cpu.reg32[REG_ES] >>> 0, 8); + var op_size = cpu.is_32[0] ? "32" : "16"; + var if_ = (cpu.flags[0] & FLAG_INTERRUPT) ? 1 : 0; + + var flag_names = { + [FLAG_CARRY]: "c", + [FLAG_PARITY]: "p", + [FLAG_ADJUST]: "a", + [FLAG_ZERO]: "z", + [FLAG_SIGN]: "s", + [FLAG_TRAP]: "t", + [FLAG_INTERRUPT]: "i", + [FLAG_DIRECTION]: "d", + [FLAG_OVERFLOW]: "o", + }; + var flag_string = ""; + + for(var i = 0; i < 16; i++) + { + if(flag_names[1 << i]) + { + if(flags & 1 << i) + { + flag_string += flag_names[1 << i]; + } + else + { + flag_string += " "; + } + } + } + + return ("mode=" + mode + "/" + op_size + " paging=" + (+((cpu.cr[0] & CR0_PG) !== 0)) + + " pae=" + (+((cpu.cr[4] & CR4_PAE) !== 0)) + + " iopl=" + iopl + " cpl=" + cpl + " if=" + if_ + " cs:eip=" + cs_eip + + " cs_off=" + h(cpu.get_seg_cs() >>> 0, 8) + + " flgs=" + h(cpu.get_eflags() >>> 0, 6) + " (" + flag_string + ")" + + " ss:esp=" + ss_esp + + " ssize=" + (+cpu.stack_size_32[0]) + + (where ? " in " + where : "")); + } + + function dump_state(where) + { + if(!DEBUG) return; + + dbg_log(get_state(where), LOG_CPU); + } + + function get_regs_short() + { + if(!DEBUG) return; + + var + r32 = { "eax": REG_EAX, "ecx": REG_ECX, "edx": REG_EDX, "ebx": REG_EBX, + "esp": REG_ESP, "ebp": REG_EBP, "esi": REG_ESI, "edi": REG_EDI }, + r32_names = ["eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi"], + s = { "cs": REG_CS, "ds": REG_DS, "es": REG_ES, "fs": REG_FS, "gs": REG_GS, "ss": REG_SS }, + line1 = "", + line2 = ""; + + for(var i = 0; i < 4; i++) + { + line1 += r32_names[i] + "=" + h(cpu.reg32[r32[r32_names[i]]] >>> 0, 8) + " "; + line2 += r32_names[i+4] + "=" + h(cpu.reg32[r32[r32_names[i+4]]] >>> 0, 8) + " "; + } + + //line1 += " eip=" + h(cpu.get_real_eip() >>> 0, 8); + //line2 += " flg=" + h(cpu.get_eflags(), 8); + + line1 += " ds=" + h(cpu.sreg[REG_DS], 4) + " es=" + h(cpu.sreg[REG_ES], 4) + " fs=" + h(cpu.sreg[REG_FS], 4); + line2 += " gs=" + h(cpu.sreg[REG_GS], 4) + " cs=" + h(cpu.sreg[REG_CS], 4) + " ss=" + h(cpu.sreg[REG_SS], 4); + + return [line1, line2]; + } + + function dump_regs_short() + { + if(!DEBUG) return; + + var lines = get_regs_short(); + + dbg_log(lines[0], LOG_CPU); + dbg_log(lines[1], LOG_CPU); + } + + function dump_gdt_ldt() + { + if(!DEBUG) return; + + dbg_log("gdt: (len = " + h(cpu.gdtr_size[0]) + ")"); + dump_table(cpu.translate_address_system_read(cpu.gdtr_offset[0]), cpu.gdtr_size[0]); + + dbg_log("\nldt: (len = " + h(cpu.segment_limits[REG_LDTR]) + ")"); + dump_table(cpu.translate_address_system_read(cpu.segment_offsets[REG_LDTR]), cpu.segment_limits[REG_LDTR]); + + function dump_table(addr, size) + { + for(var i = 0; i < size; i += 8, addr += 8) + { + var base = cpu.read16(addr + 2) | + cpu.read8(addr + 4) << 16 | + cpu.read8(addr + 7) << 24, + + limit = cpu.read16(addr) | (cpu.read8(addr + 6) & 0xF) << 16, + access = cpu.read8(addr + 5), + flags = cpu.read8(addr + 6) >> 4, + flags_str = "", + dpl = access >> 5 & 3; + + if(!(access & 128)) + { + // present bit not set + //continue; + flags_str += "NP "; + } + else + { + flags_str += " P "; + } + + if(access & 16) + { + if(flags & 4) + { + flags_str += "32b "; + } + else + { + flags_str += "16b "; + } + + if(access & 8) + { + // executable + flags_str += "X "; + + if(access & 4) + { + flags_str += "C "; + } + } + else + { + // data + flags_str += "R "; + } + + flags_str += "RW "; + } + else + { + // system + flags_str += "sys: " + h(access & 15); + } + + if(flags & 8) + { + limit = limit << 12 | 0xFFF; + } + + dbg_log(h(i & ~7, 4) + " " + h(base >>> 0, 8) + " (" + h(limit >>> 0, 8) + " bytes) " + + flags_str + "; dpl = " + dpl + ", a = " + access.toString(2) + + ", f = " + flags.toString(2)); + } + } + } + + function dump_idt() + { + if(!DEBUG) return; + + for(var i = 0; i < cpu.idtr_size[0]; i += 8) + { + var addr = cpu.translate_address_system_read(cpu.idtr_offset[0] + i), + base = cpu.read16(addr) | cpu.read16(addr + 6) << 16, + selector = cpu.read16(addr + 2), + type = cpu.read8(addr + 5), + line, + dpl = type >> 5 & 3; + + if((type & 31) === 5) + { + line = "task gate "; + } + else if((type & 31) === 14) + { + line = "intr gate "; + } + else if((type & 31) === 15) + { + line = "trap gate "; + } + else + { + line = "invalid "; + } + + + if(type & 128) + { + line += " P"; + } + else + { + // present bit not set + //continue; + line += "NP"; + } + + + dbg_log(h(i >> 3, 4) + " " + h(base >>> 0, 8) + ", " + + h(selector, 4) + "; " + line + "; dpl = " + dpl + ", t = " + type.toString(2)); + } + } + + function load_page_entry(dword_entry, pae, is_directory) + { + if(!DEBUG) return; + + if(!(dword_entry & 1)) + { + // present bit not set + return false; + } + + var size = (dword_entry & 128) === 128, + address; + + if(size && !is_directory) + { + address = dword_entry & (pae ? 0xFFE00000 : 0xFFC00000); + } + else + { + address = dword_entry & 0xFFFFF000; + } + + return { + size: size, + global: (dword_entry & 256) === 256, + accessed: (dword_entry & 0x20) === 0x20, + dirty: (dword_entry & 0x40) === 0x40, + cache_disable : (dword_entry & 16) === 16, + user : (dword_entry & 4) === 4, + read_write : (dword_entry & 2) === 2, + address : address >>> 0 + }; + } + + function dump_page_structures() { + var pae = !!(cpu.cr[4] & CR4_PAE); + if(pae) + { + dbg_log("PAE enabled"); + + for(var i = 0; i < 4; i++) { + var addr = cpu.cr[3] + 8 * i; + var dword = cpu.read32s(addr); + if(dword & 1) + { + dump_page_directory(dword & 0xFFFFF000, true, i << 30); + } + } + } + else + { + dbg_log("PAE disabled"); + dump_page_directory(cpu.cr[3], false, 0); + } + } + + /* NOTE: PAE entries are 64-bits, we ignore the high half here. */ + function dump_page_directory(pd_addr, pae, start) + { + if(!DEBUG) return; + + var n = pae ? 512 : 1024; + var entry_size = pae ? 8 : 4; + var pd_shift = pae ? 21 : 22; + + for(var i = 0; i < n; i++) + { + var addr = pd_addr + i * entry_size, + dword = cpu.read32s(addr), + entry = load_page_entry(dword, pae, true); + + if(!entry) + { + continue; + } + + var flags = ""; + + flags += entry.size ? "S " : " "; + flags += entry.accessed ? "A " : " "; + flags += entry.cache_disable ? "Cd " : " "; + flags += entry.user ? "U " : " "; + flags += entry.read_write ? "Rw " : " "; + + if(entry.size) + { + dbg_log("=== " + h(start + (i << pd_shift) >>> 0, 8) + " -> " + + h(entry.address >>> 0, 8) + " | " + flags); + continue; + } + else + { + dbg_log("=== " + h(start + (i << pd_shift) >>> 0, 8) + " | " + flags); + } + + for(var j = 0; j < n; j++) + { + var sub_addr = entry.address + j * entry_size; + dword = cpu.read32s(sub_addr); + + var subentry = load_page_entry(dword, pae, false); + + if(subentry) + { + flags = ""; + + flags += subentry.cache_disable ? "Cd " : " "; + flags += subentry.user ? "U " : " "; + flags += subentry.read_write ? "Rw " : " "; + flags += subentry.global ? "G " : " "; + flags += subentry.accessed ? "A " : " "; + flags += subentry.dirty ? "Di " : " "; + + dbg_log("# " + h(start + (i << pd_shift | j << 12) >>> 0, 8) + " -> " + + h(subentry.address, 8) + " | " + flags + " (at " + h(sub_addr, 8) + ")"); + } + } + } + } + + + function get_memory_dump(start, count) + { + if(!DEBUG) return; + + if(start === undefined) + { + start = 0; + count = cpu.memory_size[0]; + } + else if(count === undefined) + { + count = start; + start = 0; + } + + return cpu.mem8.slice(start, start + count).buffer; + } + + + function memory_hex_dump(addr, length) + { + if(!DEBUG) return; + + length = length || 4 * 0x10; + var line, byt; + + for(var i = 0; i < length >> 4; i++) + { + line = h(addr + (i << 4), 5) + " "; + + for(var j = 0; j < 0x10; j++) + { + byt = cpu.read8(addr + (i << 4) + j); + line += h(byt, 2) + " "; + } + + line += " "; + + for(j = 0; j < 0x10; j++) + { + byt = cpu.read8(addr + (i << 4) + j); + line += (byt < 33 || byt > 126) ? "." : String.fromCharCode(byt); + } + + dbg_log(line); + } + } + + function used_memory_dump() + { + if(!DEBUG) return; + + var width = 0x80, + height = 0x10, + block_size = cpu.memory_size[0] / width / height | 0, + row; + + for(var i = 0; i < height; i++) + { + row = h(i * width * block_size, 8) + " | "; + + for(var j = 0; j < width; j++) + { + var used = cpu.mem32s[(i * width + j) * block_size] > 0; + + row += used ? "X" : " "; + } + + dbg_log(row); + } + } + + + debug.debug_interrupt = function(interrupt_nr) + { + //if(interrupt_nr === 0x20) + //{ + // //var vxd_device = cpu.safe_read16(cpu.instruction_pointer + 2); + // //var vxd_sub = cpu.safe_read16(cpu.instruction_pointer + 0); + // //var service = ""; + // //if(vxd_device === 1) + // //{ + // // service = vxd_table1[vxd_sub]; + // //} + // //dbg_log("vxd: " + h(vxd_device, 4) + " " + h(vxd_sub, 4) + " " + service); + //} + + //if(interrupt_nr >= 0x21 && interrupt_nr < 0x30) + //{ + // dbg_log("dos: " + h(interrupt_nr, 2) + " ah=" + h(this.reg8[reg_ah], 2) + " ax=" + h(this.reg16[reg_ax], 4)); + //} + + //if(interrupt_nr === 0x13 && (this.reg8[reg_ah] | 1) === 0x43) + //{ + // this.debug.memory_hex_dump(this.get_seg(reg_ds) + this.reg16[reg_si], 0x18); + //} + + //if(interrupt_nr == 0x10) + //{ + // dbg_log("int10 ax=" + h(this.reg16[reg_ax], 4) + " '" + String.fromCharCode(this.reg8[reg_al]) + "'"); + // this.debug.dump_regs_short(); + // if(this.reg8[reg_ah] == 0xe) vga.tt_write(this.reg8[reg_al]); + //} + + //if(interrupt_nr === 0x13) + //{ + // this.debug.dump_regs_short(); + //} + + //if(interrupt_nr === 6) + //{ + // this.instruction_pointer += 2; + // dbg_log("BUG()", LOG_CPU); + // dbg_log("line=" + this.read_imm16() + " " + + // "file=" + this.read_string(this.translate_address_read(this.read_imm32s())), LOG_CPU); + // this.instruction_pointer -= 8; + // this.debug.dump_regs_short(); + //} + + //if(interrupt_nr === 0x80) + //{ + // dbg_log("linux syscall"); + // this.debug.dump_regs_short(); + //} + + //if(interrupt_nr === 0x40) + //{ + // dbg_log("kolibri syscall"); + // this.debug.dump_regs_short(); + //} + }; + + let cs; + let capstone_decoder; + + debug.dump_code = function(is_32, buffer, start) + { + if(!capstone_decoder) + { + if(cs === undefined) + { + /* global require */ + if(typeof require === "function") + { + cs = require("./capstone-x86.min.js"); + } + else + { + cs = window.cs; + } + + if(cs === undefined) + { + dbg_log("Warning: Missing capstone library, disassembly not available"); + return; + } + } + + capstone_decoder = [ + new cs.Capstone(cs.ARCH_X86, cs.MODE_16), + new cs.Capstone(cs.ARCH_X86, cs.MODE_32), + ]; + } + + try + { + const instructions = capstone_decoder[is_32].disasm(buffer, start); + + instructions.forEach(function (instr) { + dbg_log(h(instr.address >>> 0) + ": " + + pads(instr.bytes.map(x => h(x, 2).slice(-2)).join(" "), 20) + " " + + instr.mnemonic + " " + instr.op_str); + }); + dbg_log(""); + } + catch(e) + { + dbg_log("Could not disassemble: " + Array.from(buffer).map(x => h(x, 2)).join(" ")); + } + }; + + function dump_file(ab, name) + { + var blob = new Blob([ab]); + + var a = document.createElement("a"); + a["download"] = name; + a.href = window.URL.createObjectURL(blob); + a.dataset["downloadurl"] = ["application/octet-stream", a["download"], a.href].join(":"); + + a.click(); + window.URL.revokeObjectURL(a.src); + } + + let wabt; + + debug.dump_wasm = function(buffer) + { + /* global require */ + if(wabt === undefined) + { + if(typeof require === "function") + { + wabt = require("./libwabt.cjs"); + } + else + { + wabt = new window.WabtModule; + } + + if(wabt === undefined) + { + dbg_log("Warning: Missing libwabt, wasm dump not available"); + return; + } + } + + // Need to make a small copy otherwise libwabt goes nuts trying to copy + // the whole underlying buffer + buffer = buffer.slice(); + + try + { + var module = wabt.readWasm(buffer, { readDebugNames: false }); + module.generateNames(); + module.applyNames(); + const result = module.toText({ foldExprs: true, inlineExport: true }); + dbg_log(result); + } + catch(e) + { + dump_file(buffer, "failed.wasm"); + console.log(e.toString()); + } + finally + { + if(module) + { + module.destroy(); + } + } + }; +}; diff --git a/src/debug.js b/src/debug.js deleted file mode 100644 index 9292d661..00000000 --- a/src/debug.js +++ /dev/null @@ -1,675 +0,0 @@ -"use strict"; - -import { - LOG_BIOS, LOG_CPU, - REG_ESP, REG_EBP, REG_ESI, REG_EAX, REG_EBX, REG_ECX, REG_EDX, REG_EDI, - REG_CS, REG_DS, REG_ES, REG_FS, REG_GS, REG_SS, CR0_PG, CR4_PAE, REG_LDTR, - FLAG_VM, FLAG_INTERRUPT, FLAG_CARRY, FLAG_ADJUST, FLAG_ZERO, FLAG_SIGN, FLAG_TRAP, - FLAG_DIRECTION, FLAG_OVERFLOW, FLAG_PARITY -} from "./const.js"; -import { h, pads } from "./lib.js"; -import { dbg_log } from "./log.js"; -import { CPU } from "./cpu.js"; - -CPU.prototype.debug_init = function() -{ - var cpu = this; - var debug = {}; - this.debug = debug; - - debug.init = function() - { - if(!DEBUG) return; - - if(cpu.io) - { - // write seabios debug output to console - var seabios_debug = ""; - - cpu.io.register_write(0x402, this, handle); // seabios - cpu.io.register_write(0x500, this, handle); // vgabios - } - - function handle(out_byte) - { - if(out_byte === 10) - { - dbg_log(seabios_debug, LOG_BIOS); - seabios_debug = ""; - } - else - { - seabios_debug += String.fromCharCode(out_byte); - } - } - }; - - debug.get_regs_short = get_regs_short; - debug.dump_regs = dump_regs_short; - debug.get_state = get_state; - debug.dump_state = dump_state; - debug.dump_stack = dump_stack; - - debug.dump_page_structures = dump_page_structures; - debug.dump_gdt_ldt = dump_gdt_ldt; - debug.dump_idt = dump_idt; - - debug.get_memory_dump = get_memory_dump; - debug.memory_hex_dump = memory_hex_dump; - debug.used_memory_dump = used_memory_dump; - - function dump_stack(start, end) - { - if(!DEBUG) return; - - var esp = cpu.reg32[REG_ESP]; - dbg_log("========= STACK =========="); - - if(end >= start || end === undefined) - { - start = 5; - end = -5; - } - - for(var i = start; i > end; i--) - { - var line = " "; - - if(!i) line = "=> "; - - line += h(i, 2) + " | "; - - dbg_log(line + h(esp + 4 * i, 8) + " | " + h(cpu.read32s(esp + 4 * i) >>> 0)); - } - } - - function get_state(where) - { - if(!DEBUG) return; - - var mode = cpu.protected_mode[0] ? "prot" : "real"; - var vm = (cpu.flags[0] & FLAG_VM) ? 1 : 0; - var flags = cpu.get_eflags(); - var iopl = cpu.getiopl(); - var cpl = cpu.cpl[0]; - var cs_eip = h(cpu.sreg[REG_CS], 4) + ":" + h(cpu.get_real_eip() >>> 0, 8); - var ss_esp = h(cpu.sreg[REG_SS], 4) + ":" + h(cpu.reg32[REG_ES] >>> 0, 8); - var op_size = cpu.is_32[0] ? "32" : "16"; - var if_ = (cpu.flags[0] & FLAG_INTERRUPT) ? 1 : 0; - - var flag_names = { - [FLAG_CARRY]: "c", - [FLAG_PARITY]: "p", - [FLAG_ADJUST]: "a", - [FLAG_ZERO]: "z", - [FLAG_SIGN]: "s", - [FLAG_TRAP]: "t", - [FLAG_INTERRUPT]: "i", - [FLAG_DIRECTION]: "d", - [FLAG_OVERFLOW]: "o", - }; - var flag_string = ""; - - for(var i = 0; i < 16; i++) - { - if(flag_names[1 << i]) - { - if(flags & 1 << i) - { - flag_string += flag_names[1 << i]; - } - else - { - flag_string += " "; - } - } - } - - return ("mode=" + mode + "/" + op_size + " paging=" + (+((cpu.cr[0] & CR0_PG) !== 0)) + - " pae=" + (+((cpu.cr[4] & CR4_PAE) !== 0)) + - " iopl=" + iopl + " cpl=" + cpl + " if=" + if_ + " cs:eip=" + cs_eip + - " cs_off=" + h(cpu.get_seg_cs() >>> 0, 8) + - " flgs=" + h(cpu.get_eflags() >>> 0, 6) + " (" + flag_string + ")" + - " ss:esp=" + ss_esp + - " ssize=" + (+cpu.stack_size_32[0]) + - (where ? " in " + where : "")); - } - - function dump_state(where) - { - if(!DEBUG) return; - - dbg_log(get_state(where), LOG_CPU); - } - - function get_regs_short() - { - if(!DEBUG) return; - - var - r32 = { "eax": REG_EAX, "ecx": REG_ECX, "edx": REG_EDX, "ebx": REG_EBX, - "esp": REG_ESP, "ebp": REG_EBP, "esi": REG_ESI, "edi": REG_EDI }, - r32_names = ["eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi"], - s = { "cs": REG_CS, "ds": REG_DS, "es": REG_ES, "fs": REG_FS, "gs": REG_GS, "ss": REG_SS }, - line1 = "", - line2 = ""; - - for(var i = 0; i < 4; i++) - { - line1 += r32_names[i] + "=" + h(cpu.reg32[r32[r32_names[i]]] >>> 0, 8) + " "; - line2 += r32_names[i+4] + "=" + h(cpu.reg32[r32[r32_names[i+4]]] >>> 0, 8) + " "; - } - - //line1 += " eip=" + h(cpu.get_real_eip() >>> 0, 8); - //line2 += " flg=" + h(cpu.get_eflags(), 8); - - line1 += " ds=" + h(cpu.sreg[REG_DS], 4) + " es=" + h(cpu.sreg[REG_ES], 4) + " fs=" + h(cpu.sreg[REG_FS], 4); - line2 += " gs=" + h(cpu.sreg[REG_GS], 4) + " cs=" + h(cpu.sreg[REG_CS], 4) + " ss=" + h(cpu.sreg[REG_SS], 4); - - return [line1, line2]; - } - - function dump_regs_short() - { - if(!DEBUG) return; - - var lines = get_regs_short(); - - dbg_log(lines[0], LOG_CPU); - dbg_log(lines[1], LOG_CPU); - } - - function dump_gdt_ldt() - { - if(!DEBUG) return; - - dbg_log("gdt: (len = " + h(cpu.gdtr_size[0]) + ")"); - dump_table(cpu.translate_address_system_read(cpu.gdtr_offset[0]), cpu.gdtr_size[0]); - - dbg_log("\nldt: (len = " + h(cpu.segment_limits[REG_LDTR]) + ")"); - dump_table(cpu.translate_address_system_read(cpu.segment_offsets[REG_LDTR]), cpu.segment_limits[REG_LDTR]); - - function dump_table(addr, size) - { - for(var i = 0; i < size; i += 8, addr += 8) - { - var base = cpu.read16(addr + 2) | - cpu.read8(addr + 4) << 16 | - cpu.read8(addr + 7) << 24, - - limit = cpu.read16(addr) | (cpu.read8(addr + 6) & 0xF) << 16, - access = cpu.read8(addr + 5), - flags = cpu.read8(addr + 6) >> 4, - flags_str = "", - dpl = access >> 5 & 3; - - if(!(access & 128)) - { - // present bit not set - //continue; - flags_str += "NP "; - } - else - { - flags_str += " P "; - } - - if(access & 16) - { - if(flags & 4) - { - flags_str += "32b "; - } - else - { - flags_str += "16b "; - } - - if(access & 8) - { - // executable - flags_str += "X "; - - if(access & 4) - { - flags_str += "C "; - } - } - else - { - // data - flags_str += "R "; - } - - flags_str += "RW "; - } - else - { - // system - flags_str += "sys: " + h(access & 15); - } - - if(flags & 8) - { - limit = limit << 12 | 0xFFF; - } - - dbg_log(h(i & ~7, 4) + " " + h(base >>> 0, 8) + " (" + h(limit >>> 0, 8) + " bytes) " + - flags_str + "; dpl = " + dpl + ", a = " + access.toString(2) + - ", f = " + flags.toString(2)); - } - } - } - - function dump_idt() - { - if(!DEBUG) return; - - for(var i = 0; i < cpu.idtr_size[0]; i += 8) - { - var addr = cpu.translate_address_system_read(cpu.idtr_offset[0] + i), - base = cpu.read16(addr) | cpu.read16(addr + 6) << 16, - selector = cpu.read16(addr + 2), - type = cpu.read8(addr + 5), - line, - dpl = type >> 5 & 3; - - if((type & 31) === 5) - { - line = "task gate "; - } - else if((type & 31) === 14) - { - line = "intr gate "; - } - else if((type & 31) === 15) - { - line = "trap gate "; - } - else - { - line = "invalid "; - } - - - if(type & 128) - { - line += " P"; - } - else - { - // present bit not set - //continue; - line += "NP"; - } - - - dbg_log(h(i >> 3, 4) + " " + h(base >>> 0, 8) + ", " + - h(selector, 4) + "; " + line + "; dpl = " + dpl + ", t = " + type.toString(2)); - } - } - - function load_page_entry(dword_entry, pae, is_directory) - { - if(!DEBUG) return; - - if(!(dword_entry & 1)) - { - // present bit not set - return false; - } - - var size = (dword_entry & 128) === 128, - address; - - if(size && !is_directory) - { - address = dword_entry & (pae ? 0xFFE00000 : 0xFFC00000); - } - else - { - address = dword_entry & 0xFFFFF000; - } - - return { - size: size, - global: (dword_entry & 256) === 256, - accessed: (dword_entry & 0x20) === 0x20, - dirty: (dword_entry & 0x40) === 0x40, - cache_disable : (dword_entry & 16) === 16, - user : (dword_entry & 4) === 4, - read_write : (dword_entry & 2) === 2, - address : address >>> 0 - }; - } - - function dump_page_structures() { - var pae = !!(cpu.cr[4] & CR4_PAE); - if(pae) - { - dbg_log("PAE enabled"); - - for(var i = 0; i < 4; i++) { - var addr = cpu.cr[3] + 8 * i; - var dword = cpu.read32s(addr); - if(dword & 1) - { - dump_page_directory(dword & 0xFFFFF000, true, i << 30); - } - } - } - else - { - dbg_log("PAE disabled"); - dump_page_directory(cpu.cr[3], false, 0); - } - } - - /* NOTE: PAE entries are 64-bits, we ignore the high half here. */ - function dump_page_directory(pd_addr, pae, start) - { - if(!DEBUG) return; - - var n = pae ? 512 : 1024; - var entry_size = pae ? 8 : 4; - var pd_shift = pae ? 21 : 22; - - for(var i = 0; i < n; i++) - { - var addr = pd_addr + i * entry_size, - dword = cpu.read32s(addr), - entry = load_page_entry(dword, pae, true); - - if(!entry) - { - continue; - } - - var flags = ""; - - flags += entry.size ? "S " : " "; - flags += entry.accessed ? "A " : " "; - flags += entry.cache_disable ? "Cd " : " "; - flags += entry.user ? "U " : " "; - flags += entry.read_write ? "Rw " : " "; - - if(entry.size) - { - dbg_log("=== " + h(start + (i << pd_shift) >>> 0, 8) + " -> " + - h(entry.address >>> 0, 8) + " | " + flags); - continue; - } - else - { - dbg_log("=== " + h(start + (i << pd_shift) >>> 0, 8) + " | " + flags); - } - - for(var j = 0; j < n; j++) - { - var sub_addr = entry.address + j * entry_size; - dword = cpu.read32s(sub_addr); - - var subentry = load_page_entry(dword, pae, false); - - if(subentry) - { - flags = ""; - - flags += subentry.cache_disable ? "Cd " : " "; - flags += subentry.user ? "U " : " "; - flags += subentry.read_write ? "Rw " : " "; - flags += subentry.global ? "G " : " "; - flags += subentry.accessed ? "A " : " "; - flags += subentry.dirty ? "Di " : " "; - - dbg_log("# " + h(start + (i << pd_shift | j << 12) >>> 0, 8) + " -> " + - h(subentry.address, 8) + " | " + flags + " (at " + h(sub_addr, 8) + ")"); - } - } - } - } - - - function get_memory_dump(start, count) - { - if(!DEBUG) return; - - if(start === undefined) - { - start = 0; - count = cpu.memory_size[0]; - } - else if(count === undefined) - { - count = start; - start = 0; - } - - return cpu.mem8.slice(start, start + count).buffer; - } - - - function memory_hex_dump(addr, length) - { - if(!DEBUG) return; - - length = length || 4 * 0x10; - var line, byt; - - for(var i = 0; i < length >> 4; i++) - { - line = h(addr + (i << 4), 5) + " "; - - for(var j = 0; j < 0x10; j++) - { - byt = cpu.read8(addr + (i << 4) + j); - line += h(byt, 2) + " "; - } - - line += " "; - - for(j = 0; j < 0x10; j++) - { - byt = cpu.read8(addr + (i << 4) + j); - line += (byt < 33 || byt > 126) ? "." : String.fromCharCode(byt); - } - - dbg_log(line); - } - } - - function used_memory_dump() - { - if(!DEBUG) return; - - var width = 0x80, - height = 0x10, - block_size = cpu.memory_size[0] / width / height | 0, - row; - - for(var i = 0; i < height; i++) - { - row = h(i * width * block_size, 8) + " | "; - - for(var j = 0; j < width; j++) - { - var used = cpu.mem32s[(i * width + j) * block_size] > 0; - - row += used ? "X" : " "; - } - - dbg_log(row); - } - } - - - debug.debug_interrupt = function(interrupt_nr) - { - //if(interrupt_nr === 0x20) - //{ - // //var vxd_device = cpu.safe_read16(cpu.instruction_pointer + 2); - // //var vxd_sub = cpu.safe_read16(cpu.instruction_pointer + 0); - // //var service = ""; - // //if(vxd_device === 1) - // //{ - // // service = vxd_table1[vxd_sub]; - // //} - // //dbg_log("vxd: " + h(vxd_device, 4) + " " + h(vxd_sub, 4) + " " + service); - //} - - //if(interrupt_nr >= 0x21 && interrupt_nr < 0x30) - //{ - // dbg_log("dos: " + h(interrupt_nr, 2) + " ah=" + h(this.reg8[reg_ah], 2) + " ax=" + h(this.reg16[reg_ax], 4)); - //} - - //if(interrupt_nr === 0x13 && (this.reg8[reg_ah] | 1) === 0x43) - //{ - // this.debug.memory_hex_dump(this.get_seg(reg_ds) + this.reg16[reg_si], 0x18); - //} - - //if(interrupt_nr == 0x10) - //{ - // dbg_log("int10 ax=" + h(this.reg16[reg_ax], 4) + " '" + String.fromCharCode(this.reg8[reg_al]) + "'"); - // this.debug.dump_regs_short(); - // if(this.reg8[reg_ah] == 0xe) vga.tt_write(this.reg8[reg_al]); - //} - - //if(interrupt_nr === 0x13) - //{ - // this.debug.dump_regs_short(); - //} - - //if(interrupt_nr === 6) - //{ - // this.instruction_pointer += 2; - // dbg_log("BUG()", LOG_CPU); - // dbg_log("line=" + this.read_imm16() + " " + - // "file=" + this.read_string(this.translate_address_read(this.read_imm32s())), LOG_CPU); - // this.instruction_pointer -= 8; - // this.debug.dump_regs_short(); - //} - - //if(interrupt_nr === 0x80) - //{ - // dbg_log("linux syscall"); - // this.debug.dump_regs_short(); - //} - - //if(interrupt_nr === 0x40) - //{ - // dbg_log("kolibri syscall"); - // this.debug.dump_regs_short(); - //} - }; - - let cs; - let capstone_decoder; - - debug.dump_code = function(is_32, buffer, start) - { - if(!capstone_decoder) - { - if(cs === undefined) - { - /* global require */ - if(typeof require === "function") - { - cs = require("./capstone-x86.min.js"); - } - else - { - cs = window.cs; - } - - if(cs === undefined) - { - dbg_log("Warning: Missing capstone library, disassembly not available"); - return; - } - } - - capstone_decoder = [ - new cs.Capstone(cs.ARCH_X86, cs.MODE_16), - new cs.Capstone(cs.ARCH_X86, cs.MODE_32), - ]; - } - - try - { - const instructions = capstone_decoder[is_32].disasm(buffer, start); - - instructions.forEach(function (instr) { - dbg_log(h(instr.address >>> 0) + ": " + - pads(instr.bytes.map(x => h(x, 2).slice(-2)).join(" "), 20) + " " + - instr.mnemonic + " " + instr.op_str); - }); - dbg_log(""); - } - catch(e) - { - dbg_log("Could not disassemble: " + Array.from(buffer).map(x => h(x, 2)).join(" ")); - } - }; - - function dump_file(ab, name) - { - var blob = new Blob([ab]); - - var a = document.createElement("a"); - a["download"] = name; - a.href = window.URL.createObjectURL(blob); - a.dataset["downloadurl"] = ["application/octet-stream", a["download"], a.href].join(":"); - - a.click(); - window.URL.revokeObjectURL(a.src); - } - - let wabt; - - debug.dump_wasm = function(buffer) - { - /* global require */ - if(wabt === undefined) - { - if(typeof require === "function") - { - wabt = require("./libwabt.cjs"); - } - else - { - wabt = new window.WabtModule; - } - - if(wabt === undefined) - { - dbg_log("Warning: Missing libwabt, wasm dump not available"); - return; - } - } - - // Need to make a small copy otherwise libwabt goes nuts trying to copy - // the whole underlying buffer - buffer = buffer.slice(); - - try - { - var module = wabt.readWasm(buffer, { readDebugNames: false }); - module.generateNames(); - module.applyNames(); - const result = module.toText({ foldExprs: true, inlineExport: true }); - dbg_log(result); - } - catch(e) - { - dump_file(buffer, "failed.wasm"); - console.log(e.toString()); - } - finally - { - if(module) - { - module.destroy(); - } - } - }; -}; From e9d5849afe5fa5e61baab47b88265531effd7c47 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sun, 6 Apr 2025 23:15:37 +0700 Subject: [PATCH 023/301] inlinde memory.js into cpu.js --- Makefile | 2 +- src/browser/starter.js | 1 - src/cpu.js | 96 ++++++++++++++++++++++++++++++++++++++ src/memory.js | 102 ----------------------------------------- 4 files changed, 97 insertions(+), 104 deletions(-) delete mode 100644 src/memory.js diff --git a/Makefile b/Makefile index c2d9875d..8914e144 100644 --- a/Makefile +++ b/Makefile @@ -79,7 +79,7 @@ CARGO_FLAGS_SAFE=\ CARGO_FLAGS=$(CARGO_FLAGS_SAFE) -C target-feature=+bulk-memory -C target-feature=+multivalue -C target-feature=+simd128 CORE_FILES=cjs.js const.js config.js io.js main.js lib.js buffer.js ide.js pci.js floppy.js \ - memory.js dma.js pit.js vga.js ps2.js rtc.js uart.js \ + dma.js pit.js vga.js ps2.js rtc.js uart.js \ acpi.js apic.js ioapic.js \ state.js ne2k.js sb16.js virtio.js virtio_console.js virtio_net.js virtio_balloon.js \ bus.js log.js cpu.js \ diff --git a/src/browser/starter.js b/src/browser/starter.js index 5c0de3c2..606ba062 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -27,7 +27,6 @@ import { EEXIST, ENOENT } from "../../lib/9p.js"; // Decorates CPU -import "../memory.js"; import "../state.js"; /** diff --git a/src/cpu.js b/src/cpu.js index 4f55e064..e92f8604 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -238,6 +238,102 @@ export function CPU(bus, wm, stop_idling) //Object.seal(this); } +CPU.prototype.mmap_read8 = function(addr) +{ + const value = this.memory_map_read8[addr >>> MMAP_BLOCK_BITS](addr); + dbg_assert(value >= 0 && value <= 0xFF); + return value; +}; + +CPU.prototype.mmap_write8 = function(addr, value) +{ + dbg_assert(value >= 0 && value <= 0xFF); + this.memory_map_write8[addr >>> MMAP_BLOCK_BITS](addr, value); +}; + +CPU.prototype.mmap_read16 = function(addr) +{ + var fn = this.memory_map_read8[addr >>> MMAP_BLOCK_BITS]; + const value = fn(addr) | fn(addr + 1 | 0) << 8; + dbg_assert(value >= 0 && value <= 0xFFFF); + return value; +}; + +CPU.prototype.mmap_write16 = function(addr, value) +{ + var fn = this.memory_map_write8[addr >>> MMAP_BLOCK_BITS]; + + dbg_assert(value >= 0 && value <= 0xFFFF); + fn(addr, value & 0xFF); + fn(addr + 1 | 0, value >> 8); +}; + +CPU.prototype.mmap_read32 = function(addr) +{ + var aligned_addr = addr >>> MMAP_BLOCK_BITS; + + return this.memory_map_read32[aligned_addr](addr); +}; + +CPU.prototype.mmap_write32 = function(addr, value) +{ + var aligned_addr = addr >>> MMAP_BLOCK_BITS; + + this.memory_map_write32[aligned_addr](addr, value); +}; + +CPU.prototype.mmap_write64 = function(addr, value0, value1) +{ + var aligned_addr = addr >>> MMAP_BLOCK_BITS; + // This should hold since writes across pages are split up + dbg_assert(aligned_addr === (addr + 7) >>> MMAP_BLOCK_BITS); + + var write_func32 = this.memory_map_write32[aligned_addr]; + write_func32(addr, value0); + write_func32(addr + 4, value1); +}; + +CPU.prototype.mmap_write128 = function(addr, value0, value1, value2, value3) +{ + var aligned_addr = addr >>> MMAP_BLOCK_BITS; + // This should hold since writes across pages are split up + dbg_assert(aligned_addr === (addr + 12) >>> MMAP_BLOCK_BITS); + + var write_func32 = this.memory_map_write32[aligned_addr]; + write_func32(addr, value0); + write_func32(addr + 4, value1); + write_func32(addr + 8, value2); + write_func32(addr + 12, value3); +}; + +/** + * @param {Array.|Uint8Array} blob + * @param {number} offset + */ +CPU.prototype.write_blob = function(blob, offset) +{ + dbg_assert(blob && blob.length >= 0); + + if(blob.length) + { + dbg_assert(!this.in_mapped_range(offset)); + dbg_assert(!this.in_mapped_range(offset + blob.length - 1)); + + this.jit_dirty_cache(offset, offset + blob.length); + this.mem8.set(blob, offset); + } +}; + +CPU.prototype.read_blob = function(offset, length) +{ + if(length) + { + dbg_assert(!this.in_mapped_range(offset)); + dbg_assert(!this.in_mapped_range(offset + length - 1)); + } + return this.mem8.subarray(offset, offset + length); +}; + CPU.prototype.clear_opstats = function() { new Uint8Array(this.wasm_memory.buffer, 0x8000, 0x20000).fill(0); diff --git a/src/memory.js b/src/memory.js deleted file mode 100644 index 521ec189..00000000 --- a/src/memory.js +++ /dev/null @@ -1,102 +0,0 @@ -"use strict"; - -import { MMAP_BLOCK_BITS } from "./const.js"; -import { CPU } from "./cpu.js"; -import { dbg_assert } from "./log.js"; - - -CPU.prototype.mmap_read8 = function(addr) -{ - const value = this.memory_map_read8[addr >>> MMAP_BLOCK_BITS](addr); - dbg_assert(value >= 0 && value <= 0xFF); - return value; -}; - -CPU.prototype.mmap_write8 = function(addr, value) -{ - dbg_assert(value >= 0 && value <= 0xFF); - this.memory_map_write8[addr >>> MMAP_BLOCK_BITS](addr, value); -}; - -CPU.prototype.mmap_read16 = function(addr) -{ - var fn = this.memory_map_read8[addr >>> MMAP_BLOCK_BITS]; - const value = fn(addr) | fn(addr + 1 | 0) << 8; - dbg_assert(value >= 0 && value <= 0xFFFF); - return value; -}; - -CPU.prototype.mmap_write16 = function(addr, value) -{ - var fn = this.memory_map_write8[addr >>> MMAP_BLOCK_BITS]; - - dbg_assert(value >= 0 && value <= 0xFFFF); - fn(addr, value & 0xFF); - fn(addr + 1 | 0, value >> 8); -}; - -CPU.prototype.mmap_read32 = function(addr) -{ - var aligned_addr = addr >>> MMAP_BLOCK_BITS; - - return this.memory_map_read32[aligned_addr](addr); -}; - -CPU.prototype.mmap_write32 = function(addr, value) -{ - var aligned_addr = addr >>> MMAP_BLOCK_BITS; - - this.memory_map_write32[aligned_addr](addr, value); -}; - -CPU.prototype.mmap_write64 = function(addr, value0, value1) -{ - var aligned_addr = addr >>> MMAP_BLOCK_BITS; - // This should hold since writes across pages are split up - dbg_assert(aligned_addr === (addr + 7) >>> MMAP_BLOCK_BITS); - - var write_func32 = this.memory_map_write32[aligned_addr]; - write_func32(addr, value0); - write_func32(addr + 4, value1); -}; - -CPU.prototype.mmap_write128 = function(addr, value0, value1, value2, value3) -{ - var aligned_addr = addr >>> MMAP_BLOCK_BITS; - // This should hold since writes across pages are split up - dbg_assert(aligned_addr === (addr + 12) >>> MMAP_BLOCK_BITS); - - var write_func32 = this.memory_map_write32[aligned_addr]; - write_func32(addr, value0); - write_func32(addr + 4, value1); - write_func32(addr + 8, value2); - write_func32(addr + 12, value3); -}; - -/** - * @param {Array.|Uint8Array} blob - * @param {number} offset - */ -CPU.prototype.write_blob = function(blob, offset) -{ - dbg_assert(blob && blob.length >= 0); - - if(blob.length) - { - dbg_assert(!this.in_mapped_range(offset)); - dbg_assert(!this.in_mapped_range(offset + blob.length - 1)); - - this.jit_dirty_cache(offset, offset + blob.length); - this.mem8.set(blob, offset); - } -}; - -CPU.prototype.read_blob = function(offset, length) -{ - if(length) - { - dbg_assert(!this.in_mapped_range(offset)); - dbg_assert(!this.in_mapped_range(offset + length - 1)); - } - return this.mem8.subarray(offset, offset + length); -}; From 1d232cf2623255d69ba4b2d4f64591f8fdd5d4e1 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sun, 6 Apr 2025 23:20:32 +0700 Subject: [PATCH 024/301] state.js: don't add to CPU's prototype --- src/browser/starter.js | 3 --- src/main.js | 5 +++-- src/state.js | 50 ++++++++++++++++++++++-------------------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/browser/starter.js b/src/browser/starter.js index 606ba062..80be7bf4 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -26,9 +26,6 @@ import { FS } from "../../lib/filesystem.js"; import { EEXIST, ENOENT } from "../../lib/9p.js"; -// Decorates CPU -import "../state.js"; - /** * Constructor for emulator instances. * diff --git a/src/main.js b/src/main.js index 140197f3..51748ca4 100644 --- a/src/main.js +++ b/src/main.js @@ -1,6 +1,7 @@ "use strict"; import { CPU } from "./cpu.js"; +import { save_state, restore_state } from "./state.js"; /** * @constructor @@ -199,13 +200,13 @@ else v86.prototype.save_state = function() { // TODO: Should be implemented here, not on cpu - return this.cpu.save_state(); + return save_state(this.cpu); }; v86.prototype.restore_state = function(state) { // TODO: Should be implemented here, not on cpu - return this.cpu.restore_state(state); + return restore_state(this.cpu, state); }; /* global require */ diff --git a/src/state.js b/src/state.js index 524cf1af..f9d58a27 100644 --- a/src/state.js +++ b/src/state.js @@ -127,10 +127,11 @@ function restore_buffers(obj, buffers) return new constructor(buffer); } -CPU.prototype.save_state = function() +/* @param {CPU} cpu */ +export function save_state(cpu) { var saved_buffers = []; - var state = save_object(this, saved_buffers); + var state = save_object(cpu, saved_buffers); var buffer_infos = []; var total_buffer_size = 0; @@ -192,9 +193,10 @@ CPU.prototype.save_state = function() dbg_log("State: Total buffers size " + (buffer_block.byteLength >> 10) + "k"); return result; -}; +} -CPU.prototype.restore_state = function(state) +/* @param {CPU} cpu */ +export function restore_state(cpu, state) { state = new Uint8Array(state); @@ -239,19 +241,19 @@ CPU.prototype.restore_state = function(state) if(new Uint32Array(state.buffer, 0, 1)[0] === ZSTD_MAGIC) { - const ctx = this.zstd_create_ctx(state.length); + const ctx = cpu.zstd_create_ctx(state.length); - new Uint8Array(this.wasm_memory.buffer, this.zstd_get_src_ptr(ctx), state.length).set(state); + new Uint8Array(cpu.wasm_memory.buffer, cpu.zstd_get_src_ptr(ctx), state.length).set(state); - let ptr = this.zstd_read(ctx, 16); - const header_block = new Uint8Array(this.wasm_memory.buffer, ptr, 16); + let ptr = cpu.zstd_read(ctx, 16); + const header_block = new Uint8Array(cpu.wasm_memory.buffer, ptr, 16); const info_block_len = read_state_header(header_block, false); - this.zstd_read_free(ptr, 16); + cpu.zstd_read_free(ptr, 16); - ptr = this.zstd_read(ctx, info_block_len); - const info_block_buffer = new Uint8Array(this.wasm_memory.buffer, ptr, info_block_len); + ptr = cpu.zstd_read(ctx, info_block_len); + const info_block_buffer = new Uint8Array(cpu.wasm_memory.buffer, ptr, info_block_len); const info_block_obj = read_info_block(info_block_buffer); - this.zstd_read_free(ptr, info_block_len); + cpu.zstd_read_free(ptr, info_block_len); let state_object = info_block_obj["state"]; const buffer_infos = info_block_obj["buffer_infos"]; @@ -266,8 +268,8 @@ CPU.prototype.restore_state = function(state) if(buffer_info.length > CHUNK_SIZE) { - const ptr = this.zstd_read(ctx, front_padding); - this.zstd_read_free(ptr, front_padding); + const ptr = cpu.zstd_read(ctx, front_padding); + cpu.zstd_read_free(ptr, front_padding); const buffer = new Uint8Array(buffer_info.length); buffers.push(buffer.buffer); @@ -279,28 +281,28 @@ CPU.prototype.restore_state = function(state) dbg_assert(remaining >= 0); const to_read = Math.min(remaining, CHUNK_SIZE); - const ptr = this.zstd_read(ctx, to_read); - buffer.set(new Uint8Array(this.wasm_memory.buffer, ptr, to_read), have); - this.zstd_read_free(ptr, to_read); + const ptr = cpu.zstd_read(ctx, to_read); + buffer.set(new Uint8Array(cpu.wasm_memory.buffer, ptr, to_read), have); + cpu.zstd_read_free(ptr, to_read); have += to_read; } } else { - const ptr = this.zstd_read(ctx, front_padding + buffer_info.length); + const ptr = cpu.zstd_read(ctx, front_padding + buffer_info.length); const offset = ptr + front_padding; - buffers.push(this.wasm_memory.buffer.slice(offset, offset + buffer_info.length)); - this.zstd_read_free(ptr, front_padding + buffer_info.length); + buffers.push(cpu.wasm_memory.buffer.slice(offset, offset + buffer_info.length)); + cpu.zstd_read_free(ptr, front_padding + buffer_info.length); } position += front_padding + buffer_info.length; } state_object = restore_buffers(state_object, buffers); - this.set_state(state_object); + cpu.set_state(state_object); - this.zstd_free_ctx(ctx); + cpu.zstd_free_ctx(ctx); } else { @@ -324,6 +326,6 @@ CPU.prototype.restore_state = function(state) }); state_object = restore_buffers(state_object, buffers); - this.set_state(state_object); + cpu.set_state(state_object); } -}; +} From b3051ebcf9d2927de29e9e9c898487ec70aeea58 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sun, 6 Apr 2025 23:22:43 +0700 Subject: [PATCH 025/301] minor: fix indent --- src/browser/main.js | 5438 +++++++++++++++++++++---------------------- src/buffer.js | 1540 ++++++------ 2 files changed, 3489 insertions(+), 3489 deletions(-) diff --git a/src/browser/main.js b/src/browser/main.js index e2033af6..21126b89 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -9,2789 +9,2789 @@ import { pad0, pads, hex_dump, dump_file, download, round_up_to_next_power_of_2 import { log_data } from "../log.js"; - const ON_LOCALHOST = !location.hostname.endsWith("copy.sh"); +const ON_LOCALHOST = !location.hostname.endsWith("copy.sh"); - const DEFAULT_NETWORKING_PROXIES = ["wss://relay.widgetry.org/", "ws://localhost:8080/"]; - const DEFAULT_MEMORY_SIZE = 128; - const DEFAULT_VGA_MEMORY_SIZE = 8; - const DEFAULT_BOOT_ORDER = 0; +const DEFAULT_NETWORKING_PROXIES = ["wss://relay.widgetry.org/", "ws://localhost:8080/"]; +const DEFAULT_MEMORY_SIZE = 128; +const DEFAULT_VGA_MEMORY_SIZE = 8; +const DEFAULT_BOOT_ORDER = 0; - function query_append() +function query_append() +{ + const version = $("version"); + return version ? "?" + version.textContent : ""; +} + +function set_title(text) +{ + document.title = text + " - v86" + (DEBUG ? " - debug" : ""); + const description = document.querySelector("meta[name=description]"); + description && (description.content = "Running " + text); +} + +function bool_arg(x) +{ + return !!x && x !== "0"; +} + +function format_timestamp(time) +{ + if(time < 60) { - const version = $("version"); - return version ? "?" + version.textContent : ""; + return time + "s"; + } + else if(time < 3600) + { + return (time / 60 | 0) + "m " + pad0(time % 60, 2) + "s"; + } + else + { + return (time / 3600 | 0) + "h " + + pad0((time / 60 | 0) % 60, 2) + "m " + + pad0(time % 60, 2) + "s"; + } +} + +let progress_ticks = 0; + +function show_progress(e) +{ + const el = $("loading"); + el.style.display = "block"; + + if(e.file_name.endsWith(".wasm")) + { + const parts = e.file_name.split("/"); + el.textContent = "Fetching " + parts[parts.length - 1] + " ..."; + return; } - function set_title(text) + if(e.file_index === e.file_count - 1 && e.loaded >= e.total - 2048) { - document.title = text + " - v86" + (DEBUG ? " - debug" : ""); - const description = document.querySelector("meta[name=description]"); - description && (description.content = "Running " + text); + // last file is (almost) loaded + el.textContent = "Done downloading. Starting now ..."; + return; } - function bool_arg(x) + let line = "Downloading images "; + + if(typeof e.file_index === "number" && e.file_count) { - return !!x && x !== "0"; + line += "[" + (e.file_index + 1) + "/" + e.file_count + "] "; } - function format_timestamp(time) + if(e.total && typeof e.loaded === "number") { - if(time < 60) + var per100 = Math.floor(e.loaded / e.total * 100); + per100 = Math.min(100, Math.max(0, per100)); + + var per50 = Math.floor(per100 / 2); + + line += per100 + "% ["; + line += "#".repeat(per50); + line += " ".repeat(50 - per50) + "]"; + } + else + { + line += ".".repeat(progress_ticks++ % 50); + } + + el.textContent = line; +} + +function $(id) +{ + return document.getElementById(id); +} + +// These values were previously stored in localStorage +const elements_to_restore = [ + "memory_size", + "video_memory_size", + "networking_proxy", + "disable_audio", + "enable_acpi", + "boot_order", +]; +for(const item of elements_to_restore) +{ + try + { + window.localStorage.removeItem(item); + } + catch(e) {} +} + +function onload() +{ + if(!window.WebAssembly) + { + alert("Your browser is not supported because it doesn't support WebAssembly"); + return; + } + + $("start_emulation").onclick = function(e) + { + start_emulation(null, null); + $("start_emulation").blur(); + e.preventDefault(); + }; + + if(DEBUG) + { + debug_onload(); + } + + if(DEBUG && ON_LOCALHOST) + { + // don't use online relay in debug mode + $("relay_url").value = "ws://localhost:8080/"; + } + + const query_args = new URLSearchParams(location.search); + const host = query_args.get("cdn") || (ON_LOCALHOST ? "images/" : "//i.copy.sh/"); + + // Abandonware OS images are from https://winworldpc.com/library/operating-systems + const oses = [ { - return time + "s"; + id: "archlinux", + name: "Arch Linux", + memory_size: 512 * 1024 * 1024, + vga_memory_size: 8 * 1024 * 1024, + state: { url: host + "arch_state-v2.bin.zst" }, + filesystem: { + baseurl: host + "arch/", + }, + net_device_type: "virtio", + }, + { + id: "archlinux-boot", + name: "Arch Linux", + memory_size: 512 * 1024 * 1024, + vga_memory_size: 8 * 1024 * 1024, + filesystem: { + baseurl: host + "arch/", + basefs: { url: host + "fs.json" }, + }, + cmdline: [ + "rw apm=off vga=0x344 video=vesafb:ypan,vremap:8", + "root=host9p rootfstype=9p rootflags=trans=virtio,cache=loose", + "mitigations=off audit=0", + "init_on_free=on", + "tsc=reliable", + "random.trust_cpu=on", + "nowatchdog", + "init=/usr/bin/init-openrc net.ifnames=0 biosdevname=0", + ].join(" "), + bzimage_initrd_from_filesystem: true, + net_device_type: "virtio", + }, + { + id: "copy/skiffos", + name: "SkiffOS", + cdrom: { + url: host + "skiffos/.iso", + size: 124672000, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + memory_size: 512 * 1024 * 1024, + }, + { + id: "serenity", + name: "SerenityOS", + hda: { + url: host + "serenity-v3/.img.zst", + size: 734003200, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + memory_size: 512 * 1024 * 1024, + state: { url: host + "serenity_state-v4.bin.zst" }, + homepage: "https://serenityos.org/", + mac_address_translation: true, + }, + { + id: "serenity-boot", + name: "SerenityOS", + hda: { + url: host + "serenity-v3/.img.zst", + size: 734003200, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + memory_size: 512 * 1024 * 1024, + homepage: "https://serenityos.org/", + }, + { + id: "redox", + name: "Redox", + hda: { + url: host + "redox_demo_i686_2024-09-07_1225_harddrive/.img", + size: 671088640, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + memory_size: 1024 * 1024 * 1024, + state: { url: host + "redox_state-v2.bin.zst" }, + homepage: "https://www.redox-os.org/", + acpi: true, + }, + { + id: "redox-boot", + name: "Redox", + hda: { + url: host + "redox_demo_i686_2024-09-07_1225_harddrive/.img", + size: 671088640, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + memory_size: 1024 * 1024 * 1024, + homepage: "https://www.redox-os.org/", + acpi: true, + }, + { + id: "helenos", + memory_size: 256 * 1024 * 1024, + cdrom: { + //url: host + "HelenOS-0.11.2-ia32.iso", + //size: 25765888, + url: host + "HelenOS-0.14.1-ia32.iso", + size: 25792512, + async: false, + }, + name: "HelenOS", + homepage: "http://www.helenos.org/", + }, + { + id: "fiwix", + memory_size: 256 * 1024 * 1024, + hda: { + url: host + "FiwixOS-3.4-i386/.img", + size: 1024 * 1024 * 1024, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + name: "FiwixOS", + homepage: "https://www.fiwix.org/", + }, + { + id: "haiku", + memory_size: 512 * 1024 * 1024, + hda: { + url: host + "haiku-v4/.img", + size: 1 * 1024 * 1024 * 1024, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + state: { url: host + "haiku_state-v4.bin.zst" }, + name: "Haiku", + homepage: "https://www.haiku-os.org/", + }, + { + id: "haiku-boot", + memory_size: 512 * 1024 * 1024, + hda: { + url: host + "haiku-v4/.img", + size: 1 * 1024 * 1024 * 1024, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + name: "Haiku", + homepage: "https://www.haiku-os.org/", + }, + { + id: "beos", + memory_size: 512 * 1024 * 1024, + hda: { + url: host + "beos5/.img", + size: 536870912, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + name: "BeOS 5", + }, + { + id: "msdos", + hda: { + url: host + "msdos622/.img", + size: 64 * 1024 * 1024, + async: true, + fixed_chunk_size: 256 * 1024, + use_parts: true, + }, + name: "MS-DOS 6.22", + }, + { + id: "msdos4", + fda: { + url: host + "msdos4.img", + size: 1474560, + }, + name: "MS-DOS 4", + }, + { + id: "freedos", + fda: { + url: host + "freedos722.img", + size: 737280, + }, + name: "FreeDOS", + }, + { + id: "freegem", + hda: { + url: host + "freegem/.bin", + size: 209715200, + async: true, + fixed_chunk_size: 256 * 1024, + use_parts: true, + }, + name: "Freedos with FreeGEM", + }, + { + id: "xcom", + fda: { + url: host + "xcom144.img", + size: 1440 * 1024, + }, + name: "Freedos with Xcom", + homepage: "http://xcom.infora.hu/index.html", + }, + { + id: "psychdos", + hda: { + url: host + "psychdos/.img", + size: 549453824, + async: true, + fixed_chunk_size: 256 * 1024, + use_parts: true, + }, + name: "PsychDOS", + homepage: "https://psychoslinux.gitlab.io/DOS/INDEX.HTM", + }, + { + id: "86dos", + fda: { + url: host + "pc86dos.img", + size: 163840, + }, + name: "86-DOS", + homepage: "https://www.os2museum.com/wp/pc-86-dos/", + }, + { + id: "oberon", + hda: { + url: host + "oberon.img", + size: 24 * 1024 * 1024, + async: false, + }, + name: "Oberon", + }, + { + id: "windows1", + fda: { + url: host + "windows101.img", + size: 1474560, + }, + name: "Windows 1.01", + }, + { + id: "windows2", + hda: { + url: host + "windows2.img", + size: 4177920, + async: false, + }, + name: "Windows 2.03", + }, + { + id: "linux26", + cdrom: { + url: host + "linux.iso", + size: 6547456, + async: false, + }, + name: "Linux", + }, + { + id: "linux3", + cdrom: { + url: host + "linux3.iso", + size: 8638464, + async: false, + }, + name: "Linux", + }, + { + id: "linux4", + cdrom: { + url: host + "linux4.iso", + size: 7731200, + async: false, + }, + name: "Linux", + filesystem: {}, + }, + { + id: "buildroot", + bzimage: { + url: host + "buildroot-bzimage.bin", + size: 5166352, + async: false, + }, + name: "Buildroot Linux", + filesystem: {}, + cmdline: "tsc=reliable mitigations=off random.trust_cpu=on", + }, + { + id: "buildroot6", + bzimage: { + url: host + "buildroot-bzimage68.bin", + size: 10068480, + async: false, + }, + name: "Buildroot Linux 6.8", + filesystem: {}, + cmdline: "tsc=reliable mitigations=off random.trust_cpu=on", + }, + { + id: "basiclinux", + hda: { + url: host + "bl3-5.img", + size: 104857600, + async: false, + }, + name: "BasicLinux", + }, + { + id: "xpud", + cdrom: { + url: host + "xpud-0.9.2.iso", + size: 67108864, + async: false, + }, + name: "xPUD", + memory_size: 256 * 1024 * 1024, + }, + { + id: "elks", + hda: { + url: host + "elks-hd32-fat.img", + size: 32514048, + async: false, + }, + name: "ELKS", + homepage: "https://github.com/ghaerr/elks", + }, + { + id: "nodeos", + bzimage: { + url: host + "nodeos-kernel.bin", + size: 14452000, + async: false, + }, + name: "NodeOS", + cmdline: "tsc=reliable mitigations=off random.trust_cpu=on", + }, + { + id: "dsl", + memory_size: 256 * 1024 * 1024, + cdrom: { + url: host + "dsl-4.11.rc2.iso", + size: 52824064, + async: false, + }, + name: "Damn Small Linux", + homepage: "http://www.damnsmalllinux.org/", + }, + { + id: "xwoaf", + memory_size: 256 * 1024 * 1024, + cdrom: { + url: host + "xwoaf_rebuild4.iso", + size: 2205696, + async: false, + }, + name: "xwoaf", + homepage: "https://pupngo.dk/xwinflpy/xwoaf_rebuild.html", + }, + { + id: "minix", + name: "Minix", + memory_size: 256 * 1024 * 1024, + cdrom: { + url: host + "minix-3.3.0/.iso", + size: 605581312, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + homepage: "https://www.minix3.org/", + }, + { + id: "unix-v7", + name: "Unix V7", + hda: { + url: host + "unix-v7x86-0.8a/.img", + size: 152764416, + async: true, + fixed_chunk_size: 256 * 1024, + use_parts: true, + }, + }, + { + id: "kolibrios", + fda: { + url: ON_LOCALHOST ? + host + "kolibri.img" : + "//builds.kolibrios.org/en_US/data/data/kolibri.img", + size: 1474560, + }, + name: "KolibriOS", + homepage: "https://kolibrios.org/en/", + }, + { + id: "kolibrios-fallback", + fda: { + url: host + "kolibri.img", + size: 1474560, + }, + name: "KolibriOS", + }, + { + id: "mu", + hda: { + url: host + "mu-shell.img", + size: 10321920, + async: false, + }, + memory_size: 256 * 1024 * 1024, + name: "Mu", + homepage: "https://github.com/akkartik/mu", + }, + { + id: "openbsd", + hda: { + url: host + "openbsd/.img", + size: 1073741824, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + state: { url: host + "openbsd_state.bin.zst" }, + memory_size: 256 * 1024 * 1024, + name: "OpenBSD", + }, + { + id: "sortix", + cdrom: { + url: host + "sortix-1.0-i686.iso", + size: 71075840, + async: false, + }, + memory_size: 512 * 1024 * 1024, + name: "Sortix", + }, + { + id: "openbsd-boot", + hda: { + url: host + "openbsd/.img", + size: 1073741824, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + memory_size: 256 * 1024 * 1024, + name: "OpenBSD", + //acpi: true, // doesn't seem to work + }, + { + id: "netbsd", + hda: { + url: host + "netbsd/.img", + size: 511000064, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + memory_size: 256 * 1024 * 1024, + name: "NetBSD", + }, + { + id: "crazierl", + multiboot: { + url: host + "crazierl-elf.img", + size: 896592, + async: false, + }, + initrd: { + url: host + "crazierl-initrd.img", + size: 18448316, + async: false, + }, + acpi: true, + cmdline: "kernel /libexec/ld-elf32.so.1", + memory_size: 128 * 1024 * 1024, + name: "Crazierl", + }, + { + id: "solos", + fda: { + url: host + "os8.img", + size: 1474560, + }, + name: "Sol OS", + homepage: "http://oby.ro/os/", + }, + { + id: "bootchess", + fda: { + url: host + "bootchess.img", + size: 1474560, + }, + name: "BootChess", + homepage: "http://www.pouet.net/prod.php?which=64962", + }, + { + id: "bootbasic", + fda: { + url: host + "bootbasic.img", + size: 512, + }, + name: "bootBASIC", + homepage: "https://github.com/nanochess/bootBASIC", + }, + { + id: "bootlogo", + fda: { + url: host + "bootlogo.img", + size: 512, + }, + name: "bootLogo", + homepage: "https://github.com/nanochess/bootLogo", + }, + { + id: "pillman", + fda: { + url: host + "pillman.img", + size: 512, + }, + name: "Pillman", + homepage: "https://github.com/nanochess/Pillman", + }, + { + id: "invaders", + fda: { + url: host + "invaders.img", + size: 512, + }, + name: "Invaders", + homepage: "https://github.com/nanochess/Invaders", + }, + { + id: "sectorlisp", + fda: { + url: host + "sectorlisp-friendly.bin", + size: 512, + }, + name: "SectorLISP", + homepage: "https://justine.lol/sectorlisp2/", + }, + { + id: "sectorforth", + fda: { + url: host + "sectorforth.img", + size: 512, + }, + name: "sectorforth", + homepage: "https://github.com/cesarblum/sectorforth", + }, + { + id: "floppybird", + fda: { + url: host + "floppybird.img", + size: 1474560, + }, + name: "Floppy Bird", + homepage: "http://mihail.co/floppybird", + }, + { + id: "stillalive", + fda: { + url: host + "stillalive-os.img", + size: 368640, + }, + name: "Still Alive", + homepage: "https://github.com/maniekx86/stillalive-os", + }, + { + id: "hello-v86", + fda: { + url: host + "hello-v86.img", + size: 512, + }, + name: "Hello v86", + }, + { + id: "tetros", + fda: { + url: host + "tetros.img", + size: 512, + }, + name: "TetrOS", + homepage: "https://github.com/daniel-e/tetros", + }, + { + id: "dino", + fda: { + url: host + "bootdino.img", + size: 512, + }, + name: "dino", + homepage: "https://github.com/franeklubi/dino", + }, + { + id: "bootrogue", + fda: { + url: host + "bootrogue.img", + size: 512, + }, + name: "bootRogue", + homepage: "https://github.com/nanochess/bootRogue", + }, + { + id: "duskos", + hda: { + url: host + "duskos.img", + async: false, + size: 8388608, + }, + name: "Dusk OS", + homepage: "http://duskos.org/", + }, + { + id: "windows2000", + memory_size: 512 * 1024 * 1024, + hda: { + url: host + "windows2k-v2/.img", + size: 2 * 1024 * 1024 * 1024, + async: true, + fixed_chunk_size: 256 * 1024, + use_parts: true, + }, + name: "Windows 2000", + state: { url: host + "windows2k_state-v3.bin.zst" }, + mac_address_translation: true, + }, + { + id: "windows2000-boot", + memory_size: 512 * 1024 * 1024, + hda: { + url: host + "windows2k-v2/.img", + size: 2 * 1024 * 1024 * 1024, + async: true, + fixed_chunk_size: 256 * 1024, + use_parts: true, + }, + name: "Windows 2000", + }, + { + id: "windows-me", + memory_size: 256 * 1024 * 1024, + hda: { + url: host + "windowsme-v2/.img", + size: 834666496, + async: true, + fixed_chunk_size: 256 * 1024, + use_parts: true, + }, + state: { url: host + "windows-me_state-v2.bin.zst" }, + name: "Windows ME", + }, + { + id: "windowsnt4", + memory_size: 512 * 1024 * 1024, + hda: { + url: host + "winnt4_noacpi/.img", + size: 523837440, + async: true, + fixed_chunk_size: 256 * 1024, + use_parts: true, + }, + name: "Windows NT 4.0", + cpuid_level: 2, + }, + { + id: "windowsnt35", + memory_size: 256 * 1024 * 1024, + hda: { + url: host + "windowsnt351/.img", + size: 163577856, + async: true, + fixed_chunk_size: 256 * 1024, + use_parts: true, + }, + name: "Windows NT 3.51", + }, + { + id: "windowsnt3", + memory_size: 256 * 1024 * 1024, + hda: { + url: host + "winnt31/.img", + size: 87 * 1024 * 1024, + async: true, + fixed_chunk_size: 256 * 1024, + use_parts: true, + }, + name: "Windows NT 3.1", + }, + { + id: "windows98", + memory_size: 128 * 1024 * 1024, + hda: { + url: host + "windows98/.img", + size: 300 * 1024 * 1024, + async: true, + fixed_chunk_size: 256 * 1024, + use_parts: true, + }, + name: "Windows 98", + state: { url: host + "windows98_state.bin.zst" }, + mac_address_translation: true, + }, + { + id: "windows98-boot", + memory_size: 128 * 1024 * 1024, + hda: { + url: host + "windows98/.img", + size: 300 * 1024 * 1024, + async: true, + fixed_chunk_size: 256 * 1024, + use_parts: true, + }, + name: "Windows 98", + }, + { + id: "windows95", + memory_size: 64 * 1024 * 1024, + // old image: + //memory_size: 32 * 1024 * 1024, + //hda: { + // url: host + "w95/.img", + // size: 242049024, + // async: true, + // fixed_chunk_size: 256 * 1024, + // use_parts: true, + //}, + //state: { url: host + "windows95_state.bin.zst" }, + hda: { + url: host + "windows95-v2/.img", + size: 471859200, + async: true, + fixed_chunk_size: 256 * 1024, + use_parts: true, + }, + name: "Windows 95", + }, + { + id: "windows95-boot", + memory_size: 64 * 1024 * 1024, + hda: { + url: host + "windows95-v2/.img", + size: 471859200, + async: true, + fixed_chunk_size: 256 * 1024, + use_parts: true, + }, + name: "Windows 95", + }, + { + id: "windows30", + memory_size: 64 * 1024 * 1024, + cdrom: { + url: host + "Win30.iso", + size: 7774208, + async: false, + }, + name: "Windows 3.0", + }, + { + id: "windows31", + memory_size: 64 * 1024 * 1024, + hda: { + url: host + "win31.img", + async: false, + size: 34463744, + }, + name: "Windows 3.1", + }, + { + id: "tilck", + memory_size: 128 * 1024 * 1024, + hda: { + url: host + "tilck.img", + async: false, + size: 37748736, + }, + name: "Tilck", + homepage: "https://github.com/vvaltchev/tilck", + }, + { + id: "littlekernel", + multiboot: { + url: host + "littlekernel-multiboot.img", + async: false, + size: 969580, + }, + name: "Little Kernel", + homepage: "https://github.com/littlekernel/lk", + }, + { + id: "sanos", + memory_size: 128 * 1024 * 1024, + hda: { + url: host + "sanos-flp.img", + async: false, + size: 1474560, + }, + name: "Sanos", + homepage: "http://www.jbox.dk/sanos/", + }, + { + id: "freebsd", + memory_size: 256 * 1024 * 1024, + hda: { + url: host + "freebsd/.img", + size: 2147483648, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + state: { url: host + "freebsd_state.bin.zst" }, + name: "FreeBSD", + }, + { + id: "freebsd-boot", + memory_size: 256 * 1024 * 1024, + hda: { + url: host + "freebsd/.img", + size: 2147483648, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + name: "FreeBSD", + }, + { + id: "reactos", + memory_size: 512 * 1024 * 1024, + hda: { + url: host + "reactos-v2/.img", + size: 681574400, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + state: { url: host + "reactos_state-v2.bin.zst" }, + mac_address_translation: true, + name: "ReactOS", + acpi: true, + homepage: "https://reactos.org/", + }, + { + id: "reactos-boot", + memory_size: 512 * 1024 * 1024, + hda: { + url: host + "reactos-v2/.img", + size: 681574400, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + name: "ReactOS", + acpi: true, + homepage: "https://reactos.org/", + }, + { + id: "skift", + cdrom: { + url: host + "skift-20200910.iso", + size: 64452608, + async: false, + }, + name: "Skift", + homepage: "https://skiftos.org/", + }, + { + id: "snowdrop", + fda: { + url: host + "snowdrop.img", + size: 1440 * 1024, + }, + name: "Snowdrop", + homepage: "http://www.sebastianmihai.com/snowdrop/", + }, + { + id: "openwrt", + hda: { + url: host + "openwrt-18.06.1-x86-legacy-combined-squashfs.img", + size: 19846474, + async: false, + }, + name: "OpenWrt", + }, + { + id: "qnx", + fda: { + url: host + "qnx-demo-network-4.05.img", + size: 1474560, + }, + name: "QNX 4.05", + }, + { + id: "9front", + memory_size: 128 * 1024 * 1024, + hda: { + url: host + "9front-10931.386/.iso", + size: 489453568, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + state: { url: host + "9front_state-v3.bin.zst" }, + acpi: true, + name: "9front", + homepage: "https://9front.org/", + }, + { + id: "9front-boot", + memory_size: 128 * 1024 * 1024, + hda: { + url: host + "9front-10931.386/.iso", + size: 489453568, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + acpi: true, + name: "9front", + homepage: "https://9front.org/", + }, + { + id: "9legacy", + memory_size: 512 * 1024 * 1024, + hda: { + url: host + "9legacy.img", + async: false, + size: 16000000, + }, + name: "9legacy", + homepage: "http://www.9legacy.org/", + //net_device_type: "none", + }, + { + id: "mobius", + fda: { + url: host + "mobius-fd-release5.img", + size: 1474560, + }, + name: "Mobius", + }, + { + id: "android", + memory_size: 512 * 1024 * 1024, + cdrom: { + url: host + "android-x86-1.6-r2/.iso", + size: 54661120, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + name: "Android", + }, + { + id: "android4", + memory_size: 512 * 1024 * 1024, + cdrom: { + url: host + "android_x86_nonsse3_4.4r1_20140904/.iso", + size: 247463936, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + name: "Android 4", + }, + { + id: "tinycore", + memory_size: 256 * 1024 * 1024, + hda: { + url: host + "TinyCore-11.0.iso", + size: 19922944, + async: false, + }, + name: "Tinycore", + homepage: "http://www.tinycorelinux.net/", + }, + { + id: "slitaz", + memory_size: 512 * 1024 * 1024, + hda: { + url: host + "slitaz-rolling-2024.iso", + size: 56573952, + async: false, + }, + name: "SliTaz", + homepage: "https://slitaz.org/", + }, + { + id: "freenos", + memory_size: 256 * 1024 * 1024, + cdrom: { + url: host + "FreeNOS-1.0.3.iso", + async: false, + size: 11014144, + }, + name: "FreeNOS", + acpi: true, + homepage: "http://www.freenos.org/", + }, + { + id: "syllable", + memory_size: 512 * 1024 * 1024, + hda: { + url: host + "syllable-destop-0.6.7/.img", + async: true, + size: 500 * 1024 * 1024, + fixed_chunk_size: 512 * 1024, + use_parts: true, + }, + name: "Syllable", + homepage: "http://syllable.metaproject.frl/", + }, + { + id: "toaruos", + memory_size: 512 * 1024 * 1024, + cdrom: { + url: host + "toaruos-1.6.1-core.iso", + size: 67567616, + async: false, + }, + name: "ToaruOS", + acpi: true, + homepage: "https://toaruos.org/", + }, + { + id: "nopeos", + cdrom: { + url: host + "nopeos-0.1.iso", + size: 532480, + async: false, + }, + name: "Nope OS", + homepage: "https://github.com/d99kris/nopeos", + }, + { + id: "soso", + cdrom: { + url: host + "soso.iso", + size: 22546432, + async: false, + }, + name: "Soso", + homepage: "https://github.com/ozkl/soso", + }, + { + id: "pcmos", + fda: { + url: host + "PCMOS386-9-user-patched.img", + size: 1440 * 1024, + }, + name: "PC-MOS/386", + homepage: "https://github.com/roelandjansen/pcmos386v501", + }, + { + id: "jx", + fda: { + url: host + "jx-demo.img", + size: 1440 * 1024, + }, + name: "JX", + homepage: "https://www4.cs.fau.de/Projects/JX/index.html", + }, + { + id: "house", + fda: { + url: host + "hOp-0.8.img", + size: 1440 * 1024, + }, + name: "House", + homepage: "https://programatica.cs.pdx.edu/House/", + }, + { + id: "bleskos", + name: "BleskOS", + cdrom: { + url: host + "bleskos_2024u32.iso", + size: 1835008, + async: false, + }, + homepage: "https://github.com/VendelinSlezak/BleskOS", + }, + { + id: "boneos", + name: "BoneOS", + cdrom: { + url: host + "BoneOS.iso", + size: 11429888, + async: false, + }, + homepage: "https://amanuel.io/projects/BoneOS/", + }, + { + id: "mikeos", + name: "MikeOS", + cdrom: { + url: host + "mikeos.iso", + size: 3311616, + async: false, + }, + homepage: "https://mikeos.sourceforge.net/", + }, + { + id: "bluejay", + name: "Blue Jay", + fda: { + url: host + "bj050.img", + size: 1474560, + }, + homepage: "https://archiveos.org/blue-jay/", + }, + { + id: "t3xforth", + name: "T3XFORTH", + fda: { + url: host + "t3xforth.img", + size: 1474560, + }, + homepage: "https://t3x.org/t3xforth/", + }, + { + id: "nanoshell", + name: "NanoShell", + cdrom: { + url: host + "nanoshell.iso", + size: 6785024, + async: false, + }, + homepage: "https://github.com/iProgramMC/NanoShellOS", + }, + { + id: "catk", + name: "CatK", + cdrom: { + url: host + "catkernel.iso", + size: 11968512, + async: false, + }, + homepage: "https://catk.neocities.org/", + }, + { + id: "mcp", + name: "M/CP", + fda: { + url: host + "mcp2.img", + size: 512, + }, + homepage: "https://github.com/ybuzoku/MCP", + }, + { + id: "ibm-exploring", + name: "Exploring The IBM Personal Computer", + fda: { + url: host + "ibm-exploring.img", + size: 368640, + }, + }, + { + id: "leetos", + name: "lEEt/OS", + fda: { + url: host + "leetos.img", + size: 1474560, + }, + homepage: "http://sininenankka.dy.fi/leetos/index.php", + }, + { + id: "newos", + name: "NewOS", + fda: { + url: host + "newos-flp.img", + size: 1474560, + async: false, + }, + homepage: "https://newos.org/", + }, + { + id: "aros-broadway", + name: "AROS Broadway", + memory_size: 512 * 1024 * 1024, + cdrom: { + url: host + "broadway10/.iso", + size: 742051840, + async: true, + fixed_chunk_size: 512 * 1024, + use_parts: true, + }, + homepage: "https://web.archive.org/web/20231109224346/http://www.aros-broadway.de/", + }, + { + id: "icaros", + name: "Icaros Desktop", + memory_size: 512 * 1024 * 1024, + cdrom: { + url: host + "icaros-pc-i386-2.3/.iso", + size: 726511616, + async: true, + // NOTE: needs 136MB/287 requests to boot, maybe state image or zst parts? + fixed_chunk_size: 512 * 1024, + use_parts: true, + }, + homepage: "http://vmwaros.blogspot.com/", + }, + { + id: "tinyaros", + name: "Tiny Aros", + memory_size: 512 * 1024 * 1024, + cdrom: { + url: host + "tinyaros-pc-i386/.iso", + size: 111175680, + async: true, + fixed_chunk_size: 512 * 1024, + use_parts: true, + }, + homepage: "https://www.tinyaros.it/", + }, + { + id: "dancy", + name: "Dancy", + cdrom: { + url: host + "dancy.iso", + size: 10485760, + async: false, + }, + homepage: "https://github.com/Tiihala/Dancy", + }, + { + id: "curios", + name: "CuriOS", + hda: { + url: host + "curios.img", + size: 83886080, + async: false, + }, + homepage: "https://github.com/h5n1xp/CuriOS", + }, + { + id: "os64", + name: "OS64", + cdrom: { + url: host + "os64boot.iso", + size: 5580800, + async: false, + }, + homepage: "https://os64.blogspot.com/", + }, + { + id: "ipxe", + name: "iPXE", + cdrom: { + url: host + "ipxe.iso", + size: 4194304, + async: false, + }, + homepage: "https://ipxe.org/", + }, + { + id: "netboot.xyz", + name: "iPXE", + cdrom: { + url: host + "netboot.xyz.iso", + size: 2398208, + async: false, + }, + homepage: "https://netboot.xyz/", + net_device_type: "virtio", + }, + ]; + + if(DEBUG) + { + // see tests/kvm-unit-tests/x86/ + const tests = [ + "realmode", + // All tests below require an APIC + "cmpxchg8b", + "port80", + "setjmp", + "sieve", + "hypercall", // crashes + "init", // stops execution + "msr", // TODO: Expects 64 bit msrs + "smap", // test stops, SMAP not enabled + "tsc_adjust", // TODO: IA32_TSC_ADJUST + "tsc", // TODO: rdtscp + "rmap_chain", // crashes + "memory", // missing mfence (uninteresting) + "taskswitch", // TODO: Jump + "taskswitch2", // TODO: Call TSS + "eventinj", // Missing #nt + "ioapic", + "apic", + ]; + + for(const test of tests) + { + oses.push({ + name: "Test case: " + test, + id: "test-" + test, + memory_size: 128 * 1024 * 1024, + multiboot: { url: "tests/kvm-unit-tests/x86/" + test + ".flat" } + }); } - else if(time < 3600) + } + + const profile = query_args.get("profile"); + + if(!profile && !DEBUG) + { + const link = document.createElement("link"); + link.rel = "prefetch"; + link.href = "build/v86.wasm" + query_append(); + document.head.appendChild(link); + } + + const link = document.createElement("link"); + link.rel = "prefetch"; + link.href = "build/xterm.js"; + document.head.appendChild(link); + + for(const os of oses) + { + if(profile === os.id) { - return (time / 60 | 0) + "m " + pad0(time % 60, 2) + "s"; + start_emulation(os, query_args); + return; + } + + const element = $("start_" + os.id); + + if(element) + { + element.onclick = e => + { + if(!e.ctrlKey) + { + e.preventDefault(); + element.blur(); + start_emulation(os, null); + } + }; + } + } + + if(profile === "custom") + { + // TODO: if one of the file form fields has a value (firefox), start here? + + if(query_args.has("hda.url") || query_args.has("cdrom.url") || query_args.has("fda.url")) + { + start_emulation(null, query_args); } else { - return (time / 3600 | 0) + "h " + - pad0((time / 60 | 0) % 60, 2) + "m " + - pad0(time % 60, 2) + "s"; + if(query_args.has("m")) $("memory_size").value = query_args.get("m"); + if(query_args.has("vram")) $("vga_memory_size").value = query_args.get("vram"); + if(query_args.has("relay_url")) $("relay_url").value = query_args.get("relay_url"); + if(query_args.has("mute")) $("disable_audio").checked = bool_arg(query_args.get("mute")); + if(query_args.has("acpi")) $("acpi").checked = bool_arg(query_args.get("acpi")); + if(query_args.has("boot_order")) $("boot_order").value = query_args.get("boot_order"); + } + } + else if(/^[a-zA-Z0-9\-_]+\/[a-zA-Z0-9\-_]+$/g.test(profile)) + { + // experimental: server that allows user-uploaded images + + const base = "https://v86-user-images.b-cdn.net/" + profile; + + fetch(base + "/profile.json") + .catch(e => alert("Profile not found: " + profile)) + .then(response => response.json()) + .then(p => { + function handle_image(o) + { + return o && { url: base + "/" + o["url"], async: o["async"], size: o["size"] }; + } + + const profile = { + id: p["id"], + name: p["name"], + memory_size: p["memory_size"], + vga_memory_size: p["vga_memory_size"], + acpi: p["acpi"], + boot_order: p["boot_order"], + hda: handle_image(p["hda"]), + cdrom: handle_image(p["cdrom"]), + fda: handle_image(p["fda"]), + multiboot: handle_image(p["multiboot"]), + bzimage: handle_image(p["bzimage"]), + initrd: handle_image(p["initrd"]), + }; + + start_emulation(profile, query_args); + }); + } + + const os_info = Array.from(document.querySelectorAll("#oses tbody tr")).map(element => + { + const [_, size_raw, unit] = element.children[1].textContent.match(/([\d\.]+)\+? (\w+)/); + let size = +size_raw; + if(unit === "MB") size *= 1024 * 1024; + else if(unit === "KB") size *= 1024; + return { + element, + size, + graphical: element.children[2].firstChild.className === "gui_icon", + family: element.children[3].textContent.replace(/-like/, ""), + arch: element.children[4].textContent, + status: element.children[5].textContent, + source: element.children[6].textContent, + languages: new Set(element.children[7].textContent.split(", ")), + medium: element.children[8].textContent, + }; + }); + + const known_filter = [ + [ // Family: + { id: "linux", condition: os => os.family === "Linux" }, + { id: "bsd", condition: os => os.family === "BSD" }, + { id: "windows", condition: os => os.family === "Windows" }, + { id: "unix", condition: os => os.family === "Unix" }, + { id: "dos", condition: os => os.family === "DOS" }, + { id: "custom", condition: os => os.family === "Custom" }, + ], + [ // UI: + { id: "graphical", condition: os => os.graphical }, + { id: "text", condition: os => !os.graphical }, + ], + [ // Medium: + { id: "floppy", condition: os => os.medium === "Floppy" }, + { id: "cd", condition: os => os.medium === "CD" }, + { id: "hd", condition: os => os.medium === "HD" }, + ], + [ // Size: + { id: "bootsector", condition: os => os.size <= 512 }, + { id: "lt5mb", condition: os => os.size <= 5 * 1024 * 1024 }, + { id: "gt5mb", condition: os => os.size > 5 * 1024 * 1024 }, + ], + [ // Status: + { id: "modern", condition: os => os.status === "Modern" }, + { id: "historic", condition: os => os.status === "Historic" }, + ], + [ // License: + { id: "opensource", condition: os => os.source === "Open-source" }, + { id: "proprietary", condition: os => os.source === "Proprietary" }, + ], + [ // Arch: + { id: "16bit", condition: os => os.arch === "16-bit" }, + { id: "32bit", condition: os => os.arch === "32-bit" }, + ], + [ // Lang: + { id: "asm", condition: os => os.languages.has("ASM") }, + { id: "c", condition: os => os.languages.has("C") }, + { id: "cpp", condition: os => os.languages.has("C++") }, + { id: "other_lang", condition: os => ["ASM", "C", "C++"].every(lang => !os.languages.has(lang)) }, + ], + ]; + + const defined_filter = []; + for(const known_category of known_filter) + { + const category = known_category.filter(filter => { + const element = document.getElementById(`filter_${filter.id}`); + if(element) + { + element.onchange = update_filters; + filter.element = element; + } + return element; + }); + if(category.length) + { + defined_filter.push(category); } } - let progress_ticks = 0; + function update_filters() + { + const conjunction = []; + for(const category of defined_filter) + { + const disjunction = category.filter(filter => filter.element.checked); + if(disjunction.length) + { + conjunction.push(disjunction); + } + } + for(const os of os_info) + { + os.element.style.display = conjunction.every(disjunction => disjunction.some(filter => filter.condition(os))) ? "" : "none"; + } + } - function show_progress(e) + function set_proxy_value(id, value) + { + const elem = $(id); + if(elem) + { + elem.onclick = () => $("relay_url").value = value; + } + } + set_proxy_value("network_none", ""); + set_proxy_value("network_inbrowser", "inbrowser"); + set_proxy_value("network_fetch", "fetch"); + set_proxy_value("network_relay", "wss://relay.widgetry.org/"); + set_proxy_value("network_wisp", "wisps://wisp.mercurywork.shop/v86/"); +} + +function debug_onload() +{ + // called on window.onload, in debug mode + + const log_levels = $("log_levels"); + + if(!log_levels) + { + return; + } + + for(let i = 0; i < LOG_NAMES.length; i++) + { + const mask = LOG_NAMES[i][0]; + + if(mask === 1) + continue; + + const name = LOG_NAMES[i][1].toLowerCase(); + const input = document.createElement("input"); + const label = document.createElement("label"); + + input.type = "checkbox"; + + label.htmlFor = input.id = "log_" + name; + + if(LOG_LEVEL & mask) + { + input.checked = true; + } + input.mask = mask; + + label.append(input, pads(name, 4) + " "); + log_levels.appendChild(label); + + if(i === Math.floor(LOG_NAMES.length / 2)) + { + log_levels.append("\n"); + } + } + + log_levels.onchange = function(e) + { + const target = e.target; + const mask = target.mask; + + if(target.checked) + { + setLogLevel(LOG_LEVEL | mask); + } + else + { + setLogLevel(LOG_LEVEL & ~mask); + } + + target.blur(); + }; +} + +window.addEventListener("load", onload, false); + +// old webkit fires popstate on every load, fuck webkit +// https://code.google.com/p/chromium/issues/detail?id=63040 +window.addEventListener("load", function() +{ + setTimeout(function() + { + window.addEventListener("popstate", onpopstate); + }, 0); +}); + +// works in firefox and chromium +if(document.readyState === "complete") +{ + onload(); +} + +// we can get here in various ways: +// - the user clicked on the "start emulation" +// - the user clicked on a profile +// - the ?profile= query parameter specified a valid profile +// - the ?profile= query parameter was set to "custom" and at least one disk image was given +function start_emulation(profile, query_args) +{ + $("boot_options").style.display = "none"; + + const new_query_args = new Map(); + new_query_args.set("profile", profile?.id || "custom"); + + const settings = {}; + + if(profile) + { + if(profile.state) + { + $("reset").style.display = "none"; + } + + set_title(profile.name); + + settings.initial_state = profile.state; + settings.filesystem = profile.filesystem; + settings.fda = profile.fda; + settings.cdrom = profile.cdrom; + settings.hda = profile.hda; + settings.multiboot = profile.multiboot; + settings.bzimage = profile.bzimage; + settings.initrd = profile.initrd; + settings.cmdline = profile.cmdline; + settings.bzimage_initrd_from_filesystem = profile.bzimage_initrd_from_filesystem; + settings.mac_address_translation = profile.mac_address_translation; + settings.cpuid_level = profile.cpuid_level; + settings.acpi = profile.acpi; + settings.memory_size = profile.memory_size; + settings.vga_memory_size = profile.vga_memory_size; + settings.boot_order = profile.boot_order; + settings.net_device_type = profile.net_device_type; + + if(!DEBUG && profile.homepage) + { + $("description").style.display = "block"; + const link = document.createElement("a"); + link.href = profile.homepage; + link.textContent = profile.name; + link.target = "_blank"; + $("description").append(document.createTextNode("Running "), link); + } + } + + if(query_args) + { + // ignore certain settings when using a state image + if(!settings.initial_state) + { + let chunk_size = parseInt(query_args.get("chunk_size"), 10); + if(chunk_size >= 0) + { + chunk_size = Math.min(4 * 1024 * 1024, Math.max(512, chunk_size)); + chunk_size = round_up_to_next_power_of_2(chunk_size); + } + else + { + chunk_size = 256 * 1024; + } + + if(query_args.has("hda.url")) + { + settings.hda = { + size: parseInt(query_args.get("hda.size"), 10) || undefined, + // TODO: synchronous if small? + url: query_args.get("hda.url"), + fixed_chunk_size: chunk_size, + async: true, + }; + } + + if(query_args.has("cdrom.url")) + { + settings.cdrom = { + size: parseInt(query_args.get("cdrom.size"), 10) || undefined, + url: query_args.get("cdrom.url"), + fixed_chunk_size: chunk_size, + async: true, + }; + } + + if(query_args.has("fda.url")) + { + settings.fda = { + size: parseInt(query_args.get("fda.size"), 10) || undefined, + url: query_args.get("fda.url"), + async: false, + }; + } + + const m = parseInt(query_args.get("m"), 10); + if(m > 0) + { + settings.memory_size = Math.max(16, m) * 1024 * 1024; + } + + const vram = parseInt(query_args.get("vram"), 10); + if(vram > 0) + { + settings.vga_memory_size = vram * 1024 * 1024; + } + + settings.acpi = query_args.has("acpi") ? bool_arg(query_args.get("acpi")) : settings.acpi; + settings.use_bochs_bios = query_args.get("bios") === "bochs"; + settings.net_device_type = query_args.get("net_device_type") || settings.net_device_type; + } + + settings.relay_url = query_args.get("relay_url"); + settings.disable_jit = bool_arg(query_args.get("disable_jit")); + settings.disable_audio = bool_arg(query_args.get("mute")); + } + + if(!settings.relay_url) + { + settings.relay_url = $("relay_url").value; + if(!DEFAULT_NETWORKING_PROXIES.includes(settings.relay_url)) new_query_args.set("relay_url", settings.relay_url); + } + if(settings.relay_url.startsWith("fetch:")) + { + settings.cors_proxy = settings.relay_url.slice(6); + settings.relay_url = "fetch"; + } + settings.disable_audio = $("disable_audio").checked || settings.disable_audio; + if(settings.disable_audio) new_query_args.set("mute", "1"); + + // some settings cannot be overridden when a state image is used + if(!settings.initial_state) + { + const bios = $("bios").files[0]; + if(bios) + { + settings.bios = { buffer: bios }; + } + const vga_bios = $("vga_bios").files[0]; + if(vga_bios) + { + settings.vga_bios = { buffer: vga_bios }; + } + const fda = $("floppy_image").files[0]; + if(fda) + { + settings.fda = { buffer: fda }; + } + const cdrom = $("cdrom_image").files[0]; + if(cdrom) + { + settings.cdrom = { buffer: cdrom }; + } + const hda = $("hda_image").files[0]; + if(hda) + { + settings.hda = { buffer: hda }; + } + const hdb = $("hdb_image")?.files[0]; + if(hdb) + { + settings.hdb = { buffer: hdb }; + } + const multiboot = $("multiboot_image")?.files[0]; + if(multiboot) + { + settings.multiboot = { buffer: multiboot }; + } + const bzimage = $("bzimage").files[0]; + if(bzimage) + { + settings.bzimage = { buffer: bzimage }; + } + const initrd = $("initrd").files[0]; + if(initrd) + { + settings.initrd = { buffer: initrd }; + } + + const title = multiboot?.name || hda?.name || cdrom?.name || hdb?.name || fda?.name || bios?.name; + if(title) + { + set_title(title); + } + + const MB = 1024 * 1024; + + const memory_size = parseInt($("memory_size").value, 10) || DEFAULT_MEMORY_SIZE; + if(!settings.memory_size || memory_size !== DEFAULT_MEMORY_SIZE) + { + settings.memory_size = memory_size * MB; + } + if(memory_size !== DEFAULT_MEMORY_SIZE) new_query_args.set("m", String(memory_size)); + + const vga_memory_size = parseInt($("vga_memory_size").value, 10) || DEFAULT_VGA_MEMORY_SIZE; + if(!settings.vga_memory_size || vga_memory_size !== DEFAULT_VGA_MEMORY_SIZE) + { + settings.vga_memory_size = vga_memory_size * MB; + } + if(vga_memory_size !== DEFAULT_VGA_MEMORY_SIZE) new_query_args.set("vram", String(vga_memory_size)); + + const boot_order = parseInt($("boot_order").value, 16) || DEFAULT_BOOT_ORDER; + if(!settings.boot_order || boot_order !== DEFAULT_BOOT_ORDER) + { + settings.boot_order = boot_order; + } + if(settings.boot_order !== DEFAULT_BOOT_ORDER) new_query_args.set("boot_order", String(settings.boot_order)); + + if(settings.acpi === undefined) + { + settings.acpi = $("acpi").checked; + if(settings.acpi) new_query_args.set("acpi", "1"); + } + + const BIOSPATH = "bios/"; + + if(!settings.bios) + { + settings.bios = { url: BIOSPATH + (DEBUG ? "seabios-debug.bin" : "seabios.bin") }; + } + if(!settings.vga_bios) + { + settings.vga_bios = { url: BIOSPATH + (DEBUG ? "vgabios-debug.bin" : "vgabios.bin") }; + } + if(settings.use_bochs_bios) + { + settings.bios = { url: BIOSPATH + "bochs-bios.bin" }; + settings.vga_bios = { url: BIOSPATH + "bochs-vgabios.bin" }; + } + } + + if(!query_args) + { + push_state(new_query_args); + } + + const emulator = new V86({ + wasm_path: "build/" + (DEBUG ? "v86-debug.wasm" : "v86.wasm") + query_append(), + screen: { + container: $("screen_container"), + use_graphical_text: false, + }, + net_device: { + type: settings.net_device_type || "ne2k", + relay_url: settings.relay_url, + cors_proxy: settings.cors_proxy + }, + autostart: true, + + memory_size: settings.memory_size, + vga_memory_size: settings.vga_memory_size, + boot_order: settings.boot_order, + + bios: settings.bios, + vga_bios: settings.vga_bios, + fda: settings.fda, + hda: settings.hda, + hdb: settings.hdb, + cdrom: settings.cdrom, + multiboot: settings.multiboot, + bzimage: settings.bzimage, + initrd: settings.initrd, + + cmdline: settings.cmdline, + bzimage_initrd_from_filesystem: settings.bzimage_initrd_from_filesystem, + acpi: settings.acpi, + disable_jit: settings.disable_jit, + initial_state: settings.initial_state, + filesystem: settings.filesystem || {}, + disable_speaker: settings.disable_audio, + mac_address_translation: settings.mac_address_translation, + cpuid_level: settings.cpuid_level, + }); + + if(DEBUG) window.emulator = emulator; + + emulator.add_listener("emulator-ready", function() + { + if(DEBUG) + { + debug_start(emulator); + } + + if(emulator.v86.cpu.wm.exports["profiler_is_enabled"]()) + { + const CLEAR_STATS = false; + + const panel = document.createElement("pre"); + document.body.appendChild(panel); + + setInterval(function() + { + if(!emulator.is_running()) + { + return; + } + + const text = print_stats.stats_to_string(emulator.v86.cpu); + panel.textContent = text; + + CLEAR_STATS && emulator.v86.cpu.clear_opstats(); + }, CLEAR_STATS ? 5000 : 1000); + } + + if(["dsl", "helenos", "android", "android4", "redox", "beos", "9legacy"].includes(profile?.id)) + { + setTimeout(() => { + // hack: Start automatically + emulator.keyboard_send_text(profile.id === "9legacy" ? "1\n" : "\n"); + }, 3000); + } + + init_ui(profile, settings, emulator); + + if(query_args?.has("c")) + { + setTimeout(function() + { + emulator.keyboard_send_text(query_args.get("c") + "\n"); + }, 25); + } + + if(query_args?.has("s")) + { + setTimeout(function() + { + emulator.serial0_send(query_args.get("s") + "\n"); + }, 25); + } + }); + + emulator.add_listener("download-progress", function(e) + { + show_progress(e); + }); + + emulator.add_listener("download-error", function(e) { const el = $("loading"); el.style.display = "block"; - - if(e.file_name.endsWith(".wasm")) - { - const parts = e.file_name.split("/"); - el.textContent = "Fetching " + parts[parts.length - 1] + " ..."; - return; - } - - if(e.file_index === e.file_count - 1 && e.loaded >= e.total - 2048) - { - // last file is (almost) loaded - el.textContent = "Done downloading. Starting now ..."; - return; - } - - let line = "Downloading images "; - - if(typeof e.file_index === "number" && e.file_count) - { - line += "[" + (e.file_index + 1) + "/" + e.file_count + "] "; - } - - if(e.total && typeof e.loaded === "number") - { - var per100 = Math.floor(e.loaded / e.total * 100); - per100 = Math.min(100, Math.max(0, per100)); - - var per50 = Math.floor(per100 / 2); - - line += per100 + "% ["; - line += "#".repeat(per50); - line += " ".repeat(50 - per50) + "]"; - } - else - { - line += ".".repeat(progress_ticks++ % 50); - } - - el.textContent = line; - } - - function $(id) - { - return document.getElementById(id); - } - - // These values were previously stored in localStorage - const elements_to_restore = [ - "memory_size", - "video_memory_size", - "networking_proxy", - "disable_audio", - "enable_acpi", - "boot_order", - ]; - for(const item of elements_to_restore) - { - try - { - window.localStorage.removeItem(item); - } - catch(e) {} - } - - function onload() - { - if(!window.WebAssembly) - { - alert("Your browser is not supported because it doesn't support WebAssembly"); - return; - } - - $("start_emulation").onclick = function(e) - { - start_emulation(null, null); - $("start_emulation").blur(); - e.preventDefault(); - }; - - if(DEBUG) - { - debug_onload(); - } - - if(DEBUG && ON_LOCALHOST) - { - // don't use online relay in debug mode - $("relay_url").value = "ws://localhost:8080/"; - } - - const query_args = new URLSearchParams(location.search); - const host = query_args.get("cdn") || (ON_LOCALHOST ? "images/" : "//i.copy.sh/"); - - // Abandonware OS images are from https://winworldpc.com/library/operating-systems - const oses = [ - { - id: "archlinux", - name: "Arch Linux", - memory_size: 512 * 1024 * 1024, - vga_memory_size: 8 * 1024 * 1024, - state: { url: host + "arch_state-v2.bin.zst" }, - filesystem: { - baseurl: host + "arch/", - }, - net_device_type: "virtio", - }, - { - id: "archlinux-boot", - name: "Arch Linux", - memory_size: 512 * 1024 * 1024, - vga_memory_size: 8 * 1024 * 1024, - filesystem: { - baseurl: host + "arch/", - basefs: { url: host + "fs.json" }, - }, - cmdline: [ - "rw apm=off vga=0x344 video=vesafb:ypan,vremap:8", - "root=host9p rootfstype=9p rootflags=trans=virtio,cache=loose", - "mitigations=off audit=0", - "init_on_free=on", - "tsc=reliable", - "random.trust_cpu=on", - "nowatchdog", - "init=/usr/bin/init-openrc net.ifnames=0 biosdevname=0", - ].join(" "), - bzimage_initrd_from_filesystem: true, - net_device_type: "virtio", - }, - { - id: "copy/skiffos", - name: "SkiffOS", - cdrom: { - url: host + "skiffos/.iso", - size: 124672000, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - memory_size: 512 * 1024 * 1024, - }, - { - id: "serenity", - name: "SerenityOS", - hda: { - url: host + "serenity-v3/.img.zst", - size: 734003200, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - memory_size: 512 * 1024 * 1024, - state: { url: host + "serenity_state-v4.bin.zst" }, - homepage: "https://serenityos.org/", - mac_address_translation: true, - }, - { - id: "serenity-boot", - name: "SerenityOS", - hda: { - url: host + "serenity-v3/.img.zst", - size: 734003200, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - memory_size: 512 * 1024 * 1024, - homepage: "https://serenityos.org/", - }, - { - id: "redox", - name: "Redox", - hda: { - url: host + "redox_demo_i686_2024-09-07_1225_harddrive/.img", - size: 671088640, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - memory_size: 1024 * 1024 * 1024, - state: { url: host + "redox_state-v2.bin.zst" }, - homepage: "https://www.redox-os.org/", - acpi: true, - }, - { - id: "redox-boot", - name: "Redox", - hda: { - url: host + "redox_demo_i686_2024-09-07_1225_harddrive/.img", - size: 671088640, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - memory_size: 1024 * 1024 * 1024, - homepage: "https://www.redox-os.org/", - acpi: true, - }, - { - id: "helenos", - memory_size: 256 * 1024 * 1024, - cdrom: { - //url: host + "HelenOS-0.11.2-ia32.iso", - //size: 25765888, - url: host + "HelenOS-0.14.1-ia32.iso", - size: 25792512, - async: false, - }, - name: "HelenOS", - homepage: "http://www.helenos.org/", - }, - { - id: "fiwix", - memory_size: 256 * 1024 * 1024, - hda: { - url: host + "FiwixOS-3.4-i386/.img", - size: 1024 * 1024 * 1024, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - name: "FiwixOS", - homepage: "https://www.fiwix.org/", - }, - { - id: "haiku", - memory_size: 512 * 1024 * 1024, - hda: { - url: host + "haiku-v4/.img", - size: 1 * 1024 * 1024 * 1024, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - state: { url: host + "haiku_state-v4.bin.zst" }, - name: "Haiku", - homepage: "https://www.haiku-os.org/", - }, - { - id: "haiku-boot", - memory_size: 512 * 1024 * 1024, - hda: { - url: host + "haiku-v4/.img", - size: 1 * 1024 * 1024 * 1024, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - name: "Haiku", - homepage: "https://www.haiku-os.org/", - }, - { - id: "beos", - memory_size: 512 * 1024 * 1024, - hda: { - url: host + "beos5/.img", - size: 536870912, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - name: "BeOS 5", - }, - { - id: "msdos", - hda: { - url: host + "msdos622/.img", - size: 64 * 1024 * 1024, - async: true, - fixed_chunk_size: 256 * 1024, - use_parts: true, - }, - name: "MS-DOS 6.22", - }, - { - id: "msdos4", - fda: { - url: host + "msdos4.img", - size: 1474560, - }, - name: "MS-DOS 4", - }, - { - id: "freedos", - fda: { - url: host + "freedos722.img", - size: 737280, - }, - name: "FreeDOS", - }, - { - id: "freegem", - hda: { - url: host + "freegem/.bin", - size: 209715200, - async: true, - fixed_chunk_size: 256 * 1024, - use_parts: true, - }, - name: "Freedos with FreeGEM", - }, - { - id: "xcom", - fda: { - url: host + "xcom144.img", - size: 1440 * 1024, - }, - name: "Freedos with Xcom", - homepage: "http://xcom.infora.hu/index.html", - }, - { - id: "psychdos", - hda: { - url: host + "psychdos/.img", - size: 549453824, - async: true, - fixed_chunk_size: 256 * 1024, - use_parts: true, - }, - name: "PsychDOS", - homepage: "https://psychoslinux.gitlab.io/DOS/INDEX.HTM", - }, - { - id: "86dos", - fda: { - url: host + "pc86dos.img", - size: 163840, - }, - name: "86-DOS", - homepage: "https://www.os2museum.com/wp/pc-86-dos/", - }, - { - id: "oberon", - hda: { - url: host + "oberon.img", - size: 24 * 1024 * 1024, - async: false, - }, - name: "Oberon", - }, - { - id: "windows1", - fda: { - url: host + "windows101.img", - size: 1474560, - }, - name: "Windows 1.01", - }, - { - id: "windows2", - hda: { - url: host + "windows2.img", - size: 4177920, - async: false, - }, - name: "Windows 2.03", - }, - { - id: "linux26", - cdrom: { - url: host + "linux.iso", - size: 6547456, - async: false, - }, - name: "Linux", - }, - { - id: "linux3", - cdrom: { - url: host + "linux3.iso", - size: 8638464, - async: false, - }, - name: "Linux", - }, - { - id: "linux4", - cdrom: { - url: host + "linux4.iso", - size: 7731200, - async: false, - }, - name: "Linux", - filesystem: {}, - }, - { - id: "buildroot", - bzimage: { - url: host + "buildroot-bzimage.bin", - size: 5166352, - async: false, - }, - name: "Buildroot Linux", - filesystem: {}, - cmdline: "tsc=reliable mitigations=off random.trust_cpu=on", - }, - { - id: "buildroot6", - bzimage: { - url: host + "buildroot-bzimage68.bin", - size: 10068480, - async: false, - }, - name: "Buildroot Linux 6.8", - filesystem: {}, - cmdline: "tsc=reliable mitigations=off random.trust_cpu=on", - }, - { - id: "basiclinux", - hda: { - url: host + "bl3-5.img", - size: 104857600, - async: false, - }, - name: "BasicLinux", - }, - { - id: "xpud", - cdrom: { - url: host + "xpud-0.9.2.iso", - size: 67108864, - async: false, - }, - name: "xPUD", - memory_size: 256 * 1024 * 1024, - }, - { - id: "elks", - hda: { - url: host + "elks-hd32-fat.img", - size: 32514048, - async: false, - }, - name: "ELKS", - homepage: "https://github.com/ghaerr/elks", - }, - { - id: "nodeos", - bzimage: { - url: host + "nodeos-kernel.bin", - size: 14452000, - async: false, - }, - name: "NodeOS", - cmdline: "tsc=reliable mitigations=off random.trust_cpu=on", - }, - { - id: "dsl", - memory_size: 256 * 1024 * 1024, - cdrom: { - url: host + "dsl-4.11.rc2.iso", - size: 52824064, - async: false, - }, - name: "Damn Small Linux", - homepage: "http://www.damnsmalllinux.org/", - }, - { - id: "xwoaf", - memory_size: 256 * 1024 * 1024, - cdrom: { - url: host + "xwoaf_rebuild4.iso", - size: 2205696, - async: false, - }, - name: "xwoaf", - homepage: "https://pupngo.dk/xwinflpy/xwoaf_rebuild.html", - }, - { - id: "minix", - name: "Minix", - memory_size: 256 * 1024 * 1024, - cdrom: { - url: host + "minix-3.3.0/.iso", - size: 605581312, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - homepage: "https://www.minix3.org/", - }, - { - id: "unix-v7", - name: "Unix V7", - hda: { - url: host + "unix-v7x86-0.8a/.img", - size: 152764416, - async: true, - fixed_chunk_size: 256 * 1024, - use_parts: true, - }, - }, - { - id: "kolibrios", - fda: { - url: ON_LOCALHOST ? - host + "kolibri.img" : - "//builds.kolibrios.org/en_US/data/data/kolibri.img", - size: 1474560, - }, - name: "KolibriOS", - homepage: "https://kolibrios.org/en/", - }, - { - id: "kolibrios-fallback", - fda: { - url: host + "kolibri.img", - size: 1474560, - }, - name: "KolibriOS", - }, - { - id: "mu", - hda: { - url: host + "mu-shell.img", - size: 10321920, - async: false, - }, - memory_size: 256 * 1024 * 1024, - name: "Mu", - homepage: "https://github.com/akkartik/mu", - }, - { - id: "openbsd", - hda: { - url: host + "openbsd/.img", - size: 1073741824, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - state: { url: host + "openbsd_state.bin.zst" }, - memory_size: 256 * 1024 * 1024, - name: "OpenBSD", - }, - { - id: "sortix", - cdrom: { - url: host + "sortix-1.0-i686.iso", - size: 71075840, - async: false, - }, - memory_size: 512 * 1024 * 1024, - name: "Sortix", - }, - { - id: "openbsd-boot", - hda: { - url: host + "openbsd/.img", - size: 1073741824, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - memory_size: 256 * 1024 * 1024, - name: "OpenBSD", - //acpi: true, // doesn't seem to work - }, - { - id: "netbsd", - hda: { - url: host + "netbsd/.img", - size: 511000064, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - memory_size: 256 * 1024 * 1024, - name: "NetBSD", - }, - { - id: "crazierl", - multiboot: { - url: host + "crazierl-elf.img", - size: 896592, - async: false, - }, - initrd: { - url: host + "crazierl-initrd.img", - size: 18448316, - async: false, - }, - acpi: true, - cmdline: "kernel /libexec/ld-elf32.so.1", - memory_size: 128 * 1024 * 1024, - name: "Crazierl", - }, - { - id: "solos", - fda: { - url: host + "os8.img", - size: 1474560, - }, - name: "Sol OS", - homepage: "http://oby.ro/os/", - }, - { - id: "bootchess", - fda: { - url: host + "bootchess.img", - size: 1474560, - }, - name: "BootChess", - homepage: "http://www.pouet.net/prod.php?which=64962", - }, - { - id: "bootbasic", - fda: { - url: host + "bootbasic.img", - size: 512, - }, - name: "bootBASIC", - homepage: "https://github.com/nanochess/bootBASIC", - }, - { - id: "bootlogo", - fda: { - url: host + "bootlogo.img", - size: 512, - }, - name: "bootLogo", - homepage: "https://github.com/nanochess/bootLogo", - }, - { - id: "pillman", - fda: { - url: host + "pillman.img", - size: 512, - }, - name: "Pillman", - homepage: "https://github.com/nanochess/Pillman", - }, - { - id: "invaders", - fda: { - url: host + "invaders.img", - size: 512, - }, - name: "Invaders", - homepage: "https://github.com/nanochess/Invaders", - }, - { - id: "sectorlisp", - fda: { - url: host + "sectorlisp-friendly.bin", - size: 512, - }, - name: "SectorLISP", - homepage: "https://justine.lol/sectorlisp2/", - }, - { - id: "sectorforth", - fda: { - url: host + "sectorforth.img", - size: 512, - }, - name: "sectorforth", - homepage: "https://github.com/cesarblum/sectorforth", - }, - { - id: "floppybird", - fda: { - url: host + "floppybird.img", - size: 1474560, - }, - name: "Floppy Bird", - homepage: "http://mihail.co/floppybird", - }, - { - id: "stillalive", - fda: { - url: host + "stillalive-os.img", - size: 368640, - }, - name: "Still Alive", - homepage: "https://github.com/maniekx86/stillalive-os", - }, - { - id: "hello-v86", - fda: { - url: host + "hello-v86.img", - size: 512, - }, - name: "Hello v86", - }, - { - id: "tetros", - fda: { - url: host + "tetros.img", - size: 512, - }, - name: "TetrOS", - homepage: "https://github.com/daniel-e/tetros", - }, - { - id: "dino", - fda: { - url: host + "bootdino.img", - size: 512, - }, - name: "dino", - homepage: "https://github.com/franeklubi/dino", - }, - { - id: "bootrogue", - fda: { - url: host + "bootrogue.img", - size: 512, - }, - name: "bootRogue", - homepage: "https://github.com/nanochess/bootRogue", - }, - { - id: "duskos", - hda: { - url: host + "duskos.img", - async: false, - size: 8388608, - }, - name: "Dusk OS", - homepage: "http://duskos.org/", - }, - { - id: "windows2000", - memory_size: 512 * 1024 * 1024, - hda: { - url: host + "windows2k-v2/.img", - size: 2 * 1024 * 1024 * 1024, - async: true, - fixed_chunk_size: 256 * 1024, - use_parts: true, - }, - name: "Windows 2000", - state: { url: host + "windows2k_state-v3.bin.zst" }, - mac_address_translation: true, - }, - { - id: "windows2000-boot", - memory_size: 512 * 1024 * 1024, - hda: { - url: host + "windows2k-v2/.img", - size: 2 * 1024 * 1024 * 1024, - async: true, - fixed_chunk_size: 256 * 1024, - use_parts: true, - }, - name: "Windows 2000", - }, - { - id: "windows-me", - memory_size: 256 * 1024 * 1024, - hda: { - url: host + "windowsme-v2/.img", - size: 834666496, - async: true, - fixed_chunk_size: 256 * 1024, - use_parts: true, - }, - state: { url: host + "windows-me_state-v2.bin.zst" }, - name: "Windows ME", - }, - { - id: "windowsnt4", - memory_size: 512 * 1024 * 1024, - hda: { - url: host + "winnt4_noacpi/.img", - size: 523837440, - async: true, - fixed_chunk_size: 256 * 1024, - use_parts: true, - }, - name: "Windows NT 4.0", - cpuid_level: 2, - }, - { - id: "windowsnt35", - memory_size: 256 * 1024 * 1024, - hda: { - url: host + "windowsnt351/.img", - size: 163577856, - async: true, - fixed_chunk_size: 256 * 1024, - use_parts: true, - }, - name: "Windows NT 3.51", - }, - { - id: "windowsnt3", - memory_size: 256 * 1024 * 1024, - hda: { - url: host + "winnt31/.img", - size: 87 * 1024 * 1024, - async: true, - fixed_chunk_size: 256 * 1024, - use_parts: true, - }, - name: "Windows NT 3.1", - }, - { - id: "windows98", - memory_size: 128 * 1024 * 1024, - hda: { - url: host + "windows98/.img", - size: 300 * 1024 * 1024, - async: true, - fixed_chunk_size: 256 * 1024, - use_parts: true, - }, - name: "Windows 98", - state: { url: host + "windows98_state.bin.zst" }, - mac_address_translation: true, - }, - { - id: "windows98-boot", - memory_size: 128 * 1024 * 1024, - hda: { - url: host + "windows98/.img", - size: 300 * 1024 * 1024, - async: true, - fixed_chunk_size: 256 * 1024, - use_parts: true, - }, - name: "Windows 98", - }, - { - id: "windows95", - memory_size: 64 * 1024 * 1024, - // old image: - //memory_size: 32 * 1024 * 1024, - //hda: { - // url: host + "w95/.img", - // size: 242049024, - // async: true, - // fixed_chunk_size: 256 * 1024, - // use_parts: true, - //}, - //state: { url: host + "windows95_state.bin.zst" }, - hda: { - url: host + "windows95-v2/.img", - size: 471859200, - async: true, - fixed_chunk_size: 256 * 1024, - use_parts: true, - }, - name: "Windows 95", - }, - { - id: "windows95-boot", - memory_size: 64 * 1024 * 1024, - hda: { - url: host + "windows95-v2/.img", - size: 471859200, - async: true, - fixed_chunk_size: 256 * 1024, - use_parts: true, - }, - name: "Windows 95", - }, - { - id: "windows30", - memory_size: 64 * 1024 * 1024, - cdrom: { - url: host + "Win30.iso", - size: 7774208, - async: false, - }, - name: "Windows 3.0", - }, - { - id: "windows31", - memory_size: 64 * 1024 * 1024, - hda: { - url: host + "win31.img", - async: false, - size: 34463744, - }, - name: "Windows 3.1", - }, - { - id: "tilck", - memory_size: 128 * 1024 * 1024, - hda: { - url: host + "tilck.img", - async: false, - size: 37748736, - }, - name: "Tilck", - homepage: "https://github.com/vvaltchev/tilck", - }, - { - id: "littlekernel", - multiboot: { - url: host + "littlekernel-multiboot.img", - async: false, - size: 969580, - }, - name: "Little Kernel", - homepage: "https://github.com/littlekernel/lk", - }, - { - id: "sanos", - memory_size: 128 * 1024 * 1024, - hda: { - url: host + "sanos-flp.img", - async: false, - size: 1474560, - }, - name: "Sanos", - homepage: "http://www.jbox.dk/sanos/", - }, - { - id: "freebsd", - memory_size: 256 * 1024 * 1024, - hda: { - url: host + "freebsd/.img", - size: 2147483648, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - state: { url: host + "freebsd_state.bin.zst" }, - name: "FreeBSD", - }, - { - id: "freebsd-boot", - memory_size: 256 * 1024 * 1024, - hda: { - url: host + "freebsd/.img", - size: 2147483648, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - name: "FreeBSD", - }, - { - id: "reactos", - memory_size: 512 * 1024 * 1024, - hda: { - url: host + "reactos-v2/.img", - size: 681574400, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - state: { url: host + "reactos_state-v2.bin.zst" }, - mac_address_translation: true, - name: "ReactOS", - acpi: true, - homepage: "https://reactos.org/", - }, - { - id: "reactos-boot", - memory_size: 512 * 1024 * 1024, - hda: { - url: host + "reactos-v2/.img", - size: 681574400, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - name: "ReactOS", - acpi: true, - homepage: "https://reactos.org/", - }, - { - id: "skift", - cdrom: { - url: host + "skift-20200910.iso", - size: 64452608, - async: false, - }, - name: "Skift", - homepage: "https://skiftos.org/", - }, - { - id: "snowdrop", - fda: { - url: host + "snowdrop.img", - size: 1440 * 1024, - }, - name: "Snowdrop", - homepage: "http://www.sebastianmihai.com/snowdrop/", - }, - { - id: "openwrt", - hda: { - url: host + "openwrt-18.06.1-x86-legacy-combined-squashfs.img", - size: 19846474, - async: false, - }, - name: "OpenWrt", - }, - { - id: "qnx", - fda: { - url: host + "qnx-demo-network-4.05.img", - size: 1474560, - }, - name: "QNX 4.05", - }, - { - id: "9front", - memory_size: 128 * 1024 * 1024, - hda: { - url: host + "9front-10931.386/.iso", - size: 489453568, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - state: { url: host + "9front_state-v3.bin.zst" }, - acpi: true, - name: "9front", - homepage: "https://9front.org/", - }, - { - id: "9front-boot", - memory_size: 128 * 1024 * 1024, - hda: { - url: host + "9front-10931.386/.iso", - size: 489453568, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - acpi: true, - name: "9front", - homepage: "https://9front.org/", - }, - { - id: "9legacy", - memory_size: 512 * 1024 * 1024, - hda: { - url: host + "9legacy.img", - async: false, - size: 16000000, - }, - name: "9legacy", - homepage: "http://www.9legacy.org/", - //net_device_type: "none", - }, - { - id: "mobius", - fda: { - url: host + "mobius-fd-release5.img", - size: 1474560, - }, - name: "Mobius", - }, - { - id: "android", - memory_size: 512 * 1024 * 1024, - cdrom: { - url: host + "android-x86-1.6-r2/.iso", - size: 54661120, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - name: "Android", - }, - { - id: "android4", - memory_size: 512 * 1024 * 1024, - cdrom: { - url: host + "android_x86_nonsse3_4.4r1_20140904/.iso", - size: 247463936, - async: true, - fixed_chunk_size: 1024 * 1024, - use_parts: true, - }, - name: "Android 4", - }, - { - id: "tinycore", - memory_size: 256 * 1024 * 1024, - hda: { - url: host + "TinyCore-11.0.iso", - size: 19922944, - async: false, - }, - name: "Tinycore", - homepage: "http://www.tinycorelinux.net/", - }, - { - id: "slitaz", - memory_size: 512 * 1024 * 1024, - hda: { - url: host + "slitaz-rolling-2024.iso", - size: 56573952, - async: false, - }, - name: "SliTaz", - homepage: "https://slitaz.org/", - }, - { - id: "freenos", - memory_size: 256 * 1024 * 1024, - cdrom: { - url: host + "FreeNOS-1.0.3.iso", - async: false, - size: 11014144, - }, - name: "FreeNOS", - acpi: true, - homepage: "http://www.freenos.org/", - }, - { - id: "syllable", - memory_size: 512 * 1024 * 1024, - hda: { - url: host + "syllable-destop-0.6.7/.img", - async: true, - size: 500 * 1024 * 1024, - fixed_chunk_size: 512 * 1024, - use_parts: true, - }, - name: "Syllable", - homepage: "http://syllable.metaproject.frl/", - }, - { - id: "toaruos", - memory_size: 512 * 1024 * 1024, - cdrom: { - url: host + "toaruos-1.6.1-core.iso", - size: 67567616, - async: false, - }, - name: "ToaruOS", - acpi: true, - homepage: "https://toaruos.org/", - }, - { - id: "nopeos", - cdrom: { - url: host + "nopeos-0.1.iso", - size: 532480, - async: false, - }, - name: "Nope OS", - homepage: "https://github.com/d99kris/nopeos", - }, - { - id: "soso", - cdrom: { - url: host + "soso.iso", - size: 22546432, - async: false, - }, - name: "Soso", - homepage: "https://github.com/ozkl/soso", - }, - { - id: "pcmos", - fda: { - url: host + "PCMOS386-9-user-patched.img", - size: 1440 * 1024, - }, - name: "PC-MOS/386", - homepage: "https://github.com/roelandjansen/pcmos386v501", - }, - { - id: "jx", - fda: { - url: host + "jx-demo.img", - size: 1440 * 1024, - }, - name: "JX", - homepage: "https://www4.cs.fau.de/Projects/JX/index.html", - }, - { - id: "house", - fda: { - url: host + "hOp-0.8.img", - size: 1440 * 1024, - }, - name: "House", - homepage: "https://programatica.cs.pdx.edu/House/", - }, - { - id: "bleskos", - name: "BleskOS", - cdrom: { - url: host + "bleskos_2024u32.iso", - size: 1835008, - async: false, - }, - homepage: "https://github.com/VendelinSlezak/BleskOS", - }, - { - id: "boneos", - name: "BoneOS", - cdrom: { - url: host + "BoneOS.iso", - size: 11429888, - async: false, - }, - homepage: "https://amanuel.io/projects/BoneOS/", - }, - { - id: "mikeos", - name: "MikeOS", - cdrom: { - url: host + "mikeos.iso", - size: 3311616, - async: false, - }, - homepage: "https://mikeos.sourceforge.net/", - }, - { - id: "bluejay", - name: "Blue Jay", - fda: { - url: host + "bj050.img", - size: 1474560, - }, - homepage: "https://archiveos.org/blue-jay/", - }, - { - id: "t3xforth", - name: "T3XFORTH", - fda: { - url: host + "t3xforth.img", - size: 1474560, - }, - homepage: "https://t3x.org/t3xforth/", - }, - { - id: "nanoshell", - name: "NanoShell", - cdrom: { - url: host + "nanoshell.iso", - size: 6785024, - async: false, - }, - homepage: "https://github.com/iProgramMC/NanoShellOS", - }, - { - id: "catk", - name: "CatK", - cdrom: { - url: host + "catkernel.iso", - size: 11968512, - async: false, - }, - homepage: "https://catk.neocities.org/", - }, - { - id: "mcp", - name: "M/CP", - fda: { - url: host + "mcp2.img", - size: 512, - }, - homepage: "https://github.com/ybuzoku/MCP", - }, - { - id: "ibm-exploring", - name: "Exploring The IBM Personal Computer", - fda: { - url: host + "ibm-exploring.img", - size: 368640, - }, - }, - { - id: "leetos", - name: "lEEt/OS", - fda: { - url: host + "leetos.img", - size: 1474560, - }, - homepage: "http://sininenankka.dy.fi/leetos/index.php", - }, - { - id: "newos", - name: "NewOS", - fda: { - url: host + "newos-flp.img", - size: 1474560, - async: false, - }, - homepage: "https://newos.org/", - }, - { - id: "aros-broadway", - name: "AROS Broadway", - memory_size: 512 * 1024 * 1024, - cdrom: { - url: host + "broadway10/.iso", - size: 742051840, - async: true, - fixed_chunk_size: 512 * 1024, - use_parts: true, - }, - homepage: "https://web.archive.org/web/20231109224346/http://www.aros-broadway.de/", - }, - { - id: "icaros", - name: "Icaros Desktop", - memory_size: 512 * 1024 * 1024, - cdrom: { - url: host + "icaros-pc-i386-2.3/.iso", - size: 726511616, - async: true, - // NOTE: needs 136MB/287 requests to boot, maybe state image or zst parts? - fixed_chunk_size: 512 * 1024, - use_parts: true, - }, - homepage: "http://vmwaros.blogspot.com/", - }, - { - id: "tinyaros", - name: "Tiny Aros", - memory_size: 512 * 1024 * 1024, - cdrom: { - url: host + "tinyaros-pc-i386/.iso", - size: 111175680, - async: true, - fixed_chunk_size: 512 * 1024, - use_parts: true, - }, - homepage: "https://www.tinyaros.it/", - }, - { - id: "dancy", - name: "Dancy", - cdrom: { - url: host + "dancy.iso", - size: 10485760, - async: false, - }, - homepage: "https://github.com/Tiihala/Dancy", - }, - { - id: "curios", - name: "CuriOS", - hda: { - url: host + "curios.img", - size: 83886080, - async: false, - }, - homepage: "https://github.com/h5n1xp/CuriOS", - }, - { - id: "os64", - name: "OS64", - cdrom: { - url: host + "os64boot.iso", - size: 5580800, - async: false, - }, - homepage: "https://os64.blogspot.com/", - }, - { - id: "ipxe", - name: "iPXE", - cdrom: { - url: host + "ipxe.iso", - size: 4194304, - async: false, - }, - homepage: "https://ipxe.org/", - }, - { - id: "netboot.xyz", - name: "iPXE", - cdrom: { - url: host + "netboot.xyz.iso", - size: 2398208, - async: false, - }, - homepage: "https://netboot.xyz/", - net_device_type: "virtio", - }, - ]; - - if(DEBUG) - { - // see tests/kvm-unit-tests/x86/ - const tests = [ - "realmode", - // All tests below require an APIC - "cmpxchg8b", - "port80", - "setjmp", - "sieve", - "hypercall", // crashes - "init", // stops execution - "msr", // TODO: Expects 64 bit msrs - "smap", // test stops, SMAP not enabled - "tsc_adjust", // TODO: IA32_TSC_ADJUST - "tsc", // TODO: rdtscp - "rmap_chain", // crashes - "memory", // missing mfence (uninteresting) - "taskswitch", // TODO: Jump - "taskswitch2", // TODO: Call TSS - "eventinj", // Missing #nt - "ioapic", - "apic", - ]; - - for(const test of tests) - { - oses.push({ - name: "Test case: " + test, - id: "test-" + test, - memory_size: 128 * 1024 * 1024, - multiboot: { url: "tests/kvm-unit-tests/x86/" + test + ".flat" } - }); - } - } - - const profile = query_args.get("profile"); - - if(!profile && !DEBUG) - { - const link = document.createElement("link"); - link.rel = "prefetch"; - link.href = "build/v86.wasm" + query_append(); - document.head.appendChild(link); - } - - const link = document.createElement("link"); - link.rel = "prefetch"; - link.href = "build/xterm.js"; - document.head.appendChild(link); - - for(const os of oses) - { - if(profile === os.id) - { - start_emulation(os, query_args); - return; - } - - const element = $("start_" + os.id); - - if(element) - { - element.onclick = e => - { - if(!e.ctrlKey) - { - e.preventDefault(); - element.blur(); - start_emulation(os, null); - } - }; - } - } - - if(profile === "custom") - { - // TODO: if one of the file form fields has a value (firefox), start here? - - if(query_args.has("hda.url") || query_args.has("cdrom.url") || query_args.has("fda.url")) - { - start_emulation(null, query_args); - } - else - { - if(query_args.has("m")) $("memory_size").value = query_args.get("m"); - if(query_args.has("vram")) $("vga_memory_size").value = query_args.get("vram"); - if(query_args.has("relay_url")) $("relay_url").value = query_args.get("relay_url"); - if(query_args.has("mute")) $("disable_audio").checked = bool_arg(query_args.get("mute")); - if(query_args.has("acpi")) $("acpi").checked = bool_arg(query_args.get("acpi")); - if(query_args.has("boot_order")) $("boot_order").value = query_args.get("boot_order"); - } - } - else if(/^[a-zA-Z0-9\-_]+\/[a-zA-Z0-9\-_]+$/g.test(profile)) - { - // experimental: server that allows user-uploaded images - - const base = "https://v86-user-images.b-cdn.net/" + profile; - - fetch(base + "/profile.json") - .catch(e => alert("Profile not found: " + profile)) - .then(response => response.json()) - .then(p => { - function handle_image(o) - { - return o && { url: base + "/" + o["url"], async: o["async"], size: o["size"] }; - } - - const profile = { - id: p["id"], - name: p["name"], - memory_size: p["memory_size"], - vga_memory_size: p["vga_memory_size"], - acpi: p["acpi"], - boot_order: p["boot_order"], - hda: handle_image(p["hda"]), - cdrom: handle_image(p["cdrom"]), - fda: handle_image(p["fda"]), - multiboot: handle_image(p["multiboot"]), - bzimage: handle_image(p["bzimage"]), - initrd: handle_image(p["initrd"]), - }; - - start_emulation(profile, query_args); - }); - } - - const os_info = Array.from(document.querySelectorAll("#oses tbody tr")).map(element => - { - const [_, size_raw, unit] = element.children[1].textContent.match(/([\d\.]+)\+? (\w+)/); - let size = +size_raw; - if(unit === "MB") size *= 1024 * 1024; - else if(unit === "KB") size *= 1024; - return { - element, - size, - graphical: element.children[2].firstChild.className === "gui_icon", - family: element.children[3].textContent.replace(/-like/, ""), - arch: element.children[4].textContent, - status: element.children[5].textContent, - source: element.children[6].textContent, - languages: new Set(element.children[7].textContent.split(", ")), - medium: element.children[8].textContent, - }; - }); - - const known_filter = [ - [ // Family: - { id: "linux", condition: os => os.family === "Linux" }, - { id: "bsd", condition: os => os.family === "BSD" }, - { id: "windows", condition: os => os.family === "Windows" }, - { id: "unix", condition: os => os.family === "Unix" }, - { id: "dos", condition: os => os.family === "DOS" }, - { id: "custom", condition: os => os.family === "Custom" }, - ], - [ // UI: - { id: "graphical", condition: os => os.graphical }, - { id: "text", condition: os => !os.graphical }, - ], - [ // Medium: - { id: "floppy", condition: os => os.medium === "Floppy" }, - { id: "cd", condition: os => os.medium === "CD" }, - { id: "hd", condition: os => os.medium === "HD" }, - ], - [ // Size: - { id: "bootsector", condition: os => os.size <= 512 }, - { id: "lt5mb", condition: os => os.size <= 5 * 1024 * 1024 }, - { id: "gt5mb", condition: os => os.size > 5 * 1024 * 1024 }, - ], - [ // Status: - { id: "modern", condition: os => os.status === "Modern" }, - { id: "historic", condition: os => os.status === "Historic" }, - ], - [ // License: - { id: "opensource", condition: os => os.source === "Open-source" }, - { id: "proprietary", condition: os => os.source === "Proprietary" }, - ], - [ // Arch: - { id: "16bit", condition: os => os.arch === "16-bit" }, - { id: "32bit", condition: os => os.arch === "32-bit" }, - ], - [ // Lang: - { id: "asm", condition: os => os.languages.has("ASM") }, - { id: "c", condition: os => os.languages.has("C") }, - { id: "cpp", condition: os => os.languages.has("C++") }, - { id: "other_lang", condition: os => ["ASM", "C", "C++"].every(lang => !os.languages.has(lang)) }, - ], - ]; - - const defined_filter = []; - for(const known_category of known_filter) - { - const category = known_category.filter(filter => { - const element = document.getElementById(`filter_${filter.id}`); - if(element) - { - element.onchange = update_filters; - filter.element = element; - } - return element; - }); - if(category.length) - { - defined_filter.push(category); - } - } - - function update_filters() - { - const conjunction = []; - for(const category of defined_filter) - { - const disjunction = category.filter(filter => filter.element.checked); - if(disjunction.length) - { - conjunction.push(disjunction); - } - } - for(const os of os_info) - { - os.element.style.display = conjunction.every(disjunction => disjunction.some(filter => filter.condition(os))) ? "" : "none"; - } - } - - function set_proxy_value(id, value) - { - const elem = $(id); - if(elem) - { - elem.onclick = () => $("relay_url").value = value; - } - } - set_proxy_value("network_none", ""); - set_proxy_value("network_inbrowser", "inbrowser"); - set_proxy_value("network_fetch", "fetch"); - set_proxy_value("network_relay", "wss://relay.widgetry.org/"); - set_proxy_value("network_wisp", "wisps://wisp.mercurywork.shop/v86/"); - } - - function debug_onload() - { - // called on window.onload, in debug mode - - const log_levels = $("log_levels"); - - if(!log_levels) - { - return; - } - - for(let i = 0; i < LOG_NAMES.length; i++) - { - const mask = LOG_NAMES[i][0]; - - if(mask === 1) - continue; - - const name = LOG_NAMES[i][1].toLowerCase(); - const input = document.createElement("input"); - const label = document.createElement("label"); - - input.type = "checkbox"; - - label.htmlFor = input.id = "log_" + name; - - if(LOG_LEVEL & mask) - { - input.checked = true; - } - input.mask = mask; - - label.append(input, pads(name, 4) + " "); - log_levels.appendChild(label); - - if(i === Math.floor(LOG_NAMES.length / 2)) - { - log_levels.append("\n"); - } - } - - log_levels.onchange = function(e) - { - const target = e.target; - const mask = target.mask; - - if(target.checked) - { - setLogLevel(LOG_LEVEL | mask); - } - else - { - setLogLevel(LOG_LEVEL & ~mask); - } - - target.blur(); - }; - } - - window.addEventListener("load", onload, false); - - // old webkit fires popstate on every load, fuck webkit - // https://code.google.com/p/chromium/issues/detail?id=63040 - window.addEventListener("load", function() - { - setTimeout(function() - { - window.addEventListener("popstate", onpopstate); - }, 0); + el.textContent = `Loading ${e.file_name} failed. Check your connection and reload the page to try again.`; }); +} - // works in firefox and chromium - if(document.readyState === "complete") +/** + * @param {Object} settings + * @param {V86} emulator + */ +function init_ui(profile, settings, emulator) +{ + $("loading").style.display = "none"; + $("runtime_options").style.display = "block"; + $("runtime_infos").style.display = "block"; + $("screen_container").style.display = "block"; + + if(settings.filesystem) { - onload(); + init_filesystem_panel(emulator); } - - // we can get here in various ways: - // - the user clicked on the "start emulation" - // - the user clicked on a profile - // - the ?profile= query parameter specified a valid profile - // - the ?profile= query parameter was set to "custom" and at least one disk image was given - function start_emulation(profile, query_args) + else { - $("boot_options").style.display = "none"; - - const new_query_args = new Map(); - new_query_args.set("profile", profile?.id || "custom"); - - const settings = {}; - - if(profile) - { - if(profile.state) - { - $("reset").style.display = "none"; - } - - set_title(profile.name); - - settings.initial_state = profile.state; - settings.filesystem = profile.filesystem; - settings.fda = profile.fda; - settings.cdrom = profile.cdrom; - settings.hda = profile.hda; - settings.multiboot = profile.multiboot; - settings.bzimage = profile.bzimage; - settings.initrd = profile.initrd; - settings.cmdline = profile.cmdline; - settings.bzimage_initrd_from_filesystem = profile.bzimage_initrd_from_filesystem; - settings.mac_address_translation = profile.mac_address_translation; - settings.cpuid_level = profile.cpuid_level; - settings.acpi = profile.acpi; - settings.memory_size = profile.memory_size; - settings.vga_memory_size = profile.vga_memory_size; - settings.boot_order = profile.boot_order; - settings.net_device_type = profile.net_device_type; - - if(!DEBUG && profile.homepage) - { - $("description").style.display = "block"; - const link = document.createElement("a"); - link.href = profile.homepage; - link.textContent = profile.name; - link.target = "_blank"; - $("description").append(document.createTextNode("Running "), link); - } - } - - if(query_args) - { - // ignore certain settings when using a state image - if(!settings.initial_state) - { - let chunk_size = parseInt(query_args.get("chunk_size"), 10); - if(chunk_size >= 0) - { - chunk_size = Math.min(4 * 1024 * 1024, Math.max(512, chunk_size)); - chunk_size = round_up_to_next_power_of_2(chunk_size); - } - else - { - chunk_size = 256 * 1024; - } - - if(query_args.has("hda.url")) - { - settings.hda = { - size: parseInt(query_args.get("hda.size"), 10) || undefined, - // TODO: synchronous if small? - url: query_args.get("hda.url"), - fixed_chunk_size: chunk_size, - async: true, - }; - } - - if(query_args.has("cdrom.url")) - { - settings.cdrom = { - size: parseInt(query_args.get("cdrom.size"), 10) || undefined, - url: query_args.get("cdrom.url"), - fixed_chunk_size: chunk_size, - async: true, - }; - } - - if(query_args.has("fda.url")) - { - settings.fda = { - size: parseInt(query_args.get("fda.size"), 10) || undefined, - url: query_args.get("fda.url"), - async: false, - }; - } - - const m = parseInt(query_args.get("m"), 10); - if(m > 0) - { - settings.memory_size = Math.max(16, m) * 1024 * 1024; - } - - const vram = parseInt(query_args.get("vram"), 10); - if(vram > 0) - { - settings.vga_memory_size = vram * 1024 * 1024; - } - - settings.acpi = query_args.has("acpi") ? bool_arg(query_args.get("acpi")) : settings.acpi; - settings.use_bochs_bios = query_args.get("bios") === "bochs"; - settings.net_device_type = query_args.get("net_device_type") || settings.net_device_type; - } - - settings.relay_url = query_args.get("relay_url"); - settings.disable_jit = bool_arg(query_args.get("disable_jit")); - settings.disable_audio = bool_arg(query_args.get("mute")); - } - - if(!settings.relay_url) - { - settings.relay_url = $("relay_url").value; - if(!DEFAULT_NETWORKING_PROXIES.includes(settings.relay_url)) new_query_args.set("relay_url", settings.relay_url); - } - if(settings.relay_url.startsWith("fetch:")) - { - settings.cors_proxy = settings.relay_url.slice(6); - settings.relay_url = "fetch"; - } - settings.disable_audio = $("disable_audio").checked || settings.disable_audio; - if(settings.disable_audio) new_query_args.set("mute", "1"); - - // some settings cannot be overridden when a state image is used - if(!settings.initial_state) - { - const bios = $("bios").files[0]; - if(bios) - { - settings.bios = { buffer: bios }; - } - const vga_bios = $("vga_bios").files[0]; - if(vga_bios) - { - settings.vga_bios = { buffer: vga_bios }; - } - const fda = $("floppy_image").files[0]; - if(fda) - { - settings.fda = { buffer: fda }; - } - const cdrom = $("cdrom_image").files[0]; - if(cdrom) - { - settings.cdrom = { buffer: cdrom }; - } - const hda = $("hda_image").files[0]; - if(hda) - { - settings.hda = { buffer: hda }; - } - const hdb = $("hdb_image")?.files[0]; - if(hdb) - { - settings.hdb = { buffer: hdb }; - } - const multiboot = $("multiboot_image")?.files[0]; - if(multiboot) - { - settings.multiboot = { buffer: multiboot }; - } - const bzimage = $("bzimage").files[0]; - if(bzimage) - { - settings.bzimage = { buffer: bzimage }; - } - const initrd = $("initrd").files[0]; - if(initrd) - { - settings.initrd = { buffer: initrd }; - } - - const title = multiboot?.name || hda?.name || cdrom?.name || hdb?.name || fda?.name || bios?.name; - if(title) - { - set_title(title); - } - - const MB = 1024 * 1024; - - const memory_size = parseInt($("memory_size").value, 10) || DEFAULT_MEMORY_SIZE; - if(!settings.memory_size || memory_size !== DEFAULT_MEMORY_SIZE) - { - settings.memory_size = memory_size * MB; - } - if(memory_size !== DEFAULT_MEMORY_SIZE) new_query_args.set("m", String(memory_size)); - - const vga_memory_size = parseInt($("vga_memory_size").value, 10) || DEFAULT_VGA_MEMORY_SIZE; - if(!settings.vga_memory_size || vga_memory_size !== DEFAULT_VGA_MEMORY_SIZE) - { - settings.vga_memory_size = vga_memory_size * MB; - } - if(vga_memory_size !== DEFAULT_VGA_MEMORY_SIZE) new_query_args.set("vram", String(vga_memory_size)); - - const boot_order = parseInt($("boot_order").value, 16) || DEFAULT_BOOT_ORDER; - if(!settings.boot_order || boot_order !== DEFAULT_BOOT_ORDER) - { - settings.boot_order = boot_order; - } - if(settings.boot_order !== DEFAULT_BOOT_ORDER) new_query_args.set("boot_order", String(settings.boot_order)); - - if(settings.acpi === undefined) - { - settings.acpi = $("acpi").checked; - if(settings.acpi) new_query_args.set("acpi", "1"); - } - - const BIOSPATH = "bios/"; - - if(!settings.bios) - { - settings.bios = { url: BIOSPATH + (DEBUG ? "seabios-debug.bin" : "seabios.bin") }; - } - if(!settings.vga_bios) - { - settings.vga_bios = { url: BIOSPATH + (DEBUG ? "vgabios-debug.bin" : "vgabios.bin") }; - } - if(settings.use_bochs_bios) - { - settings.bios = { url: BIOSPATH + "bochs-bios.bin" }; - settings.vga_bios = { url: BIOSPATH + "bochs-vgabios.bin" }; - } - } - - if(!query_args) - { - push_state(new_query_args); - } - - const emulator = new V86({ - wasm_path: "build/" + (DEBUG ? "v86-debug.wasm" : "v86.wasm") + query_append(), - screen: { - container: $("screen_container"), - use_graphical_text: false, - }, - net_device: { - type: settings.net_device_type || "ne2k", - relay_url: settings.relay_url, - cors_proxy: settings.cors_proxy - }, - autostart: true, - - memory_size: settings.memory_size, - vga_memory_size: settings.vga_memory_size, - boot_order: settings.boot_order, - - bios: settings.bios, - vga_bios: settings.vga_bios, - fda: settings.fda, - hda: settings.hda, - hdb: settings.hdb, - cdrom: settings.cdrom, - multiboot: settings.multiboot, - bzimage: settings.bzimage, - initrd: settings.initrd, - - cmdline: settings.cmdline, - bzimage_initrd_from_filesystem: settings.bzimage_initrd_from_filesystem, - acpi: settings.acpi, - disable_jit: settings.disable_jit, - initial_state: settings.initial_state, - filesystem: settings.filesystem || {}, - disable_speaker: settings.disable_audio, - mac_address_translation: settings.mac_address_translation, - cpuid_level: settings.cpuid_level, - }); - - if(DEBUG) window.emulator = emulator; - - emulator.add_listener("emulator-ready", function() - { - if(DEBUG) - { - debug_start(emulator); - } - - if(emulator.v86.cpu.wm.exports["profiler_is_enabled"]()) - { - const CLEAR_STATS = false; - - const panel = document.createElement("pre"); - document.body.appendChild(panel); - - setInterval(function() - { - if(!emulator.is_running()) - { - return; - } - - const text = print_stats.stats_to_string(emulator.v86.cpu); - panel.textContent = text; - - CLEAR_STATS && emulator.v86.cpu.clear_opstats(); - }, CLEAR_STATS ? 5000 : 1000); - } - - if(["dsl", "helenos", "android", "android4", "redox", "beos", "9legacy"].includes(profile?.id)) - { - setTimeout(() => { - // hack: Start automatically - emulator.keyboard_send_text(profile.id === "9legacy" ? "1\n" : "\n"); - }, 3000); - } - - init_ui(profile, settings, emulator); - - if(query_args?.has("c")) - { - setTimeout(function() - { - emulator.keyboard_send_text(query_args.get("c") + "\n"); - }, 25); - } - - if(query_args?.has("s")) - { - setTimeout(function() - { - emulator.serial0_send(query_args.get("s") + "\n"); - }, 25); - } - }); - - emulator.add_listener("download-progress", function(e) - { - show_progress(e); - }); - - emulator.add_listener("download-error", function(e) - { - const el = $("loading"); - el.style.display = "block"; - el.textContent = `Loading ${e.file_name} failed. Check your connection and reload the page to try again.`; - }); - } - - /** - * @param {Object} settings - * @param {V86} emulator - */ - function init_ui(profile, settings, emulator) - { - $("loading").style.display = "none"; - $("runtime_options").style.display = "block"; - $("runtime_infos").style.display = "block"; - $("screen_container").style.display = "block"; - - if(settings.filesystem) + emulator.add_listener("9p-attach", function() { init_filesystem_panel(emulator); - } - else - { - emulator.add_listener("9p-attach", function() - { - init_filesystem_panel(emulator); - }); - } - - $("run").onclick = function() - { - if(emulator.is_running()) - { - $("run").value = "Run"; - emulator.stop(); - } - else - { - $("run").value = "Pause"; - emulator.run(); - } - - $("run").blur(); - }; - - $("exit").onclick = function() - { - emulator.destroy(); - location.href = location.pathname; - }; - - $("lock_mouse").onclick = function() - { - if(!mouse_is_enabled) - { - $("toggle_mouse").onclick(); - } - - emulator.lock_mouse(); - $("lock_mouse").blur(); - }; - - var mouse_is_enabled = true; - - $("toggle_mouse").onclick = function() - { - mouse_is_enabled = !mouse_is_enabled; - - emulator.mouse_set_status(mouse_is_enabled); - $("toggle_mouse").value = (mouse_is_enabled ? "Dis" : "En") + "able mouse"; - $("toggle_mouse").blur(); - }; - - var last_tick = 0; - var running_time = 0; - var last_instr_counter = 0; - var interval = null; - var os_uses_mouse = false; - var total_instructions = 0; - - function update_info() - { - var now = Date.now(); - - var instruction_counter = emulator.get_instruction_counter(); - - if(instruction_counter < last_instr_counter) - { - // 32-bit wrap-around - last_instr_counter -= 0x100000000; - } - - var last_ips = instruction_counter - last_instr_counter; - last_instr_counter = instruction_counter; - total_instructions += last_ips; - - var delta_time = now - last_tick; - - if(delta_time) - { - running_time += delta_time; - last_tick = now; - - $("speed").textContent = (last_ips / 1000 / delta_time).toFixed(1); - $("avg_speed").textContent = (total_instructions / 1000 / running_time).toFixed(1); - $("running_time").textContent = format_timestamp(running_time / 1000 | 0); - } - } - - emulator.add_listener("emulator-started", function() - { - last_tick = Date.now(); - interval = setInterval(update_info, 1000); }); - - emulator.add_listener("emulator-stopped", function() - { - update_info(); - if(interval !== null) - { - clearInterval(interval); - } - }); - - var stats_9p = { - read: 0, - write: 0, - files: [], - }; - - emulator.add_listener("9p-read-start", function(args) - { - const file = args[0]; - stats_9p.files.push(file); - $("info_filesystem").style.display = "block"; - $("info_filesystem_status").textContent = "Loading ..."; - $("info_filesystem_last_file").textContent = file; - }); - emulator.add_listener("9p-read-end", function(args) - { - stats_9p.read += args[1]; - $("info_filesystem_bytes_read").textContent = stats_9p.read; - - const file = args[0]; - stats_9p.files = stats_9p.files.filter(f => f !== file); - - if(stats_9p.files[0]) - { - $("info_filesystem_last_file").textContent = stats_9p.files[0]; - } - else - { - $("info_filesystem_status").textContent = "Idle"; - } - }); - emulator.add_listener("9p-write-end", function(args) - { - stats_9p.write += args[1]; - $("info_filesystem_bytes_written").textContent = stats_9p.write; - - if(!stats_9p.files[0]) - { - $("info_filesystem_last_file").textContent = args[0]; - } - }); - - var stats_storage = { - read: 0, - read_sectors: 0, - write: 0, - write_sectors: 0, - }; - - $("ide_type").textContent = emulator.disk_images.cdrom ? " (CD-ROM)" : " (hard disk)"; - - emulator.add_listener("ide-read-start", function() - { - $("info_storage").style.display = "block"; - $("info_storage_status").textContent = "Loading ..."; - }); - emulator.add_listener("ide-read-end", function(args) - { - stats_storage.read += args[1]; - stats_storage.read_sectors += args[2]; - - $("info_storage_status").textContent = "Idle"; - $("info_storage_bytes_read").textContent = stats_storage.read; - $("info_storage_sectors_read").textContent = stats_storage.read_sectors; - }); - emulator.add_listener("ide-write-end", function(args) - { - stats_storage.write += args[1]; - stats_storage.write_sectors += args[2]; - - $("info_storage_bytes_written").textContent = stats_storage.write; - $("info_storage_sectors_written").textContent = stats_storage.write_sectors; - }); - - var stats_net = { - bytes_transmitted: 0, - bytes_received: 0, - }; - - emulator.add_listener("eth-receive-end", function(args) - { - stats_net.bytes_received += args[0]; - - $("info_network").style.display = "block"; - $("info_network_bytes_received").textContent = stats_net.bytes_received; - }); - emulator.add_listener("eth-transmit-end", function(args) - { - stats_net.bytes_transmitted += args[0]; - - $("info_network").style.display = "block"; - $("info_network_bytes_transmitted").textContent = stats_net.bytes_transmitted; - }); - - - emulator.add_listener("mouse-enable", function(is_enabled) - { - os_uses_mouse = is_enabled; - $("info_mouse_enabled").textContent = is_enabled ? "Yes" : "No"; - }); - - emulator.add_listener("screen-set-size", function(args) - { - const [w, h, bpp] = args; - $("info_res").textContent = w + "x" + h + (bpp ? "x" + bpp : ""); - $("info_vga_mode").textContent = bpp ? "Graphical" : "Text"; - }); - - - $("reset").onclick = function() - { - emulator.restart(); - $("reset").blur(); - }; - - add_image_download_button(settings.hda, emulator.disk_images.hda, "hda"); - add_image_download_button(settings.hdb, emulator.disk_images.hdb, "hdb"); - add_image_download_button(settings.fda, emulator.disk_images.fda, "fda"); - add_image_download_button(settings.fdb, emulator.disk_images.fdb, "fdb"); - add_image_download_button(settings.cdrom, emulator.disk_images.cdrom, "cdrom"); - - function add_image_download_button(obj, buffer, type) - { - var elem = $("get_" + type + "_image"); - - if(!obj || obj.async) - { - elem.style.display = "none"; - return; - } - - elem.onclick = function(e) - { - const filename = buffer.file && buffer.file.name || ((profile?.id || "v86") + (type === "cdrom" ? ".iso" : ".img")); - - if(buffer.get_as_file) - { - var file = buffer.get_as_file(filename); - download(file, filename); - } - else - { - buffer.get_buffer(function(b) - { - if(b) - { - dump_file(b, filename); - } - else - { - alert("The file could not be loaded. Maybe it's too big?"); - } - }); - } - - elem.blur(); - }; - } - - $("change_fda_image").value = settings.fda ? "Eject floppy image" : "Insert floppy image"; - $("change_fda_image").onclick = function() - { - if(emulator.v86.cpu.devices.fdc.fda_image) - { - emulator.eject_fda(); - $("change_fda_image").value = "Insert floppy image"; - } - else - { - const file_input = document.createElement("input"); - file_input.type = "file"; - file_input.onchange = async function(e) - { - const file = file_input.files[0]; - if(file) - { - await emulator.set_fda({ buffer: file }); - $("change_fda_image").value = "Eject floppy image"; - } - }; - file_input.click(); - } - $("change_fda_image").blur(); - }; - - $("memory_dump").onclick = function() - { - const mem8 = emulator.v86.cpu.mem8; - dump_file(new Uint8Array(mem8.buffer, mem8.byteOffset, mem8.length), "v86memory.bin"); - $("memory_dump").blur(); - }; - - //$("memory_dump_dmp").onclick = function() - //{ - // var memory = emulator.v86.cpu.mem8; - // var memory_size = memory.length; - // var page_size = 4096; - // var header = new Uint8Array(4096); - // var header32 = new Int32Array(header.buffer); - - // header32[0] = 0x45474150; // 'PAGE' - // header32[1] = 0x504D5544; // 'DUMP' - - // header32[0x10 >> 2] = emulator.v86.cpu.cr[3]; // DirectoryTableBase - // header32[0x24 >> 2] = 1; // NumberProcessors - // header32[0xf88 >> 2] = 1; // DumpType: full dump - // header32[0xfa0 >> 2] = header.length + memory_size; // RequiredDumpSpace - - // header32[0x064 + 0 >> 2] = 1; // NumberOfRuns - // header32[0x064 + 4 >> 2] = memory_size / page_size; // NumberOfPages - // header32[0x064 + 8 >> 2] = 0; // BasePage - // header32[0x064 + 12 >> 2] = memory_size / page_size; // PageCount - - // dump_file([header, memory], "v86memory.dmp"); - - // $("memory_dump_dmp").blur(); - //}; - - /** - * @this HTMLElement - */ - $("capture_network_traffic").onclick = function() - { - this.value = "0 packets"; - - let capture = []; - - function do_capture(direction, data) - { - capture.push({ direction, time: performance.now() / 1000, hex_dump: hex_dump(data) }); - $("capture_network_traffic").value = capture.length + " packets"; - } - - emulator.emulator_bus.register("net0-receive", do_capture.bind(this, "I")); - emulator.add_listener("net0-send", do_capture.bind(this, "O")); - - this.onclick = function() - { - const capture_raw = capture.map(({ direction, time, hex_dump }) => { - // https://www.wireshark.org/docs/wsug_html_chunked/ChIOImportSection.html - // In wireshark: file -> import from hex -> tick direction indication, timestamp %s.%f - return direction + " " + time.toFixed(6) + hex_dump + "\n"; - }).join(""); - dump_file(capture_raw, "traffic.hex"); - capture = []; - this.value = "0 packets"; - }; - }; - - - $("save_state").onclick = async function() - { - const result = await emulator.save_state(); - dump_file(result, "v86state.bin"); - - $("save_state").blur(); - }; - - $("load_state").onclick = function() - { - $("load_state_input").click(); - $("load_state").blur(); - }; - - /** - * @this HTMLElement - */ - $("load_state_input").onchange = async function() - { - var file = this.files[0]; - - if(!file) - { - return; - } - - var was_running = emulator.is_running(); - - if(was_running) - { - await emulator.stop(); - } - - var filereader = new FileReader(); - filereader.onload = async function(e) - { - try - { - await emulator.restore_state(e.target.result); - } - catch(err) - { - alert("Something bad happened while restoring the state:\n" + err + "\n\n" + - "Note that the current configuration must be the same as the original"); - throw err; - } - - if(was_running) - { - emulator.run(); - } - }; - filereader.readAsArrayBuffer(file); - - this.value = ""; - }; - - $("ctrlaltdel").onclick = function() - { - emulator.keyboard_send_scancodes([ - 0x1D, // ctrl - 0x38, // alt - 0x53, // delete - - // break codes - 0x1D | 0x80, - 0x38 | 0x80, - 0x53 | 0x80, - ]); - - $("ctrlaltdel").blur(); - }; - - $("alttab").onclick = function() - { - emulator.keyboard_send_scancodes([ - 0x38, // alt - 0x0F, // tab - ]); - - setTimeout(function() - { - emulator.keyboard_send_scancodes([ - 0x38 | 0x80, - 0x0F | 0x80, - ]); - }, 100); - - $("alttab").blur(); - }; - - /** - * @this HTMLElement - */ - $("scale").onchange = function() - { - var n = parseFloat(this.value); - - if(n || n > 0) - { - emulator.screen_set_scale(n, n); - } - }; - - $("fullscreen").onclick = function() - { - emulator.screen_go_fullscreen(); - }; - - $("screen_container").onclick = function() - { - if(emulator.is_running() && emulator.speaker_adapter && emulator.speaker_adapter.audio_context.state === "suspended") - { - emulator.speaker_adapter.audio_context.resume(); - } - - if(mouse_is_enabled && os_uses_mouse) - { - emulator.lock_mouse(); - } - else - { - // allow text selection - if(window.getSelection().isCollapsed) - { - const phone_keyboard = document.getElementsByClassName("phone_keyboard")[0]; - - // stop mobile browser from scrolling into view when the keyboard is shown - phone_keyboard.style.top = document.body.scrollTop + 100 + "px"; - phone_keyboard.style.left = document.body.scrollLeft + 100 + "px"; - - phone_keyboard.focus(); - } - } - }; - - const phone_keyboard = document.getElementsByClassName("phone_keyboard")[0]; - - phone_keyboard.setAttribute("autocorrect", "off"); - phone_keyboard.setAttribute("autocapitalize", "off"); - phone_keyboard.setAttribute("spellcheck", "false"); - phone_keyboard.tabIndex = 0; - - $("screen_container").addEventListener("mousedown", e => - { - phone_keyboard.focus(); - }, false); - - $("take_screenshot").onclick = function() - { - const image = emulator.screen_make_screenshot(); - try { - const w = window.open(""); - w.document.write(image.outerHTML); - } - catch(e) {} - $("take_screenshot").blur(); - }; - - if(emulator.speaker_adapter) - { - let is_muted = false; - - $("mute").onclick = function() - { - if(is_muted) - { - emulator.speaker_adapter.mixer.set_volume(1, undefined); - is_muted = false; - $("mute").value = "Mute"; - } - else - { - emulator.speaker_adapter.mixer.set_volume(0, undefined); - is_muted = true; - $("mute").value = "Unmute"; - } - - $("mute").blur(); - }; - } - else - { - $("mute").remove(); - } - - window.addEventListener("keydown", ctrl_w_rescue, false); - window.addEventListener("keyup", ctrl_w_rescue, false); - window.addEventListener("blur", ctrl_w_rescue, false); - - function ctrl_w_rescue(e) - { - if(e.ctrlKey) - { - window.onbeforeunload = function() - { - window.onbeforeunload = null; - return "CTRL-W cannot be sent to the emulator."; - }; - } - else - { - window.onbeforeunload = null; - } - } - - const script = document.createElement("script"); - script.src = "build/xterm.js"; - script.async = true; - script.onload = function() - { - emulator.set_serial_container_xtermjs($("terminal")); - }; - document.body.appendChild(script); } - function init_filesystem_panel(emulator) + $("run").onclick = function() { - $("filesystem_panel").style.display = "block"; - - /** - * @this HTMLElement - */ - $("filesystem_send_file").onchange = function() + if(emulator.is_running()) { - Array.prototype.forEach.call(this.files, function(file) + $("run").value = "Run"; + emulator.stop(); + } + else + { + $("run").value = "Pause"; + emulator.run(); + } + + $("run").blur(); + }; + + $("exit").onclick = function() + { + emulator.destroy(); + location.href = location.pathname; + }; + + $("lock_mouse").onclick = function() + { + if(!mouse_is_enabled) + { + $("toggle_mouse").onclick(); + } + + emulator.lock_mouse(); + $("lock_mouse").blur(); + }; + + var mouse_is_enabled = true; + + $("toggle_mouse").onclick = function() + { + mouse_is_enabled = !mouse_is_enabled; + + emulator.mouse_set_status(mouse_is_enabled); + $("toggle_mouse").value = (mouse_is_enabled ? "Dis" : "En") + "able mouse"; + $("toggle_mouse").blur(); + }; + + var last_tick = 0; + var running_time = 0; + var last_instr_counter = 0; + var interval = null; + var os_uses_mouse = false; + var total_instructions = 0; + + function update_info() + { + var now = Date.now(); + + var instruction_counter = emulator.get_instruction_counter(); + + if(instruction_counter < last_instr_counter) + { + // 32-bit wrap-around + last_instr_counter -= 0x100000000; + } + + var last_ips = instruction_counter - last_instr_counter; + last_instr_counter = instruction_counter; + total_instructions += last_ips; + + var delta_time = now - last_tick; + + if(delta_time) + { + running_time += delta_time; + last_tick = now; + + $("speed").textContent = (last_ips / 1000 / delta_time).toFixed(1); + $("avg_speed").textContent = (total_instructions / 1000 / running_time).toFixed(1); + $("running_time").textContent = format_timestamp(running_time / 1000 | 0); + } + } + + emulator.add_listener("emulator-started", function() + { + last_tick = Date.now(); + interval = setInterval(update_info, 1000); + }); + + emulator.add_listener("emulator-stopped", function() + { + update_info(); + if(interval !== null) + { + clearInterval(interval); + } + }); + + var stats_9p = { + read: 0, + write: 0, + files: [], + }; + + emulator.add_listener("9p-read-start", function(args) + { + const file = args[0]; + stats_9p.files.push(file); + $("info_filesystem").style.display = "block"; + $("info_filesystem_status").textContent = "Loading ..."; + $("info_filesystem_last_file").textContent = file; + }); + emulator.add_listener("9p-read-end", function(args) + { + stats_9p.read += args[1]; + $("info_filesystem_bytes_read").textContent = stats_9p.read; + + const file = args[0]; + stats_9p.files = stats_9p.files.filter(f => f !== file); + + if(stats_9p.files[0]) + { + $("info_filesystem_last_file").textContent = stats_9p.files[0]; + } + else + { + $("info_filesystem_status").textContent = "Idle"; + } + }); + emulator.add_listener("9p-write-end", function(args) + { + stats_9p.write += args[1]; + $("info_filesystem_bytes_written").textContent = stats_9p.write; + + if(!stats_9p.files[0]) + { + $("info_filesystem_last_file").textContent = args[0]; + } + }); + + var stats_storage = { + read: 0, + read_sectors: 0, + write: 0, + write_sectors: 0, + }; + + $("ide_type").textContent = emulator.disk_images.cdrom ? " (CD-ROM)" : " (hard disk)"; + + emulator.add_listener("ide-read-start", function() + { + $("info_storage").style.display = "block"; + $("info_storage_status").textContent = "Loading ..."; + }); + emulator.add_listener("ide-read-end", function(args) + { + stats_storage.read += args[1]; + stats_storage.read_sectors += args[2]; + + $("info_storage_status").textContent = "Idle"; + $("info_storage_bytes_read").textContent = stats_storage.read; + $("info_storage_sectors_read").textContent = stats_storage.read_sectors; + }); + emulator.add_listener("ide-write-end", function(args) + { + stats_storage.write += args[1]; + stats_storage.write_sectors += args[2]; + + $("info_storage_bytes_written").textContent = stats_storage.write; + $("info_storage_sectors_written").textContent = stats_storage.write_sectors; + }); + + var stats_net = { + bytes_transmitted: 0, + bytes_received: 0, + }; + + emulator.add_listener("eth-receive-end", function(args) + { + stats_net.bytes_received += args[0]; + + $("info_network").style.display = "block"; + $("info_network_bytes_received").textContent = stats_net.bytes_received; + }); + emulator.add_listener("eth-transmit-end", function(args) + { + stats_net.bytes_transmitted += args[0]; + + $("info_network").style.display = "block"; + $("info_network_bytes_transmitted").textContent = stats_net.bytes_transmitted; + }); + + + emulator.add_listener("mouse-enable", function(is_enabled) + { + os_uses_mouse = is_enabled; + $("info_mouse_enabled").textContent = is_enabled ? "Yes" : "No"; + }); + + emulator.add_listener("screen-set-size", function(args) + { + const [w, h, bpp] = args; + $("info_res").textContent = w + "x" + h + (bpp ? "x" + bpp : ""); + $("info_vga_mode").textContent = bpp ? "Graphical" : "Text"; + }); + + + $("reset").onclick = function() + { + emulator.restart(); + $("reset").blur(); + }; + + add_image_download_button(settings.hda, emulator.disk_images.hda, "hda"); + add_image_download_button(settings.hdb, emulator.disk_images.hdb, "hdb"); + add_image_download_button(settings.fda, emulator.disk_images.fda, "fda"); + add_image_download_button(settings.fdb, emulator.disk_images.fdb, "fdb"); + add_image_download_button(settings.cdrom, emulator.disk_images.cdrom, "cdrom"); + + function add_image_download_button(obj, buffer, type) + { + var elem = $("get_" + type + "_image"); + + if(!obj || obj.async) + { + elem.style.display = "none"; + return; + } + + elem.onclick = function(e) + { + const filename = buffer.file && buffer.file.name || ((profile?.id || "v86") + (type === "cdrom" ? ".iso" : ".img")); + + if(buffer.get_as_file) { - var loader = new SyncFileBuffer(file); - loader.onload = function() + var file = buffer.get_as_file(filename); + download(file, filename); + } + else + { + buffer.get_buffer(function(b) { - loader.get_buffer(async function(buffer) + if(b) { - await emulator.create_file("/" + file.name, new Uint8Array(buffer)); - }); - }; - loader.load(); - }, this); - - this.value = ""; - this.blur(); - }; - - /** - * @this HTMLElement - */ - $("filesystem_get_file").onkeypress = async function(e) - { - if(e.which !== 13) - { - return; + dump_file(b, filename); + } + else + { + alert("The file could not be loaded. Maybe it's too big?"); + } + }); } - this.disabled = true; + elem.blur(); + }; + } - let result; + $("change_fda_image").value = settings.fda ? "Eject floppy image" : "Insert floppy image"; + $("change_fda_image").onclick = function() + { + if(emulator.v86.cpu.devices.fdc.fda_image) + { + emulator.eject_fda(); + $("change_fda_image").value = "Insert floppy image"; + } + else + { + const file_input = document.createElement("input"); + file_input.type = "file"; + file_input.onchange = async function(e) + { + const file = file_input.files[0]; + if(file) + { + await emulator.set_fda({ buffer: file }); + $("change_fda_image").value = "Eject floppy image"; + } + }; + file_input.click(); + } + $("change_fda_image").blur(); + }; + + $("memory_dump").onclick = function() + { + const mem8 = emulator.v86.cpu.mem8; + dump_file(new Uint8Array(mem8.buffer, mem8.byteOffset, mem8.length), "v86memory.bin"); + $("memory_dump").blur(); + }; + + //$("memory_dump_dmp").onclick = function() + //{ + // var memory = emulator.v86.cpu.mem8; + // var memory_size = memory.length; + // var page_size = 4096; + // var header = new Uint8Array(4096); + // var header32 = new Int32Array(header.buffer); + + // header32[0] = 0x45474150; // 'PAGE' + // header32[1] = 0x504D5544; // 'DUMP' + + // header32[0x10 >> 2] = emulator.v86.cpu.cr[3]; // DirectoryTableBase + // header32[0x24 >> 2] = 1; // NumberProcessors + // header32[0xf88 >> 2] = 1; // DumpType: full dump + // header32[0xfa0 >> 2] = header.length + memory_size; // RequiredDumpSpace + + // header32[0x064 + 0 >> 2] = 1; // NumberOfRuns + // header32[0x064 + 4 >> 2] = memory_size / page_size; // NumberOfPages + // header32[0x064 + 8 >> 2] = 0; // BasePage + // header32[0x064 + 12 >> 2] = memory_size / page_size; // PageCount + + // dump_file([header, memory], "v86memory.dmp"); + + // $("memory_dump_dmp").blur(); + //}; + + /** + * @this HTMLElement + */ + $("capture_network_traffic").onclick = function() + { + this.value = "0 packets"; + + let capture = []; + + function do_capture(direction, data) + { + capture.push({ direction, time: performance.now() / 1000, hex_dump: hex_dump(data) }); + $("capture_network_traffic").value = capture.length + " packets"; + } + + emulator.emulator_bus.register("net0-receive", do_capture.bind(this, "I")); + emulator.add_listener("net0-send", do_capture.bind(this, "O")); + + this.onclick = function() + { + const capture_raw = capture.map(({ direction, time, hex_dump }) => { + // https://www.wireshark.org/docs/wsug_html_chunked/ChIOImportSection.html + // In wireshark: file -> import from hex -> tick direction indication, timestamp %s.%f + return direction + " " + time.toFixed(6) + hex_dump + "\n"; + }).join(""); + dump_file(capture_raw, "traffic.hex"); + capture = []; + this.value = "0 packets"; + }; + }; + + + $("save_state").onclick = async function() + { + const result = await emulator.save_state(); + dump_file(result, "v86state.bin"); + + $("save_state").blur(); + }; + + $("load_state").onclick = function() + { + $("load_state_input").click(); + $("load_state").blur(); + }; + + /** + * @this HTMLElement + */ + $("load_state_input").onchange = async function() + { + var file = this.files[0]; + + if(!file) + { + return; + } + + var was_running = emulator.is_running(); + + if(was_running) + { + await emulator.stop(); + } + + var filereader = new FileReader(); + filereader.onload = async function(e) + { try { - result = await emulator.read_file(this.value); + await emulator.restore_state(e.target.result); } catch(err) { - console.log(err); + alert("Something bad happened while restoring the state:\n" + err + "\n\n" + + "Note that the current configuration must be the same as the original"); + throw err; } - this.disabled = false; - - if(result) + if(was_running) { - var filename = this.value.replace(/\/$/, "").split("/"); - filename = filename[filename.length - 1] || "root"; + emulator.run(); + } + }; + filereader.readAsArrayBuffer(file); - dump_file(result, filename); - this.value = ""; + this.value = ""; + }; + + $("ctrlaltdel").onclick = function() + { + emulator.keyboard_send_scancodes([ + 0x1D, // ctrl + 0x38, // alt + 0x53, // delete + + // break codes + 0x1D | 0x80, + 0x38 | 0x80, + 0x53 | 0x80, + ]); + + $("ctrlaltdel").blur(); + }; + + $("alttab").onclick = function() + { + emulator.keyboard_send_scancodes([ + 0x38, // alt + 0x0F, // tab + ]); + + setTimeout(function() + { + emulator.keyboard_send_scancodes([ + 0x38 | 0x80, + 0x0F | 0x80, + ]); + }, 100); + + $("alttab").blur(); + }; + + /** + * @this HTMLElement + */ + $("scale").onchange = function() + { + var n = parseFloat(this.value); + + if(n || n > 0) + { + emulator.screen_set_scale(n, n); + } + }; + + $("fullscreen").onclick = function() + { + emulator.screen_go_fullscreen(); + }; + + $("screen_container").onclick = function() + { + if(emulator.is_running() && emulator.speaker_adapter && emulator.speaker_adapter.audio_context.state === "suspended") + { + emulator.speaker_adapter.audio_context.resume(); + } + + if(mouse_is_enabled && os_uses_mouse) + { + emulator.lock_mouse(); + } + else + { + // allow text selection + if(window.getSelection().isCollapsed) + { + const phone_keyboard = document.getElementsByClassName("phone_keyboard")[0]; + + // stop mobile browser from scrolling into view when the keyboard is shown + phone_keyboard.style.top = document.body.scrollTop + 100 + "px"; + phone_keyboard.style.left = document.body.scrollLeft + 100 + "px"; + + phone_keyboard.focus(); + } + } + }; + + const phone_keyboard = document.getElementsByClassName("phone_keyboard")[0]; + + phone_keyboard.setAttribute("autocorrect", "off"); + phone_keyboard.setAttribute("autocapitalize", "off"); + phone_keyboard.setAttribute("spellcheck", "false"); + phone_keyboard.tabIndex = 0; + + $("screen_container").addEventListener("mousedown", e => + { + phone_keyboard.focus(); + }, false); + + $("take_screenshot").onclick = function() + { + const image = emulator.screen_make_screenshot(); + try { + const w = window.open(""); + w.document.write(image.outerHTML); + } + catch(e) {} + $("take_screenshot").blur(); + }; + + if(emulator.speaker_adapter) + { + let is_muted = false; + + $("mute").onclick = function() + { + if(is_muted) + { + emulator.speaker_adapter.mixer.set_volume(1, undefined); + is_muted = false; + $("mute").value = "Mute"; } else { - alert("Can't read file"); + emulator.speaker_adapter.mixer.set_volume(0, undefined); + is_muted = true; + $("mute").value = "Unmute"; } + + $("mute").blur(); }; } - - function debug_start(emulator) + else { - if(!emulator.v86) + $("mute").remove(); + } + + window.addEventListener("keydown", ctrl_w_rescue, false); + window.addEventListener("keyup", ctrl_w_rescue, false); + window.addEventListener("blur", ctrl_w_rescue, false); + + function ctrl_w_rescue(e) + { + if(e.ctrlKey) + { + window.onbeforeunload = function() + { + window.onbeforeunload = null; + return "CTRL-W cannot be sent to the emulator."; + }; + } + else + { + window.onbeforeunload = null; + } + } + + const script = document.createElement("script"); + script.src = "build/xterm.js"; + script.async = true; + script.onload = function() + { + emulator.set_serial_container_xtermjs($("terminal")); + }; + document.body.appendChild(script); +} + +function init_filesystem_panel(emulator) +{ + $("filesystem_panel").style.display = "block"; + + /** + * @this HTMLElement + */ + $("filesystem_send_file").onchange = function() + { + Array.prototype.forEach.call(this.files, function(file) + { + var loader = new SyncFileBuffer(file); + loader.onload = function() + { + loader.get_buffer(async function(buffer) + { + await emulator.create_file("/" + file.name, new Uint8Array(buffer)); + }); + }; + loader.load(); + }, this); + + this.value = ""; + this.blur(); + }; + + /** + * @this HTMLElement + */ + $("filesystem_get_file").onkeypress = async function(e) + { + if(e.which !== 13) { return; } - // called as soon as soon as emulation is started, in debug mode - var debug = emulator.v86.cpu.debug; + this.disabled = true; - $("dump_gdt").onclick = debug.dump_gdt_ldt.bind(debug); - $("dump_idt").onclick = debug.dump_idt.bind(debug); - $("dump_regs").onclick = debug.dump_regs.bind(debug); - $("dump_pt").onclick = debug.dump_page_structures.bind(debug); - - $("dump_log").onclick = function() + let result; + try { - dump_file(log_data.join(""), "v86.log"); - }; - - var cpu = emulator.v86.cpu; - - $("debug_panel").style.display = "block"; - setInterval(function() - { - $("debug_panel").textContent = - cpu.debug.get_regs_short().join("\n") + "\n" + cpu.debug.get_state(); - - $("dump_log").value = "Dump log" + (log_data.length ? " (" + log_data.length + " lines)" : ""); - }, 1000); - - // helps debugging - window.cpu = cpu; - window.dump_file = dump_file; - } - - function onpopstate(e) - { - location.reload(); - } - - function push_state(params) - { - if(window.history.pushState) - { - let search = "?" + Array.from(params.entries()).map(([key, value]) => key + "=" + value.replace(/[?&=#+]/g, encodeURIComponent)).join("&"); - window.history.pushState({ search }, "", search); + result = await emulator.read_file(this.value); } + catch(err) + { + console.log(err); + } + + this.disabled = false; + + if(result) + { + var filename = this.value.replace(/\/$/, "").split("/"); + filename = filename[filename.length - 1] || "root"; + + dump_file(result, filename); + this.value = ""; + } + else + { + alert("Can't read file"); + } + }; +} + +function debug_start(emulator) +{ + if(!emulator.v86) + { + return; } + + // called as soon as soon as emulation is started, in debug mode + var debug = emulator.v86.cpu.debug; + + $("dump_gdt").onclick = debug.dump_gdt_ldt.bind(debug); + $("dump_idt").onclick = debug.dump_idt.bind(debug); + $("dump_regs").onclick = debug.dump_regs.bind(debug); + $("dump_pt").onclick = debug.dump_page_structures.bind(debug); + + $("dump_log").onclick = function() + { + dump_file(log_data.join(""), "v86.log"); + }; + + var cpu = emulator.v86.cpu; + + $("debug_panel").style.display = "block"; + setInterval(function() + { + $("debug_panel").textContent = + cpu.debug.get_regs_short().join("\n") + "\n" + cpu.debug.get_state(); + + $("dump_log").value = "Dump log" + (log_data.length ? " (" + log_data.length + " lines)" : ""); + }, 1000); + + // helps debugging + window.cpu = cpu; + window.dump_file = dump_file; +} + +function onpopstate(e) +{ + location.reload(); +} + +function push_state(params) +{ + if(window.history.pushState) + { + let search = "?" + Array.from(params.entries()).map(([key, value]) => key + "=" + value.replace(/[?&=#+]/g, encodeURIComponent)).join("&"); + window.history.pushState({ search }, "", search); + } +} diff --git a/src/buffer.js b/src/buffer.js index 403ad42a..bc2ca7e3 100644 --- a/src/buffer.js +++ b/src/buffer.js @@ -4,795 +4,795 @@ import { CPU } from "./cpu.js"; import { load_file } from "./lib.js"; import { dbg_assert, dbg_log } from "./log.js"; - // The smallest size the emulated hardware can emit - const BLOCK_SIZE = 256; - - const ASYNC_SAFE = false; - - /** - * Synchronous access to ArrayBuffer - * @constructor - */ - export function SyncBuffer(buffer) - { - dbg_assert(buffer instanceof ArrayBuffer); - - this.buffer = buffer; - this.byteLength = buffer.byteLength; - this.onload = undefined; - this.onprogress = undefined; - } - - SyncBuffer.prototype.load = function() - { - this.onload && this.onload({ buffer: this.buffer }); - }; - - /** - * @this {SyncBuffer|SyncFileBuffer} - * @param {number} start - * @param {number} len - * @param {function(!Uint8Array)} fn - */ - SyncBuffer.prototype.get = function(start, len, fn) - { - dbg_assert(start + len <= this.byteLength); - fn(new Uint8Array(this.buffer, start, len)); - }; - - /** - * @this {SyncBuffer|SyncFileBuffer} - * @param {number} start - * @param {!Uint8Array} slice - * @param {function()} fn - */ - SyncBuffer.prototype.set = function(start, slice, fn) - { - dbg_assert(start + slice.byteLength <= this.byteLength); - - new Uint8Array(this.buffer, start, slice.byteLength).set(slice); - fn(); - }; - - /** - * @this {SyncBuffer|SyncFileBuffer} - * @param {function(!ArrayBuffer)} fn - */ - SyncBuffer.prototype.get_buffer = function(fn) - { - fn(this.buffer); - }; - - /** - * @this {SyncBuffer|SyncFileBuffer} - */ - SyncBuffer.prototype.get_state = function() - { - const state = []; - state[0] = this.byteLength; - state[1] = new Uint8Array(this.buffer); - return state; - }; - - /** - * @this {SyncBuffer|SyncFileBuffer} - */ - SyncBuffer.prototype.set_state = function(state) - { - this.byteLength = state[0]; - this.buffer = state[1].slice().buffer; - }; - - /** - * Asynchronous access to ArrayBuffer, loading blocks lazily as needed, - * using the `Range: bytes=...` header - * - * @constructor - * @param {string} filename Name of the file to download - * @param {number|undefined} size - * @param {number|undefined} fixed_chunk_size - */ - function AsyncXHRBuffer(filename, size, fixed_chunk_size) - { - this.filename = filename; - - this.byteLength = size; - - this.block_cache = new Map(); - this.block_cache_is_write = new Set(); - - this.fixed_chunk_size = fixed_chunk_size; - this.cache_reads = !!fixed_chunk_size; // TODO: could also be useful in other cases (needs testing) - - this.onload = undefined; - this.onprogress = undefined; - } - - AsyncXHRBuffer.prototype.load = function() - { - if(this.byteLength !== undefined) - { - this.onload && this.onload(Object.create(null)); - return; - } - - // Determine the size using a request - - determine_size(this.filename, (error, size) => - { - if(error) - { - throw new Error("Cannot use: " + this.filename + ". " + error); - } - else - { - dbg_assert(size >= 0); - this.byteLength = size; - this.onload && this.onload(Object.create(null)); - } - }); - }; - - /** - * @param {number} offset - * @param {number} len - * @this {AsyncXHRBuffer|AsyncXHRPartfileBuffer|AsyncFileBuffer} - */ - AsyncXHRBuffer.prototype.get_from_cache = function(offset, len) - { - var number_of_blocks = len / BLOCK_SIZE; - var block_index = offset / BLOCK_SIZE; - - for(var i = 0; i < number_of_blocks; i++) - { - var block = this.block_cache.get(block_index + i); - - if(!block) - { - return; - } - } - - if(number_of_blocks === 1) - { - return this.block_cache.get(block_index); - } - else - { - var result = new Uint8Array(len); - for(var i = 0; i < number_of_blocks; i++) - { - result.set(this.block_cache.get(block_index + i), i * BLOCK_SIZE); - } - return result; - } - }; - - /** - * @param {number} offset - * @param {number} len - * @param {function(!Uint8Array)} fn - */ - AsyncXHRBuffer.prototype.get = function(offset, len, fn) - { - dbg_assert(offset + len <= this.byteLength); - dbg_assert(offset % BLOCK_SIZE === 0); - dbg_assert(len % BLOCK_SIZE === 0); - dbg_assert(len); - - var block = this.get_from_cache(offset, len); - if(block) - { - if(ASYNC_SAFE) - { - setTimeout(fn.bind(this, block), 0); - } - else - { - fn(block); - } - return; - } - - var requested_start = offset; - var requested_length = len; - if(this.fixed_chunk_size) - { - requested_start = offset - (offset % this.fixed_chunk_size); - requested_length = Math.ceil((offset - requested_start + len) / this.fixed_chunk_size) * this.fixed_chunk_size; - } - - load_file(this.filename, { - done: function done(buffer) - { - var block = new Uint8Array(buffer); - this.handle_read(requested_start, requested_length, block); - if(requested_start === offset && requested_length === len) - { - fn(block); - } - else - { - fn(block.subarray(offset - requested_start, offset - requested_start + len)); - } - }.bind(this), - range: { start: requested_start, length: requested_length }, - }); - }; - - /** - * Relies on this.byteLength and this.block_cache - * - * @this {AsyncXHRBuffer|AsyncXHRPartfileBuffer|AsyncFileBuffer} - * - * @param {number} start - * @param {!Uint8Array} data - * @param {function()} fn - */ - AsyncXHRBuffer.prototype.set = function(start, data, fn) - { - var len = data.length; - dbg_assert(start + data.byteLength <= this.byteLength); - dbg_assert(start % BLOCK_SIZE === 0); - dbg_assert(len % BLOCK_SIZE === 0); - dbg_assert(len); - - var start_block = start / BLOCK_SIZE; - var block_count = len / BLOCK_SIZE; - - for(var i = 0; i < block_count; i++) - { - var block = this.block_cache.get(start_block + i); - - if(block === undefined) - { - const data_slice = data.slice(i * BLOCK_SIZE, (i + 1) * BLOCK_SIZE); - this.block_cache.set(start_block + i, data_slice); - } - else - { - const data_slice = data.subarray(i * BLOCK_SIZE, (i + 1) * BLOCK_SIZE); - dbg_assert(block.byteLength === data_slice.length); - block.set(data_slice); - } - - this.block_cache_is_write.add(start_block + i); - } - - fn(); - }; - - /** - * @this {AsyncXHRBuffer|AsyncXHRPartfileBuffer|AsyncFileBuffer} - * @param {number} offset - * @param {number} len - * @param {!Uint8Array} block - */ - AsyncXHRBuffer.prototype.handle_read = function(offset, len, block) - { - // Used by AsyncXHRBuffer, AsyncXHRPartfileBuffer and AsyncFileBuffer - // Overwrites blocks from the original source that have been written since - - var start_block = offset / BLOCK_SIZE; - var block_count = len / BLOCK_SIZE; - - for(var i = 0; i < block_count; i++) - { - const cached_block = this.block_cache.get(start_block + i); - - if(cached_block) - { - block.set(cached_block, i * BLOCK_SIZE); - } - else if(this.cache_reads) - { - this.block_cache.set(start_block + i, block.slice(i * BLOCK_SIZE, (i + 1) * BLOCK_SIZE)); - } - } - }; - - AsyncXHRBuffer.prototype.get_buffer = function(fn) - { - // We must download all parts, unlikely a good idea for big files - fn(); - }; - - ///** - // * @this {AsyncXHRBuffer|AsyncXHRPartfileBuffer|AsyncFileBuffer} - // */ - //AsyncXHRBuffer.prototype.get_block_cache = function() - //{ - // var count = Object.keys(this.block_cache).length; - - // var buffer = new Uint8Array(count * BLOCK_SIZE); - // var indices = []; - - // var i = 0; - // for(var index of Object.keys(this.block_cache)) - // { - // var block = this.block_cache.get(index); - // dbg_assert(block.length === BLOCK_SIZE); - // index = +index; - // indices.push(index); - // buffer.set( - // block, - // i * BLOCK_SIZE - // ); - // i++; - // } - - // return { - // buffer, - // indices, - // block_size: BLOCK_SIZE, - // }; - //}; - - /** - * @this {AsyncXHRBuffer|AsyncXHRPartfileBuffer|AsyncFileBuffer} - */ - AsyncXHRBuffer.prototype.get_state = function() - { - const state = []; - const block_cache = []; - - for(const [index, block] of this.block_cache) - { - dbg_assert(isFinite(index)); - if(this.block_cache_is_write.has(index)) - { - block_cache.push([index, block]); - } - } - - state[0] = block_cache; - return state; - }; - - /** - * @this {AsyncXHRBuffer|AsyncXHRPartfileBuffer|AsyncFileBuffer} - */ - AsyncXHRBuffer.prototype.set_state = function(state) - { - const block_cache = state[0]; - this.block_cache.clear(); - this.block_cache_is_write.clear(); - - for(const [index, block] of block_cache) - { - dbg_assert(isFinite(index)); - this.block_cache.set(index, block); - this.block_cache_is_write.add(index); - } - }; - - /** - * Asynchronous access to ArrayBuffer, loading blocks lazily as needed, - * downloading files named filename-%d-%d.ext (where the %d are start and end offset). - * Or, if partfile_alt_format is set, filename-%08d.ext (where %d is the part number, compatible with gnu split). - * - * @constructor - * @param {string} filename Name of the file to download - * @param {number|undefined} size - * @param {number|undefined} fixed_chunk_size - * @param {boolean|undefined} partfile_alt_format - */ - export function AsyncXHRPartfileBuffer(filename, size, fixed_chunk_size, partfile_alt_format, zstd_decompress) - { - const parts = filename.match(/\.[^\.]+(\.zst)?$/); - - this.extension = parts ? parts[0] : ""; - this.basename = filename.substring(0, filename.length - this.extension.length); - - this.is_zstd = this.extension.endsWith(".zst"); - - if(!this.basename.endsWith("/")) - { - this.basename += "-"; - } - - this.block_cache = new Map(); - this.block_cache_is_write = new Set(); - - this.byteLength = size; - this.fixed_chunk_size = fixed_chunk_size; - this.partfile_alt_format = !!partfile_alt_format; - this.zstd_decompress = zstd_decompress; - - this.cache_reads = !!fixed_chunk_size; // TODO: could also be useful in other cases (needs testing) - - this.onload = undefined; - this.onprogress = undefined; - } - - AsyncXHRPartfileBuffer.prototype.load = function() - { - if(this.byteLength !== undefined) - { - this.onload && this.onload(Object.create(null)); - return; - } - dbg_assert(false); - this.onload && this.onload(Object.create(null)); - }; - - /** - * @param {number} offset - * @param {number} len - * @param {function(!Uint8Array)} fn - */ - AsyncXHRPartfileBuffer.prototype.get = function(offset, len, fn) - { - dbg_assert(offset + len <= this.byteLength); - dbg_assert(offset % BLOCK_SIZE === 0); - dbg_assert(len % BLOCK_SIZE === 0); - dbg_assert(len); - - const block = this.get_from_cache(offset, len); - - if(block) - { - if(ASYNC_SAFE) - { - setTimeout(fn.bind(this, block), 0); - } - else - { - fn(block); - } - return; - } - - if(this.fixed_chunk_size) - { - const start_index = Math.floor(offset / this.fixed_chunk_size); - const m_offset = offset - start_index * this.fixed_chunk_size; - dbg_assert(m_offset >= 0); - const total_count = Math.ceil((m_offset + len) / this.fixed_chunk_size); - const blocks = new Uint8Array(total_count * this.fixed_chunk_size); - let finished = 0; - - for(let i = 0; i < total_count; i++) - { - const offset = (start_index + i) * this.fixed_chunk_size; - - const part_filename = - this.partfile_alt_format ? - // matches output of gnu split: - // split -b 512 -a8 -d --additional-suffix .img w95.img w95- - this.basename + (start_index + i + "").padStart(8, "0") + this.extension - : - this.basename + offset + "-" + (offset + this.fixed_chunk_size) + this.extension; - - // XXX: unnecessary allocation - const block = this.get_from_cache(offset, this.fixed_chunk_size); - - if(block) - { - blocks.set(block, i * this.fixed_chunk_size); - finished++; - if(finished === total_count) - { - fn(blocks.subarray(m_offset, m_offset + len)); - } - } - else - { - load_file(part_filename, { - done: async function done(buffer) - { - let block = new Uint8Array(buffer); - - if(this.is_zstd) - { - const decompressed = await this.zstd_decompress(this.fixed_chunk_size, block); - block = new Uint8Array(decompressed); - } - - blocks.set(block, i * this.fixed_chunk_size); - this.handle_read((start_index + i) * this.fixed_chunk_size, this.fixed_chunk_size|0, block); - - finished++; - if(finished === total_count) - { - fn(blocks.subarray(m_offset, m_offset + len)); - } - }.bind(this), - }); - } - } - } - else - { - const part_filename = this.basename + offset + "-" + (offset + len) + this.extension; - - load_file(part_filename, { - done: function done(buffer) - { - dbg_assert(buffer.byteLength === len); - var block = new Uint8Array(buffer); - this.handle_read(offset, len, block); - fn(block); - }.bind(this), - }); - } - }; - - AsyncXHRPartfileBuffer.prototype.get_from_cache = AsyncXHRBuffer.prototype.get_from_cache; - AsyncXHRPartfileBuffer.prototype.set = AsyncXHRBuffer.prototype.set; - AsyncXHRPartfileBuffer.prototype.handle_read = AsyncXHRBuffer.prototype.handle_read; - //AsyncXHRPartfileBuffer.prototype.get_block_cache = AsyncXHRBuffer.prototype.get_block_cache; - AsyncXHRPartfileBuffer.prototype.get_state = AsyncXHRBuffer.prototype.get_state; - AsyncXHRPartfileBuffer.prototype.set_state = AsyncXHRBuffer.prototype.set_state; - - /** - * Synchronous access to File, loading blocks from the input type=file - * The whole file is loaded into memory during initialisation - * - * @constructor - */ - export function SyncFileBuffer(file) - { - this.file = file; - this.byteLength = file.size; - - if(file.size > (1 << 30)) - { - console.warn("SyncFileBuffer: Allocating buffer of " + (file.size >> 20) + " MB ..."); - } - - this.buffer = new ArrayBuffer(file.size); - - this.onload = undefined; - this.onprogress = undefined; - } - - SyncFileBuffer.prototype.load = function() - { - this.load_next(0); - }; - - /** - * @param {number} start - */ - SyncFileBuffer.prototype.load_next = function(start) - { - /** @const */ - var PART_SIZE = 4 << 20; - - var filereader = new FileReader(); - - filereader.onload = function(e) - { - var buffer = new Uint8Array(e.target.result); - new Uint8Array(this.buffer, start).set(buffer); - this.load_next(start + PART_SIZE); - }.bind(this); - - if(this.onprogress) - { - this.onprogress({ - loaded: start, - total: this.byteLength, - lengthComputable: true, - }); - } - - if(start < this.byteLength) - { - var end = Math.min(start + PART_SIZE, this.byteLength); - var slice = this.file.slice(start, end); - filereader.readAsArrayBuffer(slice); - } - else - { - this.file = undefined; - this.onload && this.onload({ buffer: this.buffer }); - } - }; - - SyncFileBuffer.prototype.get = SyncBuffer.prototype.get; - SyncFileBuffer.prototype.set = SyncBuffer.prototype.set; - SyncFileBuffer.prototype.get_buffer = SyncBuffer.prototype.get_buffer; - SyncFileBuffer.prototype.get_state = SyncBuffer.prototype.get_state; - SyncFileBuffer.prototype.set_state = SyncBuffer.prototype.set_state; - - /** - * Asynchronous access to File, loading blocks from the input type=file - * - * @constructor - */ - export function AsyncFileBuffer(file) - { - this.file = file; - this.byteLength = file.size; - - this.block_cache = new Map(); - this.block_cache_is_write = new Set(); - - this.onload = undefined; - this.onprogress = undefined; - } - - AsyncFileBuffer.prototype.load = function() +// The smallest size the emulated hardware can emit +const BLOCK_SIZE = 256; + +const ASYNC_SAFE = false; + +/** + * Synchronous access to ArrayBuffer + * @constructor + */ +export function SyncBuffer(buffer) +{ + dbg_assert(buffer instanceof ArrayBuffer); + + this.buffer = buffer; + this.byteLength = buffer.byteLength; + this.onload = undefined; + this.onprogress = undefined; +} + +SyncBuffer.prototype.load = function() +{ + this.onload && this.onload({ buffer: this.buffer }); +}; + +/** + * @this {SyncBuffer|SyncFileBuffer} + * @param {number} start + * @param {number} len + * @param {function(!Uint8Array)} fn + */ +SyncBuffer.prototype.get = function(start, len, fn) +{ + dbg_assert(start + len <= this.byteLength); + fn(new Uint8Array(this.buffer, start, len)); +}; + +/** + * @this {SyncBuffer|SyncFileBuffer} + * @param {number} start + * @param {!Uint8Array} slice + * @param {function()} fn + */ +SyncBuffer.prototype.set = function(start, slice, fn) +{ + dbg_assert(start + slice.byteLength <= this.byteLength); + + new Uint8Array(this.buffer, start, slice.byteLength).set(slice); + fn(); +}; + +/** + * @this {SyncBuffer|SyncFileBuffer} + * @param {function(!ArrayBuffer)} fn + */ +SyncBuffer.prototype.get_buffer = function(fn) +{ + fn(this.buffer); +}; + +/** + * @this {SyncBuffer|SyncFileBuffer} + */ +SyncBuffer.prototype.get_state = function() +{ + const state = []; + state[0] = this.byteLength; + state[1] = new Uint8Array(this.buffer); + return state; +}; + +/** + * @this {SyncBuffer|SyncFileBuffer} + */ +SyncBuffer.prototype.set_state = function(state) +{ + this.byteLength = state[0]; + this.buffer = state[1].slice().buffer; +}; + +/** + * Asynchronous access to ArrayBuffer, loading blocks lazily as needed, + * using the `Range: bytes=...` header + * + * @constructor + * @param {string} filename Name of the file to download + * @param {number|undefined} size + * @param {number|undefined} fixed_chunk_size + */ +function AsyncXHRBuffer(filename, size, fixed_chunk_size) +{ + this.filename = filename; + + this.byteLength = size; + + this.block_cache = new Map(); + this.block_cache_is_write = new Set(); + + this.fixed_chunk_size = fixed_chunk_size; + this.cache_reads = !!fixed_chunk_size; // TODO: could also be useful in other cases (needs testing) + + this.onload = undefined; + this.onprogress = undefined; +} + +AsyncXHRBuffer.prototype.load = function() +{ + if(this.byteLength !== undefined) { this.onload && this.onload(Object.create(null)); - }; + return; + } - /** - * @param {number} offset - * @param {number} len - * @param {function(!Uint8Array)} fn - */ - AsyncFileBuffer.prototype.get = function(offset, len, fn) + // Determine the size using a request + + determine_size(this.filename, (error, size) => { - dbg_assert(offset % BLOCK_SIZE === 0); - dbg_assert(len % BLOCK_SIZE === 0); - dbg_assert(len); - - var block = this.get_from_cache(offset, len); - if(block) + if(error) + { + throw new Error("Cannot use: " + this.filename + ". " + error); + } + else + { + dbg_assert(size >= 0); + this.byteLength = size; + this.onload && this.onload(Object.create(null)); + } + }); +}; + +/** + * @param {number} offset + * @param {number} len + * @this {AsyncXHRBuffer|AsyncXHRPartfileBuffer|AsyncFileBuffer} + */ +AsyncXHRBuffer.prototype.get_from_cache = function(offset, len) +{ + var number_of_blocks = len / BLOCK_SIZE; + var block_index = offset / BLOCK_SIZE; + + for(var i = 0; i < number_of_blocks; i++) + { + var block = this.block_cache.get(block_index + i); + + if(!block) { - fn(block); return; } + } - var fr = new FileReader(); - - fr.onload = function(e) - { - var buffer = e.target.result; - var block = new Uint8Array(buffer); - - this.handle_read(offset, len, block); - fn(block); - }.bind(this); - - fr.readAsArrayBuffer(this.file.slice(offset, offset + len)); - }; - AsyncFileBuffer.prototype.get_from_cache = AsyncXHRBuffer.prototype.get_from_cache; - AsyncFileBuffer.prototype.set = AsyncXHRBuffer.prototype.set; - AsyncFileBuffer.prototype.handle_read = AsyncXHRBuffer.prototype.handle_read; - AsyncFileBuffer.prototype.get_state = AsyncXHRBuffer.prototype.get_state; - AsyncFileBuffer.prototype.set_state = AsyncXHRBuffer.prototype.set_state; - - AsyncFileBuffer.prototype.get_buffer = function(fn) + if(number_of_blocks === 1) { - // We must load all parts, unlikely a good idea for big files - fn(); - }; - - AsyncFileBuffer.prototype.get_as_file = function(name) - { - var parts = []; - var existing_blocks = Array.from(this.block_cache.keys()).sort(function(x, y) { return x - y; }); - - var current_offset = 0; - - for(var i = 0; i < existing_blocks.length; i++) - { - var block_index = existing_blocks[i]; - var block = this.block_cache.get(block_index); - var start = block_index * BLOCK_SIZE; - dbg_assert(start >= current_offset); - - if(start !== current_offset) - { - parts.push(this.file.slice(current_offset, start)); - current_offset = start; - } - - parts.push(block); - current_offset += block.length; - } - - if(current_offset !== this.file.size) - { - parts.push(this.file.slice(current_offset)); - } - - var file = new File(parts, name); - dbg_assert(file.size === this.file.size); - - return file; - }; - - var determine_size; - if(typeof XMLHttpRequest === "undefined") - { - determine_size = function(path, cb) - { - import("node:" + "fs").then(fs => fs["stat"](path, (err, stats) => - { - if(err) - { - cb(err); - } - else - { - cb(null, stats.size); - } - })); - }; + return this.block_cache.get(block_index); } else { - determine_size = function(url, cb) + var result = new Uint8Array(len); + for(var i = 0; i < number_of_blocks; i++) { - load_file(url, { - done: (buffer, http) => - { - var header = http.getResponseHeader("Content-Range") || ""; - var match = header.match(/\/(\d+)\s*$/); - - if(match) - { - cb(null, +match[1]); - } - else - { - const error = "`Range: bytes=...` header not supported (Got `" + header + "`)"; - cb(error); - } - }, - headers: { - Range: "bytes=0-0", - "X-Accept-Encoding": "identity" - } - }); - }; + result.set(this.block_cache.get(block_index + i), i * BLOCK_SIZE); + } + return result; } +}; - export function buffer_from_object(obj, zstd_decompress_worker) +/** + * @param {number} offset + * @param {number} len + * @param {function(!Uint8Array)} fn + */ +AsyncXHRBuffer.prototype.get = function(offset, len, fn) +{ + dbg_assert(offset + len <= this.byteLength); + dbg_assert(offset % BLOCK_SIZE === 0); + dbg_assert(len % BLOCK_SIZE === 0); + dbg_assert(len); + + var block = this.get_from_cache(offset, len); + if(block) { - // TODO: accept Uint8Array, ArrayBuffer, File, url rather than { url } - - if(obj.buffer instanceof ArrayBuffer) + if(ASYNC_SAFE) { - return new SyncBuffer(obj.buffer); - } - else if(typeof File !== "undefined" && obj.buffer instanceof File) - { - // SyncFileBuffer: - // - loads the whole disk image into memory, impossible for large files (more than 1GB) - // - can later serve get/set operations fast and synchronously - // - takes some time for first load, neglectable for small files (up to 100Mb) - // - // AsyncFileBuffer: - // - loads slices of the file asynchronously as requested - // - slower get/set - - // Heuristics: If file is larger than or equal to 256M, use AsyncFileBuffer - let is_async = obj.async; - if(is_async === undefined) - { - is_async = obj.buffer.size >= 256 * 1024 * 1024; - } - - if(is_async) - { - return new AsyncFileBuffer(obj.buffer); - } - else - { - return new SyncFileBuffer(obj.buffer); - } - } - else if(obj.url) - { - // Note: Only async for now - - if(obj.use_parts) - { - return new AsyncXHRPartfileBuffer(obj.url, obj.size, obj.fixed_chunk_size, false, zstd_decompress_worker); - } - else - { - return new AsyncXHRBuffer(obj.url, obj.size, obj.fixed_chunk_size); - } + setTimeout(fn.bind(this, block), 0); } else { - dbg_log("Ignored file: url=" + obj.url + " buffer=" + obj.buffer); + fn(block); + } + return; + } + + var requested_start = offset; + var requested_length = len; + if(this.fixed_chunk_size) + { + requested_start = offset - (offset % this.fixed_chunk_size); + requested_length = Math.ceil((offset - requested_start + len) / this.fixed_chunk_size) * this.fixed_chunk_size; + } + + load_file(this.filename, { + done: function done(buffer) + { + var block = new Uint8Array(buffer); + this.handle_read(requested_start, requested_length, block); + if(requested_start === offset && requested_length === len) + { + fn(block); + } + else + { + fn(block.subarray(offset - requested_start, offset - requested_start + len)); + } + }.bind(this), + range: { start: requested_start, length: requested_length }, + }); +}; + +/** + * Relies on this.byteLength and this.block_cache + * + * @this {AsyncXHRBuffer|AsyncXHRPartfileBuffer|AsyncFileBuffer} + * + * @param {number} start + * @param {!Uint8Array} data + * @param {function()} fn + */ +AsyncXHRBuffer.prototype.set = function(start, data, fn) +{ + var len = data.length; + dbg_assert(start + data.byteLength <= this.byteLength); + dbg_assert(start % BLOCK_SIZE === 0); + dbg_assert(len % BLOCK_SIZE === 0); + dbg_assert(len); + + var start_block = start / BLOCK_SIZE; + var block_count = len / BLOCK_SIZE; + + for(var i = 0; i < block_count; i++) + { + var block = this.block_cache.get(start_block + i); + + if(block === undefined) + { + const data_slice = data.slice(i * BLOCK_SIZE, (i + 1) * BLOCK_SIZE); + this.block_cache.set(start_block + i, data_slice); + } + else + { + const data_slice = data.subarray(i * BLOCK_SIZE, (i + 1) * BLOCK_SIZE); + dbg_assert(block.byteLength === data_slice.length); + block.set(data_slice); + } + + this.block_cache_is_write.add(start_block + i); + } + + fn(); +}; + +/** + * @this {AsyncXHRBuffer|AsyncXHRPartfileBuffer|AsyncFileBuffer} + * @param {number} offset + * @param {number} len + * @param {!Uint8Array} block + */ +AsyncXHRBuffer.prototype.handle_read = function(offset, len, block) +{ + // Used by AsyncXHRBuffer, AsyncXHRPartfileBuffer and AsyncFileBuffer + // Overwrites blocks from the original source that have been written since + + var start_block = offset / BLOCK_SIZE; + var block_count = len / BLOCK_SIZE; + + for(var i = 0; i < block_count; i++) + { + const cached_block = this.block_cache.get(start_block + i); + + if(cached_block) + { + block.set(cached_block, i * BLOCK_SIZE); + } + else if(this.cache_reads) + { + this.block_cache.set(start_block + i, block.slice(i * BLOCK_SIZE, (i + 1) * BLOCK_SIZE)); } } +}; + +AsyncXHRBuffer.prototype.get_buffer = function(fn) +{ + // We must download all parts, unlikely a good idea for big files + fn(); +}; + +///** +// * @this {AsyncXHRBuffer|AsyncXHRPartfileBuffer|AsyncFileBuffer} +// */ +//AsyncXHRBuffer.prototype.get_block_cache = function() +//{ +// var count = Object.keys(this.block_cache).length; + +// var buffer = new Uint8Array(count * BLOCK_SIZE); +// var indices = []; + +// var i = 0; +// for(var index of Object.keys(this.block_cache)) +// { +// var block = this.block_cache.get(index); +// dbg_assert(block.length === BLOCK_SIZE); +// index = +index; +// indices.push(index); +// buffer.set( +// block, +// i * BLOCK_SIZE +// ); +// i++; +// } + +// return { +// buffer, +// indices, +// block_size: BLOCK_SIZE, +// }; +//}; + +/** + * @this {AsyncXHRBuffer|AsyncXHRPartfileBuffer|AsyncFileBuffer} + */ +AsyncXHRBuffer.prototype.get_state = function() +{ + const state = []; + const block_cache = []; + + for(const [index, block] of this.block_cache) + { + dbg_assert(isFinite(index)); + if(this.block_cache_is_write.has(index)) + { + block_cache.push([index, block]); + } + } + + state[0] = block_cache; + return state; +}; + +/** + * @this {AsyncXHRBuffer|AsyncXHRPartfileBuffer|AsyncFileBuffer} + */ +AsyncXHRBuffer.prototype.set_state = function(state) +{ + const block_cache = state[0]; + this.block_cache.clear(); + this.block_cache_is_write.clear(); + + for(const [index, block] of block_cache) + { + dbg_assert(isFinite(index)); + this.block_cache.set(index, block); + this.block_cache_is_write.add(index); + } +}; + +/** + * Asynchronous access to ArrayBuffer, loading blocks lazily as needed, + * downloading files named filename-%d-%d.ext (where the %d are start and end offset). + * Or, if partfile_alt_format is set, filename-%08d.ext (where %d is the part number, compatible with gnu split). + * + * @constructor + * @param {string} filename Name of the file to download + * @param {number|undefined} size + * @param {number|undefined} fixed_chunk_size + * @param {boolean|undefined} partfile_alt_format + */ +export function AsyncXHRPartfileBuffer(filename, size, fixed_chunk_size, partfile_alt_format, zstd_decompress) +{ + const parts = filename.match(/\.[^\.]+(\.zst)?$/); + + this.extension = parts ? parts[0] : ""; + this.basename = filename.substring(0, filename.length - this.extension.length); + + this.is_zstd = this.extension.endsWith(".zst"); + + if(!this.basename.endsWith("/")) + { + this.basename += "-"; + } + + this.block_cache = new Map(); + this.block_cache_is_write = new Set(); + + this.byteLength = size; + this.fixed_chunk_size = fixed_chunk_size; + this.partfile_alt_format = !!partfile_alt_format; + this.zstd_decompress = zstd_decompress; + + this.cache_reads = !!fixed_chunk_size; // TODO: could also be useful in other cases (needs testing) + + this.onload = undefined; + this.onprogress = undefined; +} + +AsyncXHRPartfileBuffer.prototype.load = function() +{ + if(this.byteLength !== undefined) + { + this.onload && this.onload(Object.create(null)); + return; + } + dbg_assert(false); + this.onload && this.onload(Object.create(null)); +}; + +/** + * @param {number} offset + * @param {number} len + * @param {function(!Uint8Array)} fn + */ +AsyncXHRPartfileBuffer.prototype.get = function(offset, len, fn) +{ + dbg_assert(offset + len <= this.byteLength); + dbg_assert(offset % BLOCK_SIZE === 0); + dbg_assert(len % BLOCK_SIZE === 0); + dbg_assert(len); + + const block = this.get_from_cache(offset, len); + + if(block) + { + if(ASYNC_SAFE) + { + setTimeout(fn.bind(this, block), 0); + } + else + { + fn(block); + } + return; + } + + if(this.fixed_chunk_size) + { + const start_index = Math.floor(offset / this.fixed_chunk_size); + const m_offset = offset - start_index * this.fixed_chunk_size; + dbg_assert(m_offset >= 0); + const total_count = Math.ceil((m_offset + len) / this.fixed_chunk_size); + const blocks = new Uint8Array(total_count * this.fixed_chunk_size); + let finished = 0; + + for(let i = 0; i < total_count; i++) + { + const offset = (start_index + i) * this.fixed_chunk_size; + + const part_filename = + this.partfile_alt_format ? + // matches output of gnu split: + // split -b 512 -a8 -d --additional-suffix .img w95.img w95- + this.basename + (start_index + i + "").padStart(8, "0") + this.extension + : + this.basename + offset + "-" + (offset + this.fixed_chunk_size) + this.extension; + + // XXX: unnecessary allocation + const block = this.get_from_cache(offset, this.fixed_chunk_size); + + if(block) + { + blocks.set(block, i * this.fixed_chunk_size); + finished++; + if(finished === total_count) + { + fn(blocks.subarray(m_offset, m_offset + len)); + } + } + else + { + load_file(part_filename, { + done: async function done(buffer) + { + let block = new Uint8Array(buffer); + + if(this.is_zstd) + { + const decompressed = await this.zstd_decompress(this.fixed_chunk_size, block); + block = new Uint8Array(decompressed); + } + + blocks.set(block, i * this.fixed_chunk_size); + this.handle_read((start_index + i) * this.fixed_chunk_size, this.fixed_chunk_size|0, block); + + finished++; + if(finished === total_count) + { + fn(blocks.subarray(m_offset, m_offset + len)); + } + }.bind(this), + }); + } + } + } + else + { + const part_filename = this.basename + offset + "-" + (offset + len) + this.extension; + + load_file(part_filename, { + done: function done(buffer) + { + dbg_assert(buffer.byteLength === len); + var block = new Uint8Array(buffer); + this.handle_read(offset, len, block); + fn(block); + }.bind(this), + }); + } +}; + +AsyncXHRPartfileBuffer.prototype.get_from_cache = AsyncXHRBuffer.prototype.get_from_cache; +AsyncXHRPartfileBuffer.prototype.set = AsyncXHRBuffer.prototype.set; +AsyncXHRPartfileBuffer.prototype.handle_read = AsyncXHRBuffer.prototype.handle_read; +//AsyncXHRPartfileBuffer.prototype.get_block_cache = AsyncXHRBuffer.prototype.get_block_cache; +AsyncXHRPartfileBuffer.prototype.get_state = AsyncXHRBuffer.prototype.get_state; +AsyncXHRPartfileBuffer.prototype.set_state = AsyncXHRBuffer.prototype.set_state; + +/** + * Synchronous access to File, loading blocks from the input type=file + * The whole file is loaded into memory during initialisation + * + * @constructor + */ +export function SyncFileBuffer(file) +{ + this.file = file; + this.byteLength = file.size; + + if(file.size > (1 << 30)) + { + console.warn("SyncFileBuffer: Allocating buffer of " + (file.size >> 20) + " MB ..."); + } + + this.buffer = new ArrayBuffer(file.size); + + this.onload = undefined; + this.onprogress = undefined; +} + +SyncFileBuffer.prototype.load = function() +{ + this.load_next(0); +}; + +/** + * @param {number} start + */ +SyncFileBuffer.prototype.load_next = function(start) +{ + /** @const */ + var PART_SIZE = 4 << 20; + + var filereader = new FileReader(); + + filereader.onload = function(e) + { + var buffer = new Uint8Array(e.target.result); + new Uint8Array(this.buffer, start).set(buffer); + this.load_next(start + PART_SIZE); + }.bind(this); + + if(this.onprogress) + { + this.onprogress({ + loaded: start, + total: this.byteLength, + lengthComputable: true, + }); + } + + if(start < this.byteLength) + { + var end = Math.min(start + PART_SIZE, this.byteLength); + var slice = this.file.slice(start, end); + filereader.readAsArrayBuffer(slice); + } + else + { + this.file = undefined; + this.onload && this.onload({ buffer: this.buffer }); + } +}; + +SyncFileBuffer.prototype.get = SyncBuffer.prototype.get; +SyncFileBuffer.prototype.set = SyncBuffer.prototype.set; +SyncFileBuffer.prototype.get_buffer = SyncBuffer.prototype.get_buffer; +SyncFileBuffer.prototype.get_state = SyncBuffer.prototype.get_state; +SyncFileBuffer.prototype.set_state = SyncBuffer.prototype.set_state; + +/** + * Asynchronous access to File, loading blocks from the input type=file + * + * @constructor + */ +export function AsyncFileBuffer(file) +{ + this.file = file; + this.byteLength = file.size; + + this.block_cache = new Map(); + this.block_cache_is_write = new Set(); + + this.onload = undefined; + this.onprogress = undefined; +} + +AsyncFileBuffer.prototype.load = function() +{ + this.onload && this.onload(Object.create(null)); +}; + +/** + * @param {number} offset + * @param {number} len + * @param {function(!Uint8Array)} fn + */ +AsyncFileBuffer.prototype.get = function(offset, len, fn) +{ + dbg_assert(offset % BLOCK_SIZE === 0); + dbg_assert(len % BLOCK_SIZE === 0); + dbg_assert(len); + + var block = this.get_from_cache(offset, len); + if(block) + { + fn(block); + return; + } + + var fr = new FileReader(); + + fr.onload = function(e) + { + var buffer = e.target.result; + var block = new Uint8Array(buffer); + + this.handle_read(offset, len, block); + fn(block); + }.bind(this); + + fr.readAsArrayBuffer(this.file.slice(offset, offset + len)); +}; +AsyncFileBuffer.prototype.get_from_cache = AsyncXHRBuffer.prototype.get_from_cache; +AsyncFileBuffer.prototype.set = AsyncXHRBuffer.prototype.set; +AsyncFileBuffer.prototype.handle_read = AsyncXHRBuffer.prototype.handle_read; +AsyncFileBuffer.prototype.get_state = AsyncXHRBuffer.prototype.get_state; +AsyncFileBuffer.prototype.set_state = AsyncXHRBuffer.prototype.set_state; + +AsyncFileBuffer.prototype.get_buffer = function(fn) +{ + // We must load all parts, unlikely a good idea for big files + fn(); +}; + +AsyncFileBuffer.prototype.get_as_file = function(name) +{ + var parts = []; + var existing_blocks = Array.from(this.block_cache.keys()).sort(function(x, y) { return x - y; }); + + var current_offset = 0; + + for(var i = 0; i < existing_blocks.length; i++) + { + var block_index = existing_blocks[i]; + var block = this.block_cache.get(block_index); + var start = block_index * BLOCK_SIZE; + dbg_assert(start >= current_offset); + + if(start !== current_offset) + { + parts.push(this.file.slice(current_offset, start)); + current_offset = start; + } + + parts.push(block); + current_offset += block.length; + } + + if(current_offset !== this.file.size) + { + parts.push(this.file.slice(current_offset)); + } + + var file = new File(parts, name); + dbg_assert(file.size === this.file.size); + + return file; +}; + +var determine_size; +if(typeof XMLHttpRequest === "undefined") +{ + determine_size = function(path, cb) + { + import("node:" + "fs").then(fs => fs["stat"](path, (err, stats) => + { + if(err) + { + cb(err); + } + else + { + cb(null, stats.size); + } + })); + }; +} +else +{ + determine_size = function(url, cb) + { + load_file(url, { + done: (buffer, http) => + { + var header = http.getResponseHeader("Content-Range") || ""; + var match = header.match(/\/(\d+)\s*$/); + + if(match) + { + cb(null, +match[1]); + } + else + { + const error = "`Range: bytes=...` header not supported (Got `" + header + "`)"; + cb(error); + } + }, + headers: { + Range: "bytes=0-0", + "X-Accept-Encoding": "identity" + } + }); + }; +} + +export function buffer_from_object(obj, zstd_decompress_worker) +{ + // TODO: accept Uint8Array, ArrayBuffer, File, url rather than { url } + + if(obj.buffer instanceof ArrayBuffer) + { + return new SyncBuffer(obj.buffer); + } + else if(typeof File !== "undefined" && obj.buffer instanceof File) + { + // SyncFileBuffer: + // - loads the whole disk image into memory, impossible for large files (more than 1GB) + // - can later serve get/set operations fast and synchronously + // - takes some time for first load, neglectable for small files (up to 100Mb) + // + // AsyncFileBuffer: + // - loads slices of the file asynchronously as requested + // - slower get/set + + // Heuristics: If file is larger than or equal to 256M, use AsyncFileBuffer + let is_async = obj.async; + if(is_async === undefined) + { + is_async = obj.buffer.size >= 256 * 1024 * 1024; + } + + if(is_async) + { + return new AsyncFileBuffer(obj.buffer); + } + else + { + return new SyncFileBuffer(obj.buffer); + } + } + else if(obj.url) + { + // Note: Only async for now + + if(obj.use_parts) + { + return new AsyncXHRPartfileBuffer(obj.url, obj.size, obj.fixed_chunk_size, false, zstd_decompress_worker); + } + else + { + return new AsyncXHRBuffer(obj.url, obj.size, obj.fixed_chunk_size); + } + } + else + { + dbg_log("Ignored file: url=" + obj.url + " buffer=" + obj.buffer); + } +} From a2f6cbb5726a0b11172367c766b1a50a07eb8c80 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sun, 6 Apr 2025 23:31:39 +0700 Subject: [PATCH 026/301] s/setLogLevel/set_log_level --- src/browser/main.js | 6 +++--- src/browser/starter.js | 4 ++-- src/config.js | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/browser/main.js b/src/browser/main.js index 21126b89..901466f7 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -2,7 +2,7 @@ import { V86 } from "./starter.js"; import { LOG_NAMES } from "../const.js"; -import { LOG_LEVEL, setLogLevel } from "../config.js"; +import { LOG_LEVEL, set_log_level } from "../config.js"; import { print_stats } from "./print_stats.js"; import { SyncFileBuffer } from "../buffer.js"; import { pad0, pads, hex_dump, dump_file, download, round_up_to_next_power_of_2 } from "../lib.js"; @@ -1721,11 +1721,11 @@ function debug_onload() if(target.checked) { - setLogLevel(LOG_LEVEL | mask); + set_log_level(LOG_LEVEL | mask); } else { - setLogLevel(LOG_LEVEL & ~mask); + set_log_level(LOG_LEVEL & ~mask); } target.blur(); diff --git a/src/browser/starter.js b/src/browser/starter.js index 80be7bf4..a785990f 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -2,7 +2,7 @@ import { v86 } from "../main.js"; import { LOG_CPU, WASM_TABLE_OFFSET, WASM_TABLE_SIZE } from "../const.js"; -import { setLogLevel } from "../config.js"; +import { set_log_level } from "../config.js"; import { get_rand_int, load_file, read_sized_string_from_mem } from "../lib.js"; import { dbg_assert, dbg_trace, dbg_log } from "../log.js"; import { print_stats } from "./print_stats.js"; @@ -138,7 +138,7 @@ export function V86(options) if(typeof options.log_level === "number") { // XXX: Shared between all emulator instances - setLogLevel(options.log_level); + set_log_level(options.log_level); } //var worker = new Worker("src/browser/worker.js"); diff --git a/src/config.js b/src/config.js index 894752e3..9b98bf52 100644 --- a/src/config.js +++ b/src/config.js @@ -30,10 +30,10 @@ export var DUMP_UNCOMPILED_ASSEMBLY = false; export var LOG_LEVEL = LOG_ALL & ~LOG_PS2 & ~LOG_PIT & ~LOG_VIRTIO & ~LOG_9P & ~LOG_PIC & ~LOG_DMA & ~LOG_SERIAL & ~LOG_NET & ~LOG_FLOPPY & ~LOG_DISK & ~LOG_VGA & ~LOG_SB16; - -export function setLogLevel(level) { +export function set_log_level(level) { LOG_LEVEL = level; } + /** * @const * Draws entire buffer and visualizes the layers that would be drawn From 39c129168ebf0fd5c95e34a5a4267eddc6bb0b86 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sun, 6 Apr 2025 23:43:55 +0700 Subject: [PATCH 027/301] inline config.js into its relevant files --- Makefile | 2 +- lib/9p.js | 2 +- src/apic.js | 4 ++- src/browser/main.js | 3 +- src/browser/screen.js | 4 ++- src/browser/starter.js | 3 +- src/config.js | 50 ---------------------------------- src/cpu.js | 3 +- src/elf.js | 3 +- src/io.js | 4 ++- src/log.js | 15 +++++++++- src/rust/cpu/cpu.rs | 2 ++ tests/kvm-unit-tests/README.md | 3 +- 13 files changed, 33 insertions(+), 65 deletions(-) delete mode 100644 src/config.js diff --git a/Makefile b/Makefile index 8914e144..7d76c0c1 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,7 @@ CARGO_FLAGS_SAFE=\ CARGO_FLAGS=$(CARGO_FLAGS_SAFE) -C target-feature=+bulk-memory -C target-feature=+multivalue -C target-feature=+simd128 -CORE_FILES=cjs.js const.js config.js io.js main.js lib.js buffer.js ide.js pci.js floppy.js \ +CORE_FILES=cjs.js const.js io.js main.js lib.js buffer.js ide.js pci.js floppy.js \ dma.js pit.js vga.js ps2.js rtc.js uart.js \ acpi.js apic.js ioapic.js \ state.js ne2k.js sb16.js virtio.js virtio_console.js virtio_net.js virtio_balloon.js \ diff --git a/lib/9p.js b/lib/9p.js index e76b52b4..ada6dbe2 100644 --- a/lib/9p.js +++ b/lib/9p.js @@ -228,7 +228,7 @@ Virtio9p.prototype.set_state = function(state) // Note: dbg_name is only used for debugging messages and may not be the same as the filename, // since it is not synchronised with renames done outside of 9p. Hard-links, linking and unlinking // operations also mean that having a single filename no longer makes sense. -// Set TRACK_FILENAMES = true (in config.js) to sync dbg_name during 9p renames. +// Set TRACK_FILENAMES = true to sync dbg_name during 9p renames. Virtio9p.prototype.Createfid = function(inodeid, type, uid, dbg_name) { return {inodeid, type, uid, dbg_name}; }; diff --git a/src/apic.js b/src/apic.js index d8760cfc..6462de0f 100644 --- a/src/apic.js +++ b/src/apic.js @@ -4,7 +4,6 @@ import { v86 } from "./main.js"; import { LOG_APIC } from "../src/const.js"; -import { APIC_TIMER_FREQ } from "./config.js"; import { h, int_log2 } from "./lib.js"; import { dbg_assert, dbg_log, dbg_trace } from "./log.js"; import { IOAPIC_CONFIG_MASKED, IOAPIC_DELIVERY_INIT, IOAPIC_DELIVERY_NMI, IOAPIC_DELIVERY_FIXED } from "./ioapic.js"; @@ -15,6 +14,9 @@ import { CPU } from "./cpu.js"; /** @const */ export const APIC_LOG_VERBOSE = false; +// should probably be kept in sync with TSC_RATE in cpu.rs +const APIC_TIMER_FREQ = 1 * 1000 * 1000; + /** @const */ var APIC_ADDRESS = 0xFEE00000; diff --git a/src/browser/main.js b/src/browser/main.js index 901466f7..c87ba47e 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -2,11 +2,10 @@ import { V86 } from "./starter.js"; import { LOG_NAMES } from "../const.js"; -import { LOG_LEVEL, set_log_level } from "../config.js"; import { print_stats } from "./print_stats.js"; import { SyncFileBuffer } from "../buffer.js"; import { pad0, pads, hex_dump, dump_file, download, round_up_to_next_power_of_2 } from "../lib.js"; -import { log_data } from "../log.js"; +import { log_data, LOG_LEVEL, set_log_level } from "../log.js"; const ON_LOCALHOST = !location.hostname.endsWith("copy.sh"); diff --git a/src/browser/screen.js b/src/browser/screen.js index 3b963e67..5f1d3f80 100644 --- a/src/browser/screen.js +++ b/src/browser/screen.js @@ -1,8 +1,10 @@ "use strict"; -import { DEBUG_SCREEN_LAYERS } from "../config.js"; import { dbg_assert } from "../log.js"; +// Draws entire buffer and visualizes the layers that would be drawn +export const DEBUG_SCREEN_LAYERS = DEBUG && false; + /** * Adapter to use visual screen in browsers (in contrast to node) * @constructor diff --git a/src/browser/starter.js b/src/browser/starter.js index a785990f..f1fb4647 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -2,9 +2,8 @@ import { v86 } from "../main.js"; import { LOG_CPU, WASM_TABLE_OFFSET, WASM_TABLE_SIZE } from "../const.js"; -import { set_log_level } from "../config.js"; import { get_rand_int, load_file, read_sized_string_from_mem } from "../lib.js"; -import { dbg_assert, dbg_trace, dbg_log } from "../log.js"; +import { dbg_assert, dbg_trace, dbg_log, set_log_level } from "../log.js"; import { print_stats } from "./print_stats.js"; import { Bus } from "../bus.js"; import { BOOT_ORDER_FD_FIRST, BOOT_ORDER_HD_FIRST, BOOT_ORDER_CD_FIRST } from "../rtc.js"; diff --git a/src/config.js b/src/config.js deleted file mode 100644 index 9b98bf52..00000000 --- a/src/config.js +++ /dev/null @@ -1,50 +0,0 @@ -"use strict"; -/* - * Compile time configuration, some only relevant for debug mode - */ - -import { - LOG_ALL, LOG_PS2, LOG_PIT, LOG_9P, LOG_PIC, LOG_DMA, LOG_NET, LOG_FLOPPY, LOG_DISK, - LOG_SERIAL, LOG_VGA, LOG_SB16, LOG_VIRTIO -} from "./const.js"; - -/** @const */ -export var LOG_TO_FILE = false; - -/** - * @const - * Enables logging all IO port reads and writes. Very verbose - */ -export var LOG_ALL_IO = false; - -/** - * @const - */ -export var DUMP_GENERATED_WASM = false; - -/** - * @const - */ -export var DUMP_UNCOMPILED_ASSEMBLY = false; - -export var LOG_LEVEL = LOG_ALL & ~LOG_PS2 & ~LOG_PIT & ~LOG_VIRTIO & ~LOG_9P & ~LOG_PIC & - ~LOG_DMA & ~LOG_SERIAL & ~LOG_NET & ~LOG_FLOPPY & ~LOG_DISK & ~LOG_VGA & ~LOG_SB16; - -export function set_log_level(level) { - LOG_LEVEL = level; -} - -/** - * @const - * Draws entire buffer and visualizes the layers that would be drawn - */ -export var DEBUG_SCREEN_LAYERS = DEBUG && false; - -/** - * @const - * How many ticks the TSC does per millisecond - */ -export var TSC_RATE = 1 * 1000 * 1000; - -/** @const */ -export var APIC_TIMER_FREQ = TSC_RATE; diff --git a/src/cpu.js b/src/cpu.js index e92f8604..5abb04db 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -13,7 +13,6 @@ import { FLAG_VM, FLAG_INTERRUPT, FLAG_CARRY, FLAG_ADJUST, FLAG_ZERO, FLAG_SIGN, FLAG_TRAP, FLAG_DIRECTION, FLAG_OVERFLOW, FLAG_PARITY, } from "./const.js"; -import { DUMP_GENERATED_WASM, DUMP_UNCOMPILED_ASSEMBLY } from "./config.js"; import { h, view, pads, Bitmap } from "./lib.js"; import { dbg_assert, dbg_log } from "./log.js"; @@ -61,6 +60,8 @@ import { BusConnector } from "./bus.js"; // https://www-ssl.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html // http://ref.x86asm.net/geek32.html +const DUMP_GENERATED_WASM = false; +const DUMP_UNCOMPILED_ASSEMBLY = false; /** @constructor */ export function CPU(bus, wm, stop_idling) diff --git a/src/elf.js b/src/elf.js index 50d561aa..95a0d9a1 100644 --- a/src/elf.js +++ b/src/elf.js @@ -1,7 +1,6 @@ "use strict"; -import { LOG_LEVEL } from "./config.js"; -import { dbg_log } from "./log.js"; +import { dbg_log, LOG_LEVEL } from "./log.js"; // A minimal elf parser for loading 32 bit, x86, little endian, executable elf files diff --git a/src/io.js b/src/io.js index 923aa164..c5900f52 100644 --- a/src/io.js +++ b/src/io.js @@ -1,13 +1,15 @@ "use strict"; import { LOG_IO, MMAP_BLOCK_BITS, MMAP_BLOCK_SIZE, MMAP_MAX } from "./const.js"; -import { LOG_ALL_IO } from "./config.js"; import { h } from "./lib.js"; import { dbg_assert, dbg_log } from "./log.js"; // For Types Only import { CPU } from "./cpu.js"; +// Enables logging all IO port reads and writes. Very verbose +export const LOG_ALL_IO = false; + /** * The ISA IO bus * Devices register their ports here diff --git a/src/log.js b/src/log.js index d170266a..28ff9ea8 100644 --- a/src/log.js +++ b/src/log.js @@ -1,8 +1,21 @@ "use strict"; import { LOG_NAMES } from "./const.js"; -import { LOG_TO_FILE, LOG_LEVEL } from "./config.js"; import { pad0, pads } from "./lib.js"; +import { + LOG_ALL, LOG_PS2, LOG_PIT, LOG_9P, LOG_PIC, LOG_DMA, LOG_NET, LOG_FLOPPY, LOG_DISK, + LOG_SERIAL, LOG_VGA, LOG_SB16, LOG_VIRTIO +} from "./const.js"; + +/** @const */ +export var LOG_TO_FILE = false; + +export var LOG_LEVEL = LOG_ALL & ~LOG_PS2 & ~LOG_PIT & ~LOG_VIRTIO & ~LOG_9P & ~LOG_PIC & + ~LOG_DMA & ~LOG_SERIAL & ~LOG_NET & ~LOG_FLOPPY & ~LOG_DISK & ~LOG_VGA & ~LOG_SB16; + +export function set_log_level(level) { + LOG_LEVEL = level; +} export var log_data = []; diff --git a/src/rust/cpu/cpu.rs b/src/rust/cpu/cpu.rs index f0bf8415..097002e0 100644 --- a/src/rust/cpu/cpu.rs +++ b/src/rust/cpu/cpu.rs @@ -270,6 +270,8 @@ pub const CHECK_TLB_INVARIANTS: bool = false; pub const DEBUG: bool = cfg!(debug_assertions); pub const LOOP_COUNTER: i32 = 100_003; + +// should probably be kept in sync with APIC_TIMER_FREQ in apic.js pub const TSC_RATE: f64 = 1_000_000.0; pub static mut cpuid_level: u32 = 0x16; diff --git a/tests/kvm-unit-tests/README.md b/tests/kvm-unit-tests/README.md index 7bb7b074..2df400ca 100644 --- a/tests/kvm-unit-tests/README.md +++ b/tests/kvm-unit-tests/README.md @@ -18,8 +18,7 @@ make -C ../../build/libv86.js Tests can also be run in browser by going to `?profile=test-$name` (for example, `?profile=test-realmode`). -Most tests require you to set `ENABLE_ACPI` to `true` in `src/config.js` -(currently not the default). +Most tests require you to set `acpi: true` in the v86 constructor. # Welcome to kvm-unit-tests From f02715d2eef2ea3853776b46aa085d6d11979cd4 Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 7 Apr 2025 13:44:01 +0700 Subject: [PATCH 028/301] move get_file_size into lib.js and make it async --- src/buffer.js | 68 +----------- src/lib.js | 295 ++++++++++++++++++++++++++++---------------------- 2 files changed, 168 insertions(+), 195 deletions(-) diff --git a/src/buffer.js b/src/buffer.js index bc2ca7e3..ec3585f3 100644 --- a/src/buffer.js +++ b/src/buffer.js @@ -1,7 +1,7 @@ "use strict"; import { CPU } from "./cpu.js"; -import { load_file } from "./lib.js"; +import { load_file, get_file_size } from "./lib.js"; import { dbg_assert, dbg_log } from "./log.js"; // The smallest size the emulated hardware can emit @@ -108,7 +108,7 @@ function AsyncXHRBuffer(filename, size, fixed_chunk_size) this.onprogress = undefined; } -AsyncXHRBuffer.prototype.load = function() +AsyncXHRBuffer.prototype.load = async function() { if(this.byteLength !== undefined) { @@ -116,21 +116,9 @@ AsyncXHRBuffer.prototype.load = function() return; } - // Determine the size using a request - - determine_size(this.filename, (error, size) => - { - if(error) - { - throw new Error("Cannot use: " + this.filename + ". " + error); - } - else - { - dbg_assert(size >= 0); - this.byteLength = size; - this.onload && this.onload(Object.create(null)); - } - }); + const size = await get_file_size(this.filename); + this.byteLength = size; + this.onload && this.onload(Object.create(null)); }; /** @@ -697,52 +685,6 @@ AsyncFileBuffer.prototype.get_as_file = function(name) return file; }; -var determine_size; -if(typeof XMLHttpRequest === "undefined") -{ - determine_size = function(path, cb) - { - import("node:" + "fs").then(fs => fs["stat"](path, (err, stats) => - { - if(err) - { - cb(err); - } - else - { - cb(null, stats.size); - } - })); - }; -} -else -{ - determine_size = function(url, cb) - { - load_file(url, { - done: (buffer, http) => - { - var header = http.getResponseHeader("Content-Range") || ""; - var match = header.match(/\/(\d+)\s*$/); - - if(match) - { - cb(null, +match[1]); - } - else - { - const error = "`Range: bytes=...` header not supported (Got `" + header + "`)"; - cb(error); - } - }, - headers: { - Range: "bytes=0-0", - "X-Accept-Encoding": "identity" - } - }); - }; -} - export function buffer_from_object(obj, zstd_decompress_worker) { // TODO: accept Uint8Array, ArrayBuffer, File, url rather than { url } diff --git a/src/lib.js b/src/lib.js index 441390ef..7f12189f 100644 --- a/src/lib.js +++ b/src/lib.js @@ -532,175 +532,206 @@ Bitmap.prototype.get_buffer = function() return this.view.buffer; }; - export var load_file; +export var get_file_size; + if(typeof XMLHttpRequest === "undefined") { - load_file = load_file_nodejs; + let fs; + + /** + * @param {string} filename + * @param {Object} options + * @param {number=} n_tries + */ + load_file = async function(filename, options, n_tries) + { + if(!fs) + { + // string concat to work around closure compiler 'Invalid module path "node:fs/promises" for resolution mode' + fs = await import("node:" + "fs/promises"); + } + + if(options.range) + { + dbg_assert(!options.as_json); + + const fd = await fs["open"](filename, "r"); + + const length = options.range.length; + const buffer = Buffer.allocUnsafe(length); + + try + { + /** @type {{ bytesRead: Number }} */ + const result = await fd["read"]({ + buffer, + position: options.range.start + }); + dbg_assert(result.bytesRead === length); + } + finally + { + await fd["close"](); + } + + options.done && options.done(new Uint8Array(buffer)); + } + else + { + const o = { + encoding: options.as_json ? "utf-8" : null, + }; + + const data = await fs["readFile"](filename, o); + const result = options.as_json ? JSON.parse(data) : new Uint8Array(data).buffer; + + options.done(result); + } + }; + + get_file_size = async function(path) + { + if(!fs) + { + // string concat to work around closure compiler 'Invalid module path "node:fs/promises" for resolution mode' + fs = await import("node:" + "fs/promises"); + } + const stat = await fs["stat"](path); + return stat.size; + }; } else { - load_file = _load_file; -} - -/** - * @param {string} filename - * @param {Object} options - * @param {number=} n_tries - */ -function _load_file(filename, options, n_tries) -{ - var http = new XMLHttpRequest(); - - http.open(options.method || "get", filename, true); - - if(options.as_json) + /** + * @param {string} filename + * @param {Object} options + * @param {number=} n_tries + */ + load_file = async function(filename, options, n_tries) { - http.responseType = "json"; - } - else - { - http.responseType = "arraybuffer"; - } + var http = new XMLHttpRequest(); - if(options.headers) - { - var header_names = Object.keys(options.headers); + http.open(options.method || "get", filename, true); - for(var i = 0; i < header_names.length; i++) + if(options.as_json) { - var name = header_names[i]; - http.setRequestHeader(name, options.headers[name]); + http.responseType = "json"; } - } - - if(options.range) - { - const start = options.range.start; - const end = start + options.range.length - 1; - http.setRequestHeader("Range", "bytes=" + start + "-" + end); - http.setRequestHeader("X-Accept-Encoding", "identity"); - - // Abort if server responds with complete file in response to range - // request, to prevent downloading large files from broken http servers - http.onreadystatechange = function() + else { - if(http.status === 200) + http.responseType = "arraybuffer"; + } + + if(options.headers) + { + var header_names = Object.keys(options.headers); + + for(var i = 0; i < header_names.length; i++) { - console.error("Server sent full file in response to ranged request, aborting", { filename }); - http.abort(); + var name = header_names[i]; + http.setRequestHeader(name, options.headers[name]); } - }; - } + } - http.onload = function(e) - { - if(http.readyState === 4) + if(options.range) { - if(http.status !== 200 && http.status !== 206) + const start = options.range.start; + const end = start + options.range.length - 1; + http.setRequestHeader("Range", "bytes=" + start + "-" + end); + http.setRequestHeader("X-Accept-Encoding", "identity"); + + // Abort if server responds with complete file in response to range + // request, to prevent downloading large files from broken http servers + http.onreadystatechange = function() { - console.error("Loading the image " + filename + " failed (status %d)", http.status); - if(http.status >= 500 && http.status < 600) + if(http.status === 200) { - retry(); + console.error("Server sent full file in response to ranged request, aborting", { filename }); + http.abort(); } - } - else if(http.response) + }; + } + + http.onload = function(e) + { + if(http.readyState === 4) { - if(options.range) + if(http.status !== 200 && http.status !== 206) { - const enc = http.getResponseHeader("Content-Encoding"); - if(enc && enc !== "identity") + console.error("Loading the image " + filename + " failed (status %d)", http.status); + if(http.status >= 500 && http.status < 600) { - console.error("Server sent Content-Encoding in response to ranged request", {filename, enc}); + retry(); } } - options.done && options.done(http.response, http); + else if(http.response) + { + if(options.range) + { + const enc = http.getResponseHeader("Content-Encoding"); + if(enc && enc !== "identity") + { + console.error("Server sent Content-Encoding in response to ranged request", {filename, enc}); + } + } + options.done && options.done(http.response, http); + } } + }; + + http.onerror = function(e) + { + console.error("Loading the image " + filename + " failed", e); + retry(); + }; + + if(options.progress) + { + http.onprogress = function(e) + { + options.progress(e); + }; + } + + http.send(null); + + function retry() + { + const number_of_tries = n_tries || 0; + const timeout = [1, 1, 2, 3, 5, 8, 13, 21][number_of_tries] || 34; + setTimeout(() => { + load_file(filename, options, number_of_tries + 1); + }, 1000 * timeout); } }; - http.onerror = function(e) + get_file_size = async function(url) { - console.error("Loading the image " + filename + " failed", e); - retry(); - }; - - if(options.progress) - { - http.onprogress = function(e) - { - options.progress(e); - }; - } - - http.send(null); - - function retry() - { - const number_of_tries = n_tries || 0; - const timeout = [1, 1, 2, 3, 5, 8, 13, 21][number_of_tries] || 34; - setTimeout(() => { - load_file(filename, options, number_of_tries + 1); - }, 1000 * timeout); - } -} - -function load_file_nodejs(filename, options) -{ - if(options.range) - { - dbg_assert(!options.as_json); - - import("node:" + "fs").then(fs => fs["open"](filename, "r", (err, fd) => - { - if(err) throw err; - - const length = options.range.length; - var buffer = Buffer.allocUnsafe(length); - - fs["read"](fd, buffer, 0, length, options.range.start, (err, bytes_read) => - { - if(err) throw err; - - dbg_assert(bytes_read === length); - options.done && options.done(new Uint8Array(buffer)); - - fs["close"](fd, (err) => { - if(err) throw err; - }); - }); - })); - } - else - { - var o = { - encoding: options.as_json ? "utf-8" : null, - }; - - import("node:" + "fs").then(fs => fs["readFile"](filename, o, function(err, data) - { - if(err) + return new Promise((resolve, reject) => { + load_file(url, { + done: (buffer, http) => { - console.log("Could not read file:", filename, err); - } - else - { - var result = data; + var header = http.getResponseHeader("Content-Range") || ""; + var match = header.match(/\/(\d+)\s*$/); - if(options.as_json) + if(match) { - result = JSON.parse(result); + resolve(+match[1]); } else { - result = new Uint8Array(result).buffer; + const error = new Error("`Range: bytes=...` header not supported (Got `" + header + "`)"); + reject(error); } - - options.done(result); + }, + headers: { + Range: "bytes=0-0", + "X-Accept-Encoding": "identity" } - })); - } + }); + }); + }; } // Reads len characters at offset from Memory object mem as a JS string From 16d961e8623ca412914adbb7d088d1df0fc5eab1 Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 7 Apr 2025 13:46:16 +0700 Subject: [PATCH 029/301] in electron, prefer the fs module over XMLHttpRequest (see #1262) --- src/lib.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.js b/src/lib.js index 7f12189f..f5916047 100644 --- a/src/lib.js +++ b/src/lib.js @@ -535,7 +535,8 @@ Bitmap.prototype.get_buffer = function() export var load_file; export var get_file_size; -if(typeof XMLHttpRequest === "undefined") +if(typeof XMLHttpRequest === "undefined" || + typeof process !== "undefined" && process.versions && process.versions.node) { let fs; From 862ea200b90e83665ac1c37bae8c04910c4b45d5 Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 7 Apr 2025 18:13:57 +0700 Subject: [PATCH 030/301] move DEBUG magic into log.js, remove nodejs-loader.js (use src/main.js instead) --- debug.html | 3 --- nodejs-loader.mjs | 5 ----- src/cjs.js | 2 +- src/log.js | 5 +++++ src/main.js | 1 + tests/api/reset.js | 3 +-- 6 files changed, 8 insertions(+), 11 deletions(-) delete mode 100644 nodejs-loader.mjs diff --git a/debug.html b/debug.html index e99fc7da..36c79c83 100644 --- a/debug.html +++ b/debug.html @@ -4,9 +4,6 @@ v86 (debug) - diff --git a/nodejs-loader.mjs b/nodejs-loader.mjs deleted file mode 100644 index 8e05d6e5..00000000 --- a/nodejs-loader.mjs +++ /dev/null @@ -1,5 +0,0 @@ - -globalThis.DEBUG = true; -var V86 = await import("./src/browser/starter.js"); -export default V86.V86; - diff --git a/src/cjs.js b/src/cjs.js index 845072c4..9750546b 100644 --- a/src/cjs.js +++ b/src/cjs.js @@ -24,4 +24,4 @@ goog.exportProperty = function() {}; * @define {boolean} * Overridden for production by closure compiler */ - var DEBUG = true; +var DEBUG = true; diff --git a/src/log.js b/src/log.js index 28ff9ea8..6464d55a 100644 --- a/src/log.js +++ b/src/log.js @@ -1,5 +1,10 @@ "use strict"; +if(typeof DEBUG === "undefined") +{ + globalThis.DEBUG = true; +} + import { LOG_NAMES } from "./const.js"; import { pad0, pads } from "./lib.js"; import { diff --git a/src/main.js b/src/main.js index 51748ca4..8c476a61 100644 --- a/src/main.js +++ b/src/main.js @@ -2,6 +2,7 @@ import { CPU } from "./cpu.js"; import { save_state, restore_state } from "./state.js"; +export { V86 } from "./browser/starter.js"; /** * @constructor diff --git a/tests/api/reset.js b/tests/api/reset.js index 651c7491..b19d30b7 100755 --- a/tests/api/reset.js +++ b/tests/api/reset.js @@ -8,8 +8,7 @@ const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); // This test checks that reset works const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; - -var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); process.on("unhandledRejection", exn => { throw exn; }); From b44f6ea83ba3a60547d71820080c3e4cfd53be4e Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 7 Apr 2025 18:59:18 +0700 Subject: [PATCH 031/301] arch profile uses virtio-net now --- tests/benchmark/arch-bytemark.js | 5 ++++- tests/benchmark/arch-python.js | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/benchmark/arch-bytemark.js b/tests/benchmark/arch-bytemark.js index 51d815ad..dc3c9ebd 100755 --- a/tests/benchmark/arch-bytemark.js +++ b/tests/benchmark/arch-bytemark.js @@ -17,7 +17,10 @@ const emulator = new V86({ autostart: true, memory_size: 512 * 1024 * 1024, vga_memory_size: 8 * 1024 * 1024, - network_relay_url: "", + net_device: { + type: "virtio", + relay_url: "", + }, initial_state: { url: path.join(V86_ROOT, "/images/arch_state.bin") }, filesystem: { baseurl: path.join(V86_ROOT, "/images/arch/") }, disable_jit: +process.env.DISABLE_JIT, diff --git a/tests/benchmark/arch-python.js b/tests/benchmark/arch-python.js index e587660a..b1caff64 100755 --- a/tests/benchmark/arch-python.js +++ b/tests/benchmark/arch-python.js @@ -17,7 +17,10 @@ const emulator = new V86({ autostart: true, memory_size: 512 * 1024 * 1024, vga_memory_size: 8 * 1024 * 1024, - network_relay_url: "", + net_device: { + type: "virtio", + relay_url: "", + }, initial_state: { url: path.join(V86_ROOT, "/images/arch_state.bin") }, filesystem: { baseurl: path.join(V86_ROOT, "/images/arch/") }, disable_jit: +process.env.DISABLE_JIT, From 2a4fe44a7d8fa868027c8c23671481220dd6a35f Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 7 Apr 2025 19:29:47 +0700 Subject: [PATCH 032/301] liberate tests from libv86-debug.js --- Makefile | 18 +++++++++--------- src/main.js | 1 + tests/Readme.md | 2 +- tests/api/clean-shutdown.js | 3 +-- tests/api/floppy-insert-eject.js | 9 ++++----- tests/api/pic.js | 7 ++++--- tests/api/reboot.js | 3 +-- tests/api/serial.js | 3 +-- tests/api/state.js | 3 +-- tests/api/test.js | 4 ++-- tests/benchmark/arch-bytemark.js | 3 +-- tests/benchmark/arch-python.js | 4 ++-- tests/benchmark/linux-boot.js | 2 +- tests/benchmark/snapshot.js | 3 +-- tests/devices/fetch_network.js | 5 +++-- tests/devices/virtio_9p.js | 4 ++-- tests/devices/virtio_balloon.js | 3 +-- tests/devices/virtio_console.js | 5 ++--- tests/devices/wisp_network.js | 3 +-- tests/expect/run.js | 15 +++------------ tests/full/run.js | 13 +++---------- tests/jit-paging/run.js | 5 ++--- tests/kvm-unit-tests/run.mjs | 3 +-- tests/nasm/run.js | 14 +++----------- tests/qemu/run.js | 5 ++--- 25 files changed, 53 insertions(+), 87 deletions(-) diff --git a/Makefile b/Makefile index 7d76c0c1..2718c47a 100644 --- a/Makefile +++ b/Makefile @@ -297,27 +297,27 @@ build/integration-test-fs/fs.json: images/buildroot-bzimage68.bin ./tools/copy-to-sha256.py build/integration-test-fs/fs.tar build/integration-test-fs/flat rm build/integration-test-fs/fs.tar build/integration-test-fs/bzImage build/integration-test-fs/initrd -tests: build/libv86-debug.js build/v86-debug.wasm build/integration-test-fs/fs.json +tests: build/v86-debug.wasm build/integration-test-fs/fs.json LOG_LEVEL=3 ./tests/full/run.js tests-release: build/libv86.js build/v86.wasm build/integration-test-fs/fs.json TEST_RELEASE_BUILD=1 ./tests/full/run.js -nasmtests: build/libv86-debug.mjs build/v86-debug.wasm +nasmtests: build/v86-debug.wasm $(NASM_TEST_DIR)/create_tests.js $(NASM_TEST_DIR)/gen_fixtures.js $(NASM_TEST_DIR)/run.js -nasmtests-force-jit: build/libv86-debug.mjs build/v86-debug.wasm +nasmtests-force-jit: build/v86-debug.wasm $(NASM_TEST_DIR)/create_tests.js $(NASM_TEST_DIR)/gen_fixtures.js $(NASM_TEST_DIR)/run.js --force-jit -jitpagingtests: build/libv86-debug.mjs build/v86-debug.wasm +jitpagingtests: build/v86-debug.wasm $(MAKE) -C tests/jit-paging test-jit ./tests/jit-paging/run.js -qemutests: build/libv86-debug.mjs build/v86-debug.wasm +qemutests: build/v86-debug.wasm $(MAKE) -C tests/qemu test-i386 LOG_LEVEL=3 ./tests/qemu/run.js build/qemu-test-result ./tests/qemu/run-qemu.js > build/qemu-test-reference @@ -329,7 +329,7 @@ qemutests-release: build/libv86.mjs build/v86.wasm ./tests/qemu/run-qemu.js > build/qemu-test-reference diff build/qemu-test-result build/qemu-test-reference -kvm-unit-test: build/libv86-debug.mjs build/v86-debug.wasm +kvm-unit-test: build/v86-debug.wasm (cd tests/kvm-unit-tests && ./configure && make x86/realmode.flat) tests/kvm-unit-tests/run.mjs tests/kvm-unit-tests/x86/realmode.flat @@ -337,11 +337,11 @@ kvm-unit-test-release: build/libv86.mjs build/v86.wasm (cd tests/kvm-unit-tests && ./configure && make x86/realmode.flat) TEST_RELEASE_BUILD=1 tests/kvm-unit-tests/run.mjs tests/kvm-unit-tests/x86/realmode.flat -expect-tests: build/libv86-debug.mjs build/v86-debug.wasm build/libwabt.cjs +expect-tests: build/v86-debug.wasm build/libwabt.cjs make -C tests/expect/tests ./tests/expect/run.js -devices-test: build/libv86-debug.mjs build/v86-debug.wasm +devices-test: build/v86-debug.wasm ./tests/devices/virtio_9p.js ./tests/devices/virtio_console.js ./tests/devices/fetch_network.js @@ -356,7 +356,7 @@ rust-test: $(RUST_FILES) rust-test-intensive: QUICKCHECK_TESTS=100000000 make rust-test -api-tests: build/libv86-debug.mjs build/v86-debug.wasm +api-tests: build/v86-debug.wasm ./tests/api/clean-shutdown.js ./tests/api/state.js ./tests/api/reset.js diff --git a/src/main.js b/src/main.js index 8c476a61..ed1ba783 100644 --- a/src/main.js +++ b/src/main.js @@ -3,6 +3,7 @@ import { CPU } from "./cpu.js"; import { save_state, restore_state } from "./state.js"; export { V86 } from "./browser/starter.js"; +export { print_stats } from "./browser/print_stats.js"; /** * @constructor diff --git a/tests/Readme.md b/tests/Readme.md index 804380d3..deb063ec 100644 --- a/tests/Readme.md +++ b/tests/Readme.md @@ -19,7 +19,7 @@ following list is roughtly sorted from most interesting/useful to least. The following environmental variables are respected by most tests if applicable: - `TEST_RELEASE_BUILD=1`: Test the release build (libv86.js, v86.wasm) instead of the - debug build (libv86-debug.js, v86-debug.wasm) + debug build (source files with v86-debug.wasm) - `MAX_PARALLEL_TESTS=n`: Maximum number of tests to run in parallel. Defaults to the number of cores in your system or less. - `TEST_NAME="…"`: Run only the specified test (only expect, full, nasm) diff --git a/tests/api/clean-shutdown.js b/tests/api/clean-shutdown.js index a9b080f2..a9adbf52 100755 --- a/tests/api/clean-shutdown.js +++ b/tests/api/clean-shutdown.js @@ -8,8 +8,7 @@ const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); // listeners, so that the nodejs process cleanly and automatically exits. const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; - -var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); process.on("unhandledRejection", exn => { throw exn; }); diff --git a/tests/api/floppy-insert-eject.js b/tests/api/floppy-insert-eject.js index 176609be..13792408 100755 --- a/tests/api/floppy-insert-eject.js +++ b/tests/api/floppy-insert-eject.js @@ -1,14 +1,13 @@ #!/usr/bin/env node "use strict"; -import {setTimeout as pause} from "timers/promises"; + +import { setTimeout as pause } from "timers/promises"; import url from "node:url"; - const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; - - -var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); process.on("unhandledRejection", exn => { throw exn; }); diff --git a/tests/api/pic.js b/tests/api/pic.js index 69131158..f6227f45 100755 --- a/tests/api/pic.js +++ b/tests/api/pic.js @@ -1,12 +1,13 @@ #!/usr/bin/env node "use strict"; + import url from "node:url"; +import fs from "node:fs"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); -const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; -import fs from "node:fs"; -const { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); +const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); const root_path = __dirname + "/../.."; diff --git a/tests/api/reboot.js b/tests/api/reboot.js index 2fc33f07..89fe8afc 100755 --- a/tests/api/reboot.js +++ b/tests/api/reboot.js @@ -6,8 +6,7 @@ import url from "node:url"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; - -var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); process.on("unhandledRejection", exn => { throw exn; }); diff --git a/tests/api/serial.js b/tests/api/serial.js index 63728201..2107cf0e 100755 --- a/tests/api/serial.js +++ b/tests/api/serial.js @@ -8,8 +8,7 @@ import crypto from "node:crypto"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; - -var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); process.on("unhandledRejection", exn => { throw exn; }); diff --git a/tests/api/state.js b/tests/api/state.js index a6b992bb..ea33b9a1 100755 --- a/tests/api/state.js +++ b/tests/api/state.js @@ -8,8 +8,7 @@ const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); process.on("unhandledRejection", exn => { throw exn; }); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; - -var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); const config_async_cdrom = { bios: { url: __dirname + "/../../bios/seabios.bin" }, diff --git a/tests/api/test.js b/tests/api/test.js index b354ac5b..0d193a0a 100755 --- a/tests/api/test.js +++ b/tests/api/test.js @@ -4,9 +4,9 @@ import url from "node:url"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); -const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; -const { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); +const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); process.on("unhandledRejection", exn => { throw exn; }); diff --git a/tests/benchmark/arch-bytemark.js b/tests/benchmark/arch-bytemark.js index dc3c9ebd..6eb6c82c 100755 --- a/tests/benchmark/arch-bytemark.js +++ b/tests/benchmark/arch-bytemark.js @@ -6,8 +6,7 @@ import url from "node:url"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const BENCH_COLLECT_STATS = +process.env.BENCH_COLLECT_STATS; - -const { V86, print_stats } = await import(`../../build/${BENCH_COLLECT_STATS ? "libv86-debug" : "libv86"}.js`); +const { V86, print_stats } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); const V86_ROOT = path.join(__dirname, "../.."); diff --git a/tests/benchmark/arch-python.js b/tests/benchmark/arch-python.js index b1caff64..c2d87399 100755 --- a/tests/benchmark/arch-python.js +++ b/tests/benchmark/arch-python.js @@ -5,9 +5,9 @@ import path from "node:path"; import url from "node:url"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); -const BENCH_COLLECT_STATS = +process.env.BENCH_COLLECT_STATS; -const { V86, print_stats } = await import(`../../build/${BENCH_COLLECT_STATS ? "libv86-debug" : "libv86"}.js`); +const BENCH_COLLECT_STATS = +process.env.BENCH_COLLECT_STATS; +const { V86, print_stats } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); const V86_ROOT = path.join(__dirname, "../.."); diff --git a/tests/benchmark/linux-boot.js b/tests/benchmark/linux-boot.js index 883f2dc2..ed3c88fe 100755 --- a/tests/benchmark/linux-boot.js +++ b/tests/benchmark/linux-boot.js @@ -8,7 +8,7 @@ import url from "node:url"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const BENCH_COLLECT_STATS = +process.env.BENCH_COLLECT_STATS; -const { V86, print_stats } = await import(`../../build/${BENCH_COLLECT_STATS ? "libv86-debug" : "libv86"}.js`).V86; +const { V86, print_stats } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); const V86_ROOT = path.join(__dirname, "../.."); diff --git a/tests/benchmark/snapshot.js b/tests/benchmark/snapshot.js index db53b814..c7d437d1 100755 --- a/tests/benchmark/snapshot.js +++ b/tests/benchmark/snapshot.js @@ -5,8 +5,7 @@ import path from "node:path"; import url from "node:url"; const BENCH_COLLECT_STATS = +process.env.BENCH_COLLECT_STATS; - -let { V86, print_stats } = await import(`../../build/${BENCH_COLLECT_STATS ? "libv86-debug" : "libv86"}.js`); +const { V86, print_stats } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const V86_ROOT = path.join(__dirname, "../.."); diff --git a/tests/devices/fetch_network.js b/tests/devices/fetch_network.js index b62b1f37..9b70e1e2 100755 --- a/tests/devices/fetch_network.js +++ b/tests/devices/fetch_network.js @@ -7,10 +7,11 @@ import url from "node:url"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); process.on("unhandledRejection", exn => { throw exn; }); -const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; const USE_VIRTIO = !!process.env.USE_VIRTIO; -const { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); +const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); + const SHOW_LOGS = false; function wait(time) { diff --git a/tests/devices/virtio_9p.js b/tests/devices/virtio_9p.js index 1dba5edf..73188968 100755 --- a/tests/devices/virtio_9p.js +++ b/tests/devices/virtio_9p.js @@ -5,10 +5,10 @@ import fs from "node:fs"; process.on("unhandledRejection", exn => { throw exn; }); -const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); -var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); +const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); const testfsjson = JSON.parse(fs.readFileSync(__dirname + "/testfs.json", "utf-8")); const SHOW_LOGS = false; diff --git a/tests/devices/virtio_balloon.js b/tests/devices/virtio_balloon.js index 89474aa0..b5f34711 100755 --- a/tests/devices/virtio_balloon.js +++ b/tests/devices/virtio_balloon.js @@ -7,8 +7,7 @@ const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); process.on("unhandledRejection", exn => { throw exn; }); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; - -const { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); const SHOW_LOGS = false; diff --git a/tests/devices/virtio_console.js b/tests/devices/virtio_console.js index c7efefd4..4ed81ab9 100755 --- a/tests/devices/virtio_console.js +++ b/tests/devices/virtio_console.js @@ -7,11 +7,10 @@ import url from "node:url"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); process.on("unhandledRejection", exn => { throw exn; }); -const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; const SHOW_LOGS = false; -var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); - +const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); const emulator = new V86({ bios: { url: __dirname + "/../../bios/seabios.bin" }, diff --git a/tests/devices/wisp_network.js b/tests/devices/wisp_network.js index dadd5836..f70fb6dc 100755 --- a/tests/devices/wisp_network.js +++ b/tests/devices/wisp_network.js @@ -8,8 +8,7 @@ const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); process.on("unhandledRejection", exn => { throw exn; }); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; - -const { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); const SHOW_LOGS = false; diff --git a/tests/expect/run.js b/tests/expect/run.js index 0bf78c04..cea8cd34 100755 --- a/tests/expect/run.js +++ b/tests/expect/run.js @@ -6,23 +6,14 @@ import path from "node:path"; import assert from "node:assert/strict"; import url from "node:url"; import { spawnSync } from "node:child_process"; -import wabtfactory from "../../build/libwabt.cjs"; +import wabt from "../../build/libwabt.cjs"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); -const libwabt = wabtfactory(); - -try { - var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); -} -catch(e) { - console.error(e); - console.error("Failed to import build/libv86-debug.js. Run " + - "`make build/libv86-debug.js` first."); - process.exit(1); -} +const libwabt = wabt(); const TEST_NAME = process.env.TEST_NAME; diff --git a/tests/full/run.js b/tests/full/run.js index cdc92e94..31f9a955 100755 --- a/tests/full/run.js +++ b/tests/full/run.js @@ -7,6 +7,9 @@ import os from "node:os"; import fs from "node:fs"; import url from "node:url"; +const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); + const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); process.on("unhandledRejection", exn => { throw exn; }); @@ -14,21 +17,11 @@ process.on("unhandledRejection", exn => { throw exn; }); var TIMEOUT_EXTRA_FACTOR = +process.env.TIMEOUT_EXTRA_FACTOR || 1; var MAX_PARALLEL_TESTS = +process.env.MAX_PARALLEL_TESTS || 4; var TEST_NAME = process.env.TEST_NAME; -const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; const RUN_SLOW_TESTS = +process.env.RUN_SLOW_TESTS; const VERBOSE = false; const LOG_SCREEN = false; -try -{ - var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); -} -catch(e) -{ - console.error("Failed to import build/libv86-debug.js. Run `make build/libv86-debug.js first."); - process.exit(1); -} var root_path = __dirname + "/../.."; diff --git a/tests/jit-paging/run.js b/tests/jit-paging/run.js index fc4396a7..aa9e50b7 100755 --- a/tests/jit-paging/run.js +++ b/tests/jit-paging/run.js @@ -6,10 +6,9 @@ import url from "node:url"; process.on("unhandledRejection", exn => { throw exn; }); const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; - -var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); - +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); var test_executable = new Uint8Array(fs.readFileSync(__dirname + "/test-jit")); diff --git a/tests/kvm-unit-tests/run.mjs b/tests/kvm-unit-tests/run.mjs index f8f64d46..29153af0 100755 --- a/tests/kvm-unit-tests/run.mjs +++ b/tests/kvm-unit-tests/run.mjs @@ -10,8 +10,7 @@ const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); process.on("unhandledRejection", exn => { throw exn; }); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; - -var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); function readfile(path) { diff --git a/tests/nasm/run.js b/tests/nasm/run.js index 8926a824..9dcf72ab 100755 --- a/tests/nasm/run.js +++ b/tests/nasm/run.js @@ -8,6 +8,9 @@ import assert from "node:assert/strict"; import os from "node:os"; import cluster from "node:cluster"; +const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); + const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); process.on("unhandledRejection", exn => { throw exn; }); @@ -27,7 +30,6 @@ process.on("unhandledRejection", exn => { throw exn; }); const MAX_PARALLEL_TESTS = +process.env.MAX_PARALLEL_TESTS || 99; const TEST_NAME = new RegExp(process.env.TEST_NAME || "", "i"); const SINGLE_TEST_TIMEOUT = 10000; -const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; const TEST_DIR = __dirname + "/build/"; const DONE_MSG = "DONE"; @@ -50,16 +52,6 @@ const FPU_TAG_ALL_INVALID = 0xAAAA; const FPU_STATUS_MASK = 0xFFFF & ~(1 << 9 | 1 << 5 | 1 << 3 | 1 << 1); // bits that are not correctly implemented by v86 const FP_COMPARISON_SIGNIFICANT_DIGITS = 7; -try { - var { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); -} -catch(e) { - console.error(e); - console.error("Failed to import build/libv86-debug.js. Run " + - "`make build/libv86-debug.js` first."); - process.exit(1); -} - function float_equal(x, y) { assert(typeof x === "number"); diff --git a/tests/qemu/run.js b/tests/qemu/run.js index 877d4061..13138f01 100755 --- a/tests/qemu/run.js +++ b/tests/qemu/run.js @@ -3,14 +3,13 @@ process.on("unhandledRejection", exn => { throw exn; }); -const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; - import { fileURLToPath } from "url"; import path from "path"; import fs from "fs"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); +const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); var test_executable = new Uint8Array(fs.readFileSync(__dirname + "/test-i386")); From 2d69353416b49121a2a834140e02ef41fb3ada21 Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 7 Apr 2025 19:30:35 +0700 Subject: [PATCH 033/301] remove @export annotations (exports are explicit now) --- src/browser/fetch_network.js | 1 - src/browser/print_stats.js | 3 --- src/browser/starter.js | 26 -------------------------- src/cjs.js | 20 -------------------- 4 files changed, 50 deletions(-) diff --git a/src/browser/fetch_network.js b/src/browser/fetch_network.js index 250d144b..7be99b2c 100644 --- a/src/browser/fetch_network.js +++ b/src/browser/fetch_network.js @@ -21,7 +21,6 @@ import { BusConnector } from "../bus.js"; * * @param {BusConnector} bus * @param {*=} config - * @export */ export function FetchNetworkAdapter(bus, config) { diff --git a/src/browser/print_stats.js b/src/browser/print_stats.js index 033cbe48..c2d84dd6 100644 --- a/src/browser/print_stats.js +++ b/src/browser/print_stats.js @@ -2,9 +2,6 @@ import { pads } from "../lib.js"; -/** - * @export - */ export const print_stats = { stats_to_string: function(cpu) { diff --git a/src/browser/starter.js b/src/browser/starter.js index f1fb4647..6be147df 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -130,7 +130,6 @@ import { EEXIST, ENOENT } from "../../lib/9p.js"; } | undefined), }} options * @constructor - * @export */ export function V86(options) { @@ -822,7 +821,6 @@ V86.prototype.get_bzimage_initrd_from_filesystem = function(filesystem) /** * Start emulation. Do nothing if emulator is running already. Can be * asynchronous. - * @export */ V86.prototype.run = async function() { @@ -831,7 +829,6 @@ V86.prototype.run = async function() /** * Stop emulation. Do nothing if emulator is not running. Can be asynchronous. - * @export */ V86.prototype.stop = async function() { @@ -852,7 +849,6 @@ V86.prototype.stop = async function() /** * @ignore - * @export */ V86.prototype.destroy = async function() { @@ -869,7 +865,6 @@ V86.prototype.destroy = async function() /** * Restart (force a reboot). - * @export */ V86.prototype.restart = function() { @@ -884,7 +879,6 @@ V86.prototype.restart = function() * * @param {string} event Name of the event. * @param {function(?)} listener The callback function. - * @export */ V86.prototype.add_listener = function(event, listener) { @@ -896,7 +890,6 @@ V86.prototype.add_listener = function(event, listener) * * @param {string} event * @param {function(*)} listener - * @export */ V86.prototype.remove_listener = function(event, listener) { @@ -916,7 +909,6 @@ V86.prototype.remove_listener = function(event, listener) * state buffer. * * @param {ArrayBuffer} state - * @export */ V86.prototype.restore_state = async function(state) { @@ -928,7 +920,6 @@ V86.prototype.restore_state = async function(state) * Asynchronously save the current state of the emulator. * * @return {Promise} - * @export */ V86.prototype.save_state = async function() { @@ -939,7 +930,6 @@ V86.prototype.save_state = async function() /** * @return {number} * @ignore - * @export */ V86.prototype.get_instruction_counter = function() { @@ -956,7 +946,6 @@ V86.prototype.get_instruction_counter = function() /** * @return {boolean} - * @export */ V86.prototype.is_running = function() { @@ -966,7 +955,6 @@ V86.prototype.is_running = function() /** * Set the image inserted in the floppy drive. Can be changed at runtime, as * when physically changing the floppy disk. - * @export */ V86.prototype.set_fda = async function(file) { @@ -992,7 +980,6 @@ V86.prototype.set_fda = async function(file) /** * Eject the floppy drive. - * @export */ V86.prototype.eject_fda = function() { @@ -1005,7 +992,6 @@ V86.prototype.eject_fda = function() * Do nothing if there is no keyboard controller. * * @param {Array.} codes - * @export */ V86.prototype.keyboard_send_scancodes = function(codes) { @@ -1018,7 +1004,6 @@ V86.prototype.keyboard_send_scancodes = function(codes) /** * Send translated keys * @ignore - * @export */ V86.prototype.keyboard_send_keys = function(codes) { @@ -1031,7 +1016,6 @@ V86.prototype.keyboard_send_keys = function(codes) /** * Send text * @ignore - * @export */ V86.prototype.keyboard_send_text = function(string) { @@ -1045,7 +1029,6 @@ V86.prototype.keyboard_send_text = function(string) * Download a screenshot. * * @ignore - * @export */ V86.prototype.screen_make_screenshot = function() { @@ -1063,7 +1046,6 @@ V86.prototype.screen_make_screenshot = function() * @param {number} sy * * @ignore - * @export */ V86.prototype.screen_set_scale = function(sx, sy) { @@ -1077,7 +1059,6 @@ V86.prototype.screen_set_scale = function(sx, sy) * Go fullscreen. * * @ignore - * @export */ V86.prototype.screen_go_fullscreen = function() { @@ -1121,7 +1102,6 @@ V86.prototype.screen_go_fullscreen = function() * browser window. * * @ignore - * @export */ V86.prototype.lock_mouse = function() { @@ -1154,7 +1134,6 @@ V86.prototype.mouse_set_status = function(enabled) * Enable or disable sending keyboard events to the emulated PS2 controller. * * @param {boolean} enabled - * @export */ V86.prototype.keyboard_set_status = function(enabled) { @@ -1169,7 +1148,6 @@ V86.prototype.keyboard_set_status = function(enabled) * Send a string to the first emulated serial terminal. * * @param {string} data - * @export */ V86.prototype.serial0_send = function(data) { @@ -1183,7 +1161,6 @@ V86.prototype.serial0_send = function(data) * Send bytes to a serial port (to be received by the emulated PC). * * @param {Uint8Array} data - * @export */ V86.prototype.serial_send_bytes = function(serial, data) { @@ -1238,7 +1215,6 @@ V86.prototype.serial_set_clear_to_send = function(serial, status) * @param {string} path Path for the mount point * @param {string|undefined} baseurl * @param {string|undefined} basefs As a JSON string - * @export */ V86.prototype.mount_fs = async function(path, baseurl, basefs) { @@ -1278,7 +1254,6 @@ V86.prototype.mount_fs = async function(path, baseurl, basefs) * * @param {string} file * @param {Uint8Array} data - * @export */ V86.prototype.create_file = async function(file, data) { @@ -1312,7 +1287,6 @@ V86.prototype.create_file = async function(file, data) * initialized. * * @param {string} file - * @export */ V86.prototype.read_file = async function(file) { diff --git a/src/cjs.js b/src/cjs.js index 9750546b..97a99c83 100644 --- a/src/cjs.js +++ b/src/cjs.js @@ -1,25 +1,5 @@ "use strict"; -/* global module, self */ - -var goog = goog || {}; -goog.exportSymbol = function(name, sym) { - if(typeof module !== "undefined" && typeof module.exports !== "undefined") - { - module.exports[name] = sym; - } - else if(typeof window !== "undefined") - { - window[name] = sym; - } - else if(typeof importScripts === "function") - { - // web worker - self[name] = sym; - } -}; -goog.exportProperty = function() {}; - /** * @define {boolean} * Overridden for production by closure compiler From 51bd604cc5726154e2e08f660c40fc394d39b551 Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 7 Apr 2025 19:39:08 +0700 Subject: [PATCH 034/301] remove "use strict" (is default in es6 modules) --- eslint.config.mjs | 3 ++- examples/nodejs.js | 2 +- examples/nodejs_state.js | 2 +- gen/generate_analyzer.js | 2 +- gen/generate_interpreter.js | 2 +- gen/generate_jit.js | 2 +- gen/rust_ast.js | 2 -- gen/util.js | 2 -- gen/x86_table.js | 2 -- lib/9p.js | 1 - lib/filesystem.js | 1 - lib/marshall.js | 1 - src/acpi.js | 2 -- src/apic.js | 2 -- src/browser/dummy_screen.js | 2 -- src/browser/fake_network.js | 2 -- src/browser/fetch_network.js | 2 -- src/browser/filestorage.js | 3 --- src/browser/inbrowser_network.js | 2 -- src/browser/keyboard.js | 2 -- src/browser/main.js | 2 -- src/browser/mouse.js | 3 --- src/browser/network.js | 2 -- src/browser/print_stats.js | 2 -- src/browser/screen.js | 2 -- src/browser/serial.js | 2 -- src/browser/speaker.js | 2 -- src/browser/starter.js | 2 -- src/browser/wisp_network.js | 2 -- src/browser/worker_bus.js | 2 -- src/buffer.js | 2 -- src/bus.js | 2 -- src/cjs.js | 2 -- src/const.js | 2 -- src/cpu.js | 2 -- src/dma.js | 2 -- src/elf.js | 2 -- src/externs.js | 2 -- src/floppy.js | 2 -- src/ide.js | 2 -- src/io.js | 2 -- src/ioapic.js | 2 -- src/kernel.js | 2 -- src/lib.js | 2 -- src/log.js | 2 -- src/main.js | 2 -- src/ne2k.js | 2 -- src/pci.js | 2 -- src/pit.js | 2 -- src/ps2.js | 2 -- src/rtc.js | 2 -- src/sb16.js | 2 -- src/state.js | 2 -- src/uart.js | 2 -- src/vga.js | 2 -- src/virtio.js | 2 -- src/virtio_balloon.js | 2 -- src/virtio_console.js | 2 -- src/virtio_net.js | 2 -- tests/api/clean-shutdown.js | 1 - tests/api/floppy-insert-eject.js | 1 - tests/api/pic.js | 1 - tests/api/reboot.js | 1 - tests/api/reset.js | 1 - tests/api/serial.js | 1 - tests/api/state.js | 1 - tests/api/test.js | 1 - tests/benchmark/arch-bytemark.js | 1 - tests/benchmark/arch-python.js | 1 - tests/benchmark/linux-boot.js | 1 - tests/benchmark/snapshot.js | 1 - tests/devices/fetch_network.js | 1 - tests/devices/virtio_9p.js | 2 +- tests/devices/virtio_balloon.js | 2 +- tests/devices/virtio_console.js | 2 +- tests/devices/wisp_network.js | 2 +- tests/expect/run.js | 1 - tests/full/run.js | 1 - tests/jit-paging/run.js | 1 - tests/kvm-unit-tests/run.mjs | 1 - tests/nasm/create_tests.js | 1 - tests/nasm/gen_fixtures.js | 1 - tests/nasm/rand.js | 2 -- tests/nasm/run.js | 1 - tests/qemu/run-qemu.js | 1 - tests/qemu/run.js | 5 ++--- tests/rust/verify-wasmgen-dummy-output.js | 1 - tools/docker/alpine/build-state.js | 1 - 88 files changed, 13 insertions(+), 143 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index d30ddd2c..a2691db3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -133,7 +133,8 @@ export default [ "no-with": "error", "require-yield": "error", "use-isnan": "error", - "valid-typeof": "error" + "valid-typeof": "error", + "strict": "error" } } ]; diff --git a/examples/nodejs.js b/examples/nodejs.js index 98b4fd8b..2a7d0fb3 100755 --- a/examples/nodejs.js +++ b/examples/nodejs.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -"use strict"; + import fs from "node:fs"; import url from "node:url"; diff --git a/examples/nodejs_state.js b/examples/nodejs_state.js index 63f5df25..834f3b5b 100755 --- a/examples/nodejs_state.js +++ b/examples/nodejs_state.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -"use strict"; + import fs from "node:fs"; import url from "node:url"; diff --git a/gen/generate_analyzer.js b/gen/generate_analyzer.js index a75cfe03..5fb95388 100755 --- a/gen/generate_analyzer.js +++ b/gen/generate_analyzer.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -"use strict"; + import assert from "node:assert/strict"; import fs from "node:fs"; diff --git a/gen/generate_interpreter.js b/gen/generate_interpreter.js index 54d68598..02a9d902 100755 --- a/gen/generate_interpreter.js +++ b/gen/generate_interpreter.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -"use strict"; + import assert from "node:assert/strict"; import fs from "node:fs"; diff --git a/gen/generate_jit.js b/gen/generate_jit.js index e9cdc357..33f15882 100755 --- a/gen/generate_jit.js +++ b/gen/generate_jit.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -"use strict"; + import assert from "node:assert/strict"; import fs from "node:fs"; diff --git a/gen/rust_ast.js b/gen/rust_ast.js index 7d50cd55..23177801 100644 --- a/gen/rust_ast.js +++ b/gen/rust_ast.js @@ -1,5 +1,3 @@ -"use strict"; - import assert from "node:assert/strict"; function indent(lines, how_much) diff --git a/gen/util.js b/gen/util.js index ed59ec99..c33ca963 100644 --- a/gen/util.js +++ b/gen/util.js @@ -1,5 +1,3 @@ -"use strict"; - import fs from "node:fs"; import path from "node:path"; import process from "node:process"; diff --git a/gen/x86_table.js b/gen/x86_table.js index a360cba9..bd6039cf 100644 --- a/gen/x86_table.js +++ b/gen/x86_table.js @@ -1,5 +1,3 @@ -"use strict"; - // http://ref.x86asm.net/coder32.html const zf = 1 << 6; diff --git a/lib/9p.js b/lib/9p.js index ada6dbe2..70f4f8c8 100644 --- a/lib/9p.js +++ b/lib/9p.js @@ -4,7 +4,6 @@ // Implementation of the 9p filesystem device following the // 9P2000.L protocol ( https://code.google.com/p/diod/wiki/protocol ) -"use strict"; import { LOG_9P } from "./../src/const.js"; import { VirtIO, VIRTIO_F_VERSION_1, VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_RING_INDIRECT_DESC } from "../src/virtio.js"; diff --git a/lib/filesystem.js b/lib/filesystem.js index fbc4f61a..60fd30fb 100644 --- a/lib/filesystem.js +++ b/lib/filesystem.js @@ -3,7 +3,6 @@ // ------------------------------------------------- // Implementation of a unix filesystem in memory. -"use strict"; import { LOG_9P } from "../src/const.js"; import { h } from "../src/lib.js"; diff --git a/lib/marshall.js b/lib/marshall.js index b514e55a..548172be 100644 --- a/lib/marshall.js +++ b/lib/marshall.js @@ -2,7 +2,6 @@ // ------------------ Marshall --------------------- // ------------------------------------------------- // helper functions for virtio and 9p. -"use strict"; import { dbg_log } from "./../src/log.js"; diff --git a/src/acpi.js b/src/acpi.js index dbeab02a..f38150d6 100644 --- a/src/acpi.js +++ b/src/acpi.js @@ -1,5 +1,3 @@ -"use strict"; - // http://www.uefi.org/sites/default/files/resources/ACPI_6_1.pdf import { v86 } from "./main.js"; diff --git a/src/apic.js b/src/apic.js index 6462de0f..40192734 100644 --- a/src/apic.js +++ b/src/apic.js @@ -1,5 +1,3 @@ -"use strict"; - // See Intel's System Programming Guide import { v86 } from "./main.js"; diff --git a/src/browser/dummy_screen.js b/src/browser/dummy_screen.js index 6039d961..e7f6af63 100644 --- a/src/browser/dummy_screen.js +++ b/src/browser/dummy_screen.js @@ -1,5 +1,3 @@ -"use strict"; - import { dbg_assert } from "../log.js"; /** diff --git a/src/browser/fake_network.js b/src/browser/fake_network.js index 8b1290e3..5b39b28b 100644 --- a/src/browser/fake_network.js +++ b/src/browser/fake_network.js @@ -1,5 +1,3 @@ -"use strict"; - import { LOG_FETCH } from "../const.js"; import { h } from "../lib.js"; import { dbg_assert, dbg_log } from "../log.js"; diff --git a/src/browser/fetch_network.js b/src/browser/fetch_network.js index 7be99b2c..0526c618 100644 --- a/src/browser/fetch_network.js +++ b/src/browser/fetch_network.js @@ -1,5 +1,3 @@ -"use strict"; - import { LOG_FETCH } from "../const.js"; import { h } from "../lib.js"; import { dbg_log } from "../log.js"; diff --git a/src/browser/filestorage.js b/src/browser/filestorage.js index 2cfd8fdd..c84c7c11 100644 --- a/src/browser/filestorage.js +++ b/src/browser/filestorage.js @@ -1,6 +1,3 @@ -"use strict"; - - import { dbg_assert } from "../log.js"; import { load_file } from "../lib.js"; diff --git a/src/browser/inbrowser_network.js b/src/browser/inbrowser_network.js index 9ec1ea23..626eda03 100644 --- a/src/browser/inbrowser_network.js +++ b/src/browser/inbrowser_network.js @@ -1,5 +1,3 @@ -"use strict"; - // For Types Only import { BusConnector } from "../bus.js"; diff --git a/src/browser/keyboard.js b/src/browser/keyboard.js index 1f47bf2e..0465b68f 100644 --- a/src/browser/keyboard.js +++ b/src/browser/keyboard.js @@ -1,5 +1,3 @@ -"use strict"; - // For Types Only import { BusConnector } from "../bus.js"; diff --git a/src/browser/main.js b/src/browser/main.js index c87ba47e..f5a6df03 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1,5 +1,3 @@ -"use strict"; - import { V86 } from "./starter.js"; import { LOG_NAMES } from "../const.js"; import { print_stats } from "./print_stats.js"; diff --git a/src/browser/mouse.js b/src/browser/mouse.js index eb071ae2..9d43edbd 100644 --- a/src/browser/mouse.js +++ b/src/browser/mouse.js @@ -1,6 +1,3 @@ -"use strict"; - - import { dbg_log } from "../log.js"; // For Types Only diff --git a/src/browser/network.js b/src/browser/network.js index 4ee59d6d..72abcdfd 100644 --- a/src/browser/network.js +++ b/src/browser/network.js @@ -1,5 +1,3 @@ -"use strict"; - // For Types Only import { BusConnector } from "../bus.js"; diff --git a/src/browser/print_stats.js b/src/browser/print_stats.js index c2d84dd6..1c807411 100644 --- a/src/browser/print_stats.js +++ b/src/browser/print_stats.js @@ -1,5 +1,3 @@ -"use strict"; - import { pads } from "../lib.js"; export const print_stats = { diff --git a/src/browser/screen.js b/src/browser/screen.js index 5f1d3f80..ab314a23 100644 --- a/src/browser/screen.js +++ b/src/browser/screen.js @@ -1,5 +1,3 @@ -"use strict"; - import { dbg_assert } from "../log.js"; // Draws entire buffer and visualizes the layers that would be drawn diff --git a/src/browser/serial.js b/src/browser/serial.js index 4d804195..9d65b9b1 100644 --- a/src/browser/serial.js +++ b/src/browser/serial.js @@ -1,5 +1,3 @@ -"use strict"; - import { dbg_assert, dbg_log } from "../log.js"; // For Types Only diff --git a/src/browser/speaker.js b/src/browser/speaker.js index 2d29c10e..5a6273dd 100644 --- a/src/browser/speaker.js +++ b/src/browser/speaker.js @@ -1,5 +1,3 @@ -"use strict"; - import { MIXER_CHANNEL_BOTH, MIXER_CHANNEL_LEFT, MIXER_CHANNEL_RIGHT, MIXER_SRC_PCSPEAKER, MIXER_SRC_DAC, MIXER_SRC_MASTER, diff --git a/src/browser/starter.js b/src/browser/starter.js index 6be147df..c57b48cb 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -1,5 +1,3 @@ -"use strict"; - import { v86 } from "../main.js"; import { LOG_CPU, WASM_TABLE_OFFSET, WASM_TABLE_SIZE } from "../const.js"; import { get_rand_int, load_file, read_sized_string_from_mem } from "../lib.js"; diff --git a/src/browser/wisp_network.js b/src/browser/wisp_network.js index 99240b45..e0de942f 100644 --- a/src/browser/wisp_network.js +++ b/src/browser/wisp_network.js @@ -1,5 +1,3 @@ -"use strict"; - import { LOG_NET } from "../const.js"; import { dbg_log } from "../log.js"; diff --git a/src/browser/worker_bus.js b/src/browser/worker_bus.js index 3564d7c9..afe27803 100644 --- a/src/browser/worker_bus.js +++ b/src/browser/worker_bus.js @@ -1,5 +1,3 @@ -"use strict"; - import { dbg_assert } from "../log.js"; /** @constructor */ diff --git a/src/buffer.js b/src/buffer.js index ec3585f3..a2b97b9b 100644 --- a/src/buffer.js +++ b/src/buffer.js @@ -1,5 +1,3 @@ -"use strict"; - import { CPU } from "./cpu.js"; import { load_file, get_file_size } from "./lib.js"; import { dbg_assert, dbg_log } from "./log.js"; diff --git a/src/bus.js b/src/bus.js index 07ce172b..96b865c7 100644 --- a/src/bus.js +++ b/src/bus.js @@ -1,5 +1,3 @@ -"use strict"; - import { dbg_assert } from "./log.js"; export var Bus = {}; diff --git a/src/cjs.js b/src/cjs.js index 97a99c83..690e6630 100644 --- a/src/cjs.js +++ b/src/cjs.js @@ -1,5 +1,3 @@ -"use strict"; - /** * @define {boolean} * Overridden for production by closure compiler diff --git a/src/const.js b/src/const.js index 8d60ab33..f0189fb5 100644 --- a/src/const.js +++ b/src/const.js @@ -1,5 +1,3 @@ -"use strict"; - export const /** @const */ LOG_ALL = -1, /** @const */ LOG_NONE = 0, diff --git a/src/cpu.js b/src/cpu.js index 5abb04db..9690418a 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -1,5 +1,3 @@ -"use strict"; - import { LOG_CPU, LOG_BIOS, FW_CFG_SIGNATURE, FW_CFG_SIGNATURE_QEMU, diff --git a/src/dma.js b/src/dma.js index 6ad6d710..0345b76e 100644 --- a/src/dma.js +++ b/src/dma.js @@ -1,5 +1,3 @@ -"use strict"; - import { LOG_DMA } from "./const.js"; import { h } from "./lib.js"; import { dbg_log } from "./log.js"; diff --git a/src/elf.js b/src/elf.js index 95a0d9a1..4283fc67 100644 --- a/src/elf.js +++ b/src/elf.js @@ -1,5 +1,3 @@ -"use strict"; - import { dbg_log, LOG_LEVEL } from "./log.js"; // A minimal elf parser for loading 32 bit, x86, little endian, executable elf files diff --git a/src/externs.js b/src/externs.js index 057dfd05..9bd63a32 100644 --- a/src/externs.js +++ b/src/externs.js @@ -1,5 +1,3 @@ -"use strict"; - var global = {}; var process = { hrtime: function() {} }; diff --git a/src/floppy.js b/src/floppy.js index 302e29b5..437e8b66 100644 --- a/src/floppy.js +++ b/src/floppy.js @@ -1,5 +1,3 @@ -"use strict"; - // https://www.isdaman.com/alsos/hardware/fdc/floppy.htm // https://wiki.osdev.org/Floppy_Disk_Controller diff --git a/src/ide.js b/src/ide.js index ea4c97e7..5498ef1c 100644 --- a/src/ide.js +++ b/src/ide.js @@ -1,5 +1,3 @@ -"use strict"; - import { LOG_DISK } from "./const.js"; import { h } from "./lib.js"; import { dbg_assert, dbg_log } from "./log.js"; diff --git a/src/io.js b/src/io.js index c5900f52..3cdc6405 100644 --- a/src/io.js +++ b/src/io.js @@ -1,5 +1,3 @@ -"use strict"; - import { LOG_IO, MMAP_BLOCK_BITS, MMAP_BLOCK_SIZE, MMAP_MAX } from "./const.js"; import { h } from "./lib.js"; import { dbg_assert, dbg_log } from "./log.js"; diff --git a/src/ioapic.js b/src/ioapic.js index ab51b675..1e622840 100644 --- a/src/ioapic.js +++ b/src/ioapic.js @@ -1,5 +1,3 @@ -"use strict"; - // http://download.intel.com/design/chipsets/datashts/29056601.pdf import { LOG_APIC, MMAP_BLOCK_SIZE } from "./const.js"; diff --git a/src/kernel.js b/src/kernel.js index 0fdd0995..5362655b 100644 --- a/src/kernel.js +++ b/src/kernel.js @@ -1,5 +1,3 @@ -"use strict"; - import { h } from "./lib.js"; import { dbg_assert, dbg_log } from "./log.js"; diff --git a/src/lib.js b/src/lib.js index f5916047..fdab4c83 100644 --- a/src/lib.js +++ b/src/lib.js @@ -1,5 +1,3 @@ -"use strict"; - import { dbg_assert } from "./log.js"; // pad string with spaces on the right diff --git a/src/log.js b/src/log.js index 6464d55a..c8c60887 100644 --- a/src/log.js +++ b/src/log.js @@ -1,5 +1,3 @@ -"use strict"; - if(typeof DEBUG === "undefined") { globalThis.DEBUG = true; diff --git a/src/main.js b/src/main.js index ed1ba783..000e3430 100644 --- a/src/main.js +++ b/src/main.js @@ -1,5 +1,3 @@ -"use strict"; - import { CPU } from "./cpu.js"; import { save_state, restore_state } from "./state.js"; export { V86 } from "./browser/starter.js"; diff --git a/src/ne2k.js b/src/ne2k.js index 6f5a94b3..5668cd86 100644 --- a/src/ne2k.js +++ b/src/ne2k.js @@ -1,5 +1,3 @@ -"use strict"; - import { LOG_NET } from "./const.js"; import { h, hex_dump } from "./lib.js"; import { dbg_assert, dbg_log } from "./log.js"; diff --git a/src/pci.js b/src/pci.js index da43efa0..84d7a671 100644 --- a/src/pci.js +++ b/src/pci.js @@ -1,5 +1,3 @@ -"use strict"; - import { LOG_PCI } from "./const.js"; import { h } from "./lib.js"; import { dbg_assert, dbg_log } from "./log.js"; diff --git a/src/pit.js b/src/pit.js index 272b36c7..6d04107b 100644 --- a/src/pit.js +++ b/src/pit.js @@ -1,5 +1,3 @@ -"use strict"; - import { v86 } from "./main.js"; import { LOG_PIT } from "./const.js"; import { h } from "./lib.js"; diff --git a/src/ps2.js b/src/ps2.js index ac8c2994..2c93e77a 100644 --- a/src/ps2.js +++ b/src/ps2.js @@ -1,5 +1,3 @@ -"use strict"; - import { LOG_PS2 } from "./const.js"; import { h } from "./lib.js"; import { dbg_log } from "./log.js"; diff --git a/src/rtc.js b/src/rtc.js index 404ff4c7..147c9abd 100644 --- a/src/rtc.js +++ b/src/rtc.js @@ -1,5 +1,3 @@ -"use strict"; - import { v86 } from "./main.js"; import { LOG_RTC } from "./const.js"; import { h } from "./lib.js"; diff --git a/src/sb16.js b/src/sb16.js index 0c2bc263..b8c456c3 100644 --- a/src/sb16.js +++ b/src/sb16.js @@ -1,5 +1,3 @@ -"use strict"; - import { LOG_SB16, MIXER_CHANNEL_BOTH, MIXER_CHANNEL_LEFT, MIXER_CHANNEL_RIGHT, diff --git a/src/state.js b/src/state.js index f9d58a27..da9142c1 100644 --- a/src/state.js +++ b/src/state.js @@ -1,5 +1,3 @@ -"use strict"; - import { h } from "./lib.js"; import { dbg_assert, dbg_log } from "./log.js"; import { CPU } from "./cpu.js"; diff --git a/src/uart.js b/src/uart.js index c8078861..01ee766a 100644 --- a/src/uart.js +++ b/src/uart.js @@ -1,5 +1,3 @@ -"use strict"; - import { LOG_SERIAL } from "./const.js"; import { h } from "./lib.js"; import { dbg_log } from "./log.js"; diff --git a/src/vga.js b/src/vga.js index 61b0d605..07b78bb0 100644 --- a/src/vga.js +++ b/src/vga.js @@ -1,5 +1,3 @@ -"use strict"; - import { LOG_VGA } from "./const.js"; import { h } from "./lib.js"; import { dbg_assert, dbg_log } from "./log.js"; diff --git a/src/virtio.js b/src/virtio.js index 26be9836..045e3d8b 100644 --- a/src/virtio.js +++ b/src/virtio.js @@ -1,5 +1,3 @@ -"use strict"; - import { LOG_VIRTIO } from "./const.js"; import { h, zeros, int_log2 } from "./lib.js"; import { dbg_assert, dbg_log } from "./log.js"; diff --git a/src/virtio_balloon.js b/src/virtio_balloon.js index 59fce932..1308d437 100644 --- a/src/virtio_balloon.js +++ b/src/virtio_balloon.js @@ -1,5 +1,3 @@ -"use strict"; - // https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-2900003 import { LOG_PCI } from "./const.js"; diff --git a/src/virtio_console.js b/src/virtio_console.js index 177e8529..a8c87c5e 100644 --- a/src/virtio_console.js +++ b/src/virtio_console.js @@ -1,5 +1,3 @@ -"use strict"; - import { dbg_assert } from "./log.js"; import { VirtIO, VIRTIO_F_VERSION_1 } from "./virtio.js"; import * as marshall from "../lib/marshall.js"; diff --git a/src/virtio_net.js b/src/virtio_net.js index 495c8284..ea6e08bd 100644 --- a/src/virtio_net.js +++ b/src/virtio_net.js @@ -1,5 +1,3 @@ -"use strict"; - // https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-2900003 import { dbg_assert } from "./log.js"; diff --git a/tests/api/clean-shutdown.js b/tests/api/clean-shutdown.js index a9adbf52..40080988 100755 --- a/tests/api/clean-shutdown.js +++ b/tests/api/clean-shutdown.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import url from "node:url"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); diff --git a/tests/api/floppy-insert-eject.js b/tests/api/floppy-insert-eject.js index 13792408..94f168dc 100755 --- a/tests/api/floppy-insert-eject.js +++ b/tests/api/floppy-insert-eject.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import { setTimeout as pause } from "timers/promises"; import url from "node:url"; diff --git a/tests/api/pic.js b/tests/api/pic.js index f6227f45..769c4d32 100755 --- a/tests/api/pic.js +++ b/tests/api/pic.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import url from "node:url"; import fs from "node:fs"; diff --git a/tests/api/reboot.js b/tests/api/reboot.js index 89fe8afc..3d076843 100755 --- a/tests/api/reboot.js +++ b/tests/api/reboot.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import url from "node:url"; diff --git a/tests/api/reset.js b/tests/api/reset.js index b19d30b7..8df1e961 100755 --- a/tests/api/reset.js +++ b/tests/api/reset.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import url from "node:url"; diff --git a/tests/api/serial.js b/tests/api/serial.js index 2107cf0e..44084217 100755 --- a/tests/api/serial.js +++ b/tests/api/serial.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import url from "node:url"; import assert from "node:assert/strict"; diff --git a/tests/api/state.js b/tests/api/state.js index ea33b9a1..ef743dfa 100755 --- a/tests/api/state.js +++ b/tests/api/state.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import url from "node:url"; diff --git a/tests/api/test.js b/tests/api/test.js index 0d193a0a..86a6f3a2 100755 --- a/tests/api/test.js +++ b/tests/api/test.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import url from "node:url"; diff --git a/tests/benchmark/arch-bytemark.js b/tests/benchmark/arch-bytemark.js index 6eb6c82c..267fd4df 100755 --- a/tests/benchmark/arch-bytemark.js +++ b/tests/benchmark/arch-bytemark.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import path from "node:path"; import url from "node:url"; diff --git a/tests/benchmark/arch-python.js b/tests/benchmark/arch-python.js index c2d87399..6d40d8a8 100755 --- a/tests/benchmark/arch-python.js +++ b/tests/benchmark/arch-python.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import path from "node:path"; import url from "node:url"; diff --git a/tests/benchmark/linux-boot.js b/tests/benchmark/linux-boot.js index ed3c88fe..22ea8c40 100755 --- a/tests/benchmark/linux-boot.js +++ b/tests/benchmark/linux-boot.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import fs from "node:fs"; import path from "node:path"; diff --git a/tests/benchmark/snapshot.js b/tests/benchmark/snapshot.js index c7d437d1..ec65599e 100755 --- a/tests/benchmark/snapshot.js +++ b/tests/benchmark/snapshot.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import path from "node:path"; import url from "node:url"; diff --git a/tests/devices/fetch_network.js b/tests/devices/fetch_network.js index 9b70e1e2..96111763 100755 --- a/tests/devices/fetch_network.js +++ b/tests/devices/fetch_network.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import assert from "assert/strict"; import url from "node:url"; diff --git a/tests/devices/virtio_9p.js b/tests/devices/virtio_9p.js index 73188968..f02a495f 100755 --- a/tests/devices/virtio_9p.js +++ b/tests/devices/virtio_9p.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -"use strict"; + import url from "node:url"; import fs from "node:fs"; diff --git a/tests/devices/virtio_balloon.js b/tests/devices/virtio_balloon.js index b5f34711..004f78b1 100755 --- a/tests/devices/virtio_balloon.js +++ b/tests/devices/virtio_balloon.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -"use strict"; + import assert from "assert/strict"; import url from "node:url"; diff --git a/tests/devices/virtio_console.js b/tests/devices/virtio_console.js index 4ed81ab9..b3286fd7 100755 --- a/tests/devices/virtio_console.js +++ b/tests/devices/virtio_console.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -"use strict"; + import assert from "assert/strict"; import fs from "node:fs"; import url from "node:url"; diff --git a/tests/devices/wisp_network.js b/tests/devices/wisp_network.js index f70fb6dc..25feb73b 100755 --- a/tests/devices/wisp_network.js +++ b/tests/devices/wisp_network.js @@ -1,5 +1,5 @@ #!/usr/bin/env -S node --experimental-websocket -"use strict"; + import assert from "assert/strict"; import url from "node:url"; diff --git a/tests/expect/run.js b/tests/expect/run.js index cea8cd34..78871d1f 100755 --- a/tests/expect/run.js +++ b/tests/expect/run.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import fs from "node:fs"; import path from "node:path"; diff --git a/tests/full/run.js b/tests/full/run.js index 31f9a955..57e5746c 100755 --- a/tests/full/run.js +++ b/tests/full/run.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import assert from "node:assert/strict"; import cluster from "node:cluster"; diff --git a/tests/jit-paging/run.js b/tests/jit-paging/run.js index aa9e50b7..8130ecbd 100755 --- a/tests/jit-paging/run.js +++ b/tests/jit-paging/run.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import fs from "node:fs"; import url from "node:url"; diff --git a/tests/kvm-unit-tests/run.mjs b/tests/kvm-unit-tests/run.mjs index 29153af0..76ec8970 100755 --- a/tests/kvm-unit-tests/run.mjs +++ b/tests/kvm-unit-tests/run.mjs @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import fs from "node:fs"; import path from "node:path"; diff --git a/tests/nasm/create_tests.js b/tests/nasm/create_tests.js index e820f31d..9cc84861 100755 --- a/tests/nasm/create_tests.js +++ b/tests/nasm/create_tests.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import fs from "node:fs"; import fse from "node:fs/promises"; diff --git a/tests/nasm/gen_fixtures.js b/tests/nasm/gen_fixtures.js index 36922117..feea254c 100755 --- a/tests/nasm/gen_fixtures.js +++ b/tests/nasm/gen_fixtures.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import fs from "node:fs"; import path from "node:path"; diff --git a/tests/nasm/rand.js b/tests/nasm/rand.js index 66076e49..02bc3b49 100644 --- a/tests/nasm/rand.js +++ b/tests/nasm/rand.js @@ -1,5 +1,3 @@ -"use strict"; - // From http://baagoe.com/en/RandomMusings/javascript/ // Johannes Baagøe , 2010 function Mash() { diff --git a/tests/nasm/run.js b/tests/nasm/run.js index 9dcf72ab..9d6990a6 100755 --- a/tests/nasm/run.js +++ b/tests/nasm/run.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import fs from "node:fs"; import path from "node:path"; diff --git a/tests/qemu/run-qemu.js b/tests/qemu/run-qemu.js index ce477bc3..c67c449a 100755 --- a/tests/qemu/run-qemu.js +++ b/tests/qemu/run-qemu.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import fs from "node:fs"; import path from "node:path"; diff --git a/tests/qemu/run.js b/tests/qemu/run.js index 13138f01..b6bc14c5 100755 --- a/tests/qemu/run.js +++ b/tests/qemu/run.js @@ -1,7 +1,4 @@ #!/usr/bin/env node -"use strict"; - -process.on("unhandledRejection", exn => { throw exn; }); import { fileURLToPath } from "url"; import path from "path"; @@ -11,6 +8,8 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); +process.on("unhandledRejection", exn => { throw exn; }); + var test_executable = new Uint8Array(fs.readFileSync(__dirname + "/test-i386")); var emulator = new V86({ diff --git a/tests/rust/verify-wasmgen-dummy-output.js b/tests/rust/verify-wasmgen-dummy-output.js index c7a94308..6e139624 100755 --- a/tests/rust/verify-wasmgen-dummy-output.js +++ b/tests/rust/verify-wasmgen-dummy-output.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import fs from "node:fs"; import path from "node:path"; diff --git a/tools/docker/alpine/build-state.js b/tools/docker/alpine/build-state.js index 616622dc..63409fbe 100755 --- a/tools/docker/alpine/build-state.js +++ b/tools/docker/alpine/build-state.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import path from "node:path"; import fs from "node:fs"; From 3bb724fa694838ff78e2498c160011725b284b3d Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 7 Apr 2025 19:43:58 +0700 Subject: [PATCH 035/301] minor: inline some library functions --- lib/9p.js | 7 +++++-- src/lib.js | 12 ------------ src/virtio.js | 4 ++-- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/lib/9p.js b/lib/9p.js index 70f4f8c8..06612cf1 100644 --- a/lib/9p.js +++ b/lib/9p.js @@ -4,12 +4,10 @@ // Implementation of the 9p filesystem device following the // 9P2000.L protocol ( https://code.google.com/p/diod/wiki/protocol ) - import { LOG_9P } from "./../src/const.js"; import { VirtIO, VIRTIO_F_VERSION_1, VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_RING_INDIRECT_DESC } from "../src/virtio.js"; import { S_IFREG, S_IFDIR, STATUS_UNLINKED } from "./filesystem.js"; import * as marshall from "../lib/marshall.js"; -import { range } from "../src/lib.js"; import { dbg_log, dbg_assert } from "../src/log.js"; import { h } from "../src/lib.js"; @@ -84,6 +82,11 @@ var FID_NONE = -1; var FID_INODE = 1; var FID_XATTR = 2; +function range(size) +{ + return Array.from(Array(size).keys()); +} + /** * @constructor * diff --git a/src/lib.js b/src/lib.js index fdab4c83..f7752d29 100644 --- a/src/lib.js +++ b/src/lib.js @@ -14,18 +14,6 @@ export function pad0(str, len) return str.padStart(len, "0"); } -// generates array given size with zeros -export function zeros(size) -{ - return Array(size).fill(0); -} - -// generates [0, 1, 2, ..., size-1] -export function range(size) -{ - return Array.from(Array(size).keys()); -} - export var view = function(constructor, memory, offset, length) { dbg_assert(offset >= 0); diff --git a/src/virtio.js b/src/virtio.js index 045e3d8b..e234a7a5 100644 --- a/src/virtio.js +++ b/src/virtio.js @@ -1,5 +1,5 @@ import { LOG_VIRTIO } from "./const.js"; -import { h, zeros, int_log2 } from "./lib.js"; +import { h, int_log2 } from "./lib.js"; import { dbg_assert, dbg_log } from "./log.js"; // For Types Only @@ -230,7 +230,7 @@ export function VirtIO(cpu, options) ]; // Prevent sparse arrays by preallocating. - this.pci_space = this.pci_space.concat(zeros(256 - this.pci_space.length)); + this.pci_space = this.pci_space.concat(Array(256 - this.pci_space.length).fill(0)); // Remaining PCI space is appended by capabilities further below. this.pci_id = options.pci_id; From 8559d4d46d8d228fd96c23923f8b7445c48b2f15 Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 7 Apr 2025 19:52:10 +0700 Subject: [PATCH 036/301] simplify print_stats exports --- src/browser/main.js | 2 +- src/browser/print_stats.js | 503 +++++++++++++++---------------- src/browser/starter.js | 2 +- src/main.js | 2 +- tests/benchmark/arch-bytemark.js | 4 +- tests/benchmark/arch-python.js | 4 +- tests/benchmark/linux-boot.js | 4 +- tests/benchmark/snapshot.js | 4 +- 8 files changed, 261 insertions(+), 264 deletions(-) diff --git a/src/browser/main.js b/src/browser/main.js index f5a6df03..16f1e65b 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1,6 +1,6 @@ import { V86 } from "./starter.js"; import { LOG_NAMES } from "../const.js"; -import { print_stats } from "./print_stats.js"; +import * as print_stats from "./print_stats.js"; import { SyncFileBuffer } from "../buffer.js"; import { pad0, pads, hex_dump, dump_file, download, round_up_to_next_power_of_2 } from "../lib.js"; import { log_data, LOG_LEVEL, set_log_level } from "../log.js"; diff --git a/src/browser/print_stats.js b/src/browser/print_stats.js index 1c807411..ea603bc4 100644 --- a/src/browser/print_stats.js +++ b/src/browser/print_stats.js @@ -1,284 +1,281 @@ import { pads } from "../lib.js"; -export const print_stats = { - stats_to_string: function(cpu) +export function stats_to_string(cpu) +{ + return print_misc_stats(cpu) + print_instruction_counts(cpu); +} + +function print_misc_stats(cpu) +{ + let text = ""; + + const stat_names = [ + "COMPILE", + "COMPILE_SKIPPED_NO_NEW_ENTRY_POINTS", + "COMPILE_WRONG_ADDRESS_SPACE", + "COMPILE_CUT_OFF_AT_END_OF_PAGE", + "COMPILE_WITH_LOOP_SAFETY", + "COMPILE_PAGE", + "COMPILE_PAGE/COMPILE", + "COMPILE_BASIC_BLOCK", + "COMPILE_DUPLICATED_BASIC_BLOCK", + "COMPILE_WASM_BLOCK", + "COMPILE_WASM_LOOP", + "COMPILE_DISPATCHER", + "COMPILE_ENTRY_POINT", + "COMPILE_WASM_TOTAL_BYTES", + "COMPILE_WASM_TOTAL_BYTES/COMPILE_PAGE", + "RUN_INTERPRETED", + "RUN_INTERPRETED_NEW_PAGE", + "RUN_INTERPRETED_PAGE_HAS_CODE", + "RUN_INTERPRETED_PAGE_HAS_ENTRY_AFTER_PAGE_WALK", + "RUN_INTERPRETED_NEAR_END_OF_PAGE", + "RUN_INTERPRETED_DIFFERENT_STATE", + "RUN_INTERPRETED_DIFFERENT_STATE_CPL3", + "RUN_INTERPRETED_DIFFERENT_STATE_FLAT", + "RUN_INTERPRETED_DIFFERENT_STATE_IS32", + "RUN_INTERPRETED_DIFFERENT_STATE_SS32", + "RUN_INTERPRETED_MISSED_COMPILED_ENTRY_RUN_INTERPRETED", + "RUN_INTERPRETED_STEPS", + "RUN_FROM_CACHE", + "RUN_FROM_CACHE_STEPS", + "RUN_FROM_CACHE_STEPS/RUN_FROM_CACHE", + "RUN_FROM_CACHE_STEPS/RUN_INTERPRETED_STEPS", + "DIRECT_EXIT", + "INDIRECT_JUMP", + "INDIRECT_JUMP_NO_ENTRY", + "NORMAL_PAGE_CHANGE", + "NORMAL_FALLTHRU", + "NORMAL_FALLTHRU_WITH_TARGET_BLOCK", + "NORMAL_BRANCH", + "NORMAL_BRANCH_WITH_TARGET_BLOCK", + "CONDITIONAL_JUMP", + "CONDITIONAL_JUMP_PAGE_CHANGE", + "CONDITIONAL_JUMP_EXIT", + "CONDITIONAL_JUMP_FALLTHRU", + "CONDITIONAL_JUMP_FALLTHRU_WITH_TARGET_BLOCK", + "CONDITIONAL_JUMP_BRANCH", + "CONDITIONAL_JUMP_BRANCH_WITH_TARGET_BLOCK", + "DISPATCHER_SMALL", + "DISPATCHER_LARGE", + "LOOP", + "LOOP_SAFETY", + "CONDITION_OPTIMISED", + "CONDITION_UNOPTIMISED", + "CONDITION_UNOPTIMISED_PF", + "CONDITION_UNOPTIMISED_UNHANDLED_L", + "CONDITION_UNOPTIMISED_UNHANDLED_LE", + "FAILED_PAGE_CHANGE", + "SAFE_READ_FAST", + "SAFE_READ_SLOW_PAGE_CROSSED", + "SAFE_READ_SLOW_NOT_VALID", + "SAFE_READ_SLOW_NOT_USER", + "SAFE_READ_SLOW_IN_MAPPED_RANGE", + "SAFE_WRITE_FAST", + "SAFE_WRITE_SLOW_PAGE_CROSSED", + "SAFE_WRITE_SLOW_NOT_VALID", + "SAFE_WRITE_SLOW_NOT_USER", + "SAFE_WRITE_SLOW_IN_MAPPED_RANGE", + "SAFE_WRITE_SLOW_READ_ONLY", + "SAFE_WRITE_SLOW_HAS_CODE", + "SAFE_READ_WRITE_FAST", + "SAFE_READ_WRITE_SLOW_PAGE_CROSSED", + "SAFE_READ_WRITE_SLOW_NOT_VALID", + "SAFE_READ_WRITE_SLOW_NOT_USER", + "SAFE_READ_WRITE_SLOW_IN_MAPPED_RANGE", + "SAFE_READ_WRITE_SLOW_READ_ONLY", + "SAFE_READ_WRITE_SLOW_HAS_CODE", + "PAGE_FAULT", + "TLB_MISS", + "MAIN_LOOP", + "MAIN_LOOP_IDLE", + "DO_MANY_CYCLES", + "CYCLE_INTERNAL", + "INVALIDATE_ALL_MODULES_NO_FREE_WASM_INDICES", + "INVALIDATE_MODULE_WRITTEN_WHILE_COMPILED", + "INVALIDATE_MODULE_UNUSED_AFTER_OVERWRITE", + "INVALIDATE_MODULE_DIRTY_PAGE", + "INVALIDATE_PAGE_HAD_CODE", + "INVALIDATE_PAGE_HAD_ENTRY_POINTS", + "DIRTY_PAGE_DID_NOT_HAVE_CODE", + "RUN_FROM_CACHE_EXIT_SAME_PAGE", + "RUN_FROM_CACHE_EXIT_NEAR_END_OF_PAGE", + "RUN_FROM_CACHE_EXIT_DIFFERENT_PAGE", + "CLEAR_TLB", + "FULL_CLEAR_TLB", + "TLB_FULL", + "TLB_GLOBAL_FULL", + "MODRM_SIMPLE_REG", + "MODRM_SIMPLE_REG_WITH_OFFSET", + "MODRM_SIMPLE_CONST_OFFSET", + "MODRM_COMPLEX", + "SEG_OFFSET_OPTIMISED", + "SEG_OFFSET_NOT_OPTIMISED", + "SEG_OFFSET_NOT_OPTIMISED_ES", + "SEG_OFFSET_NOT_OPTIMISED_FS", + "SEG_OFFSET_NOT_OPTIMISED_GS", + "SEG_OFFSET_NOT_OPTIMISED_NOT_FLAT", + ]; + + let j = 0; + const stat_values = {}; + for(let i = 0; i < stat_names.length; i++) { - return print_stats.print_misc_stats(cpu) + - print_stats.print_instruction_counts(cpu); - }, + const name = stat_names[i]; + let value; + if(name.includes("/")) + { + j++; // skip profiler_stat_get + const [left, right] = name.split("/"); + value = stat_values[left] / stat_values[right]; + } + else + { + const stat = stat_values[name] = cpu.wm.exports["profiler_stat_get"](i - j); + value = stat >= 100e6 ? Math.round(stat / 1e6) + "m" : stat >= 100e3 ? Math.round(stat / 1e3) + "k" : stat; + } + text += name + "=" + value + "\n"; + } - print_misc_stats: function(cpu) + text += "\n"; + + const tlb_entries = cpu.wm.exports["get_valid_tlb_entries_count"](); + const global_tlb_entries = cpu.wm.exports["get_valid_global_tlb_entries_count"](); + const nonglobal_tlb_entries = tlb_entries - global_tlb_entries; + + text += "TLB_ENTRIES=" + tlb_entries + " (" + global_tlb_entries + " global, " + nonglobal_tlb_entries + " non-global)\n"; + text += "WASM_TABLE_FREE=" + cpu.wm.exports["jit_get_wasm_table_index_free_list_count"]() + "\n"; + text += "JIT_CACHE_SIZE=" + cpu.wm.exports["jit_get_cache_size"]() + "\n"; + text += "FLAT_SEGMENTS=" + cpu.wm.exports["has_flat_segmentation"]() + "\n"; + + text += "wasm memory size: " + (cpu.wasm_memory.buffer.byteLength >> 20) + "m\n"; + + text += "Config:\n"; + text += "JIT_DISABLED=" + cpu.wm.exports["get_jit_config"](0) + "\n"; + text += "MAX_PAGES=" + cpu.wm.exports["get_jit_config"](1) + "\n"; + text += "JIT_USE_LOOP_SAFETY=" + Boolean(cpu.wm.exports["get_jit_config"](2)) + "\n"; + text += "MAX_EXTRA_BASIC_BLOCKS=" + cpu.wm.exports["get_jit_config"](3) + "\n"; + + return text; +} + +function print_instruction_counts(cpu) +{ + return [ + print_instruction_counts_offset(cpu, false, false, false, false), + print_instruction_counts_offset(cpu, true, false, false, false), + print_instruction_counts_offset(cpu, false, true, false, false), + print_instruction_counts_offset(cpu, false, false, true, false), + print_instruction_counts_offset(cpu, false, false, false, true), + ].join("\n\n"); +} + +function print_instruction_counts_offset(cpu, compiled, jit_exit, unguarded_register, wasm_size) +{ + let text = ""; + + const counts = []; + + const label = + compiled ? "compiled" : + jit_exit ? "jit exit" : + unguarded_register ? "unguarded register" : + wasm_size ? "wasm size" : + "executed"; + + for(let opcode = 0; opcode < 0x100; opcode++) { - let text = ""; - - const stat_names = [ - "COMPILE", - "COMPILE_SKIPPED_NO_NEW_ENTRY_POINTS", - "COMPILE_WRONG_ADDRESS_SPACE", - "COMPILE_CUT_OFF_AT_END_OF_PAGE", - "COMPILE_WITH_LOOP_SAFETY", - "COMPILE_PAGE", - "COMPILE_PAGE/COMPILE", - "COMPILE_BASIC_BLOCK", - "COMPILE_DUPLICATED_BASIC_BLOCK", - "COMPILE_WASM_BLOCK", - "COMPILE_WASM_LOOP", - "COMPILE_DISPATCHER", - "COMPILE_ENTRY_POINT", - "COMPILE_WASM_TOTAL_BYTES", - "COMPILE_WASM_TOTAL_BYTES/COMPILE_PAGE", - "RUN_INTERPRETED", - "RUN_INTERPRETED_NEW_PAGE", - "RUN_INTERPRETED_PAGE_HAS_CODE", - "RUN_INTERPRETED_PAGE_HAS_ENTRY_AFTER_PAGE_WALK", - "RUN_INTERPRETED_NEAR_END_OF_PAGE", - "RUN_INTERPRETED_DIFFERENT_STATE", - "RUN_INTERPRETED_DIFFERENT_STATE_CPL3", - "RUN_INTERPRETED_DIFFERENT_STATE_FLAT", - "RUN_INTERPRETED_DIFFERENT_STATE_IS32", - "RUN_INTERPRETED_DIFFERENT_STATE_SS32", - "RUN_INTERPRETED_MISSED_COMPILED_ENTRY_RUN_INTERPRETED", - "RUN_INTERPRETED_STEPS", - "RUN_FROM_CACHE", - "RUN_FROM_CACHE_STEPS", - "RUN_FROM_CACHE_STEPS/RUN_FROM_CACHE", - "RUN_FROM_CACHE_STEPS/RUN_INTERPRETED_STEPS", - "DIRECT_EXIT", - "INDIRECT_JUMP", - "INDIRECT_JUMP_NO_ENTRY", - "NORMAL_PAGE_CHANGE", - "NORMAL_FALLTHRU", - "NORMAL_FALLTHRU_WITH_TARGET_BLOCK", - "NORMAL_BRANCH", - "NORMAL_BRANCH_WITH_TARGET_BLOCK", - "CONDITIONAL_JUMP", - "CONDITIONAL_JUMP_PAGE_CHANGE", - "CONDITIONAL_JUMP_EXIT", - "CONDITIONAL_JUMP_FALLTHRU", - "CONDITIONAL_JUMP_FALLTHRU_WITH_TARGET_BLOCK", - "CONDITIONAL_JUMP_BRANCH", - "CONDITIONAL_JUMP_BRANCH_WITH_TARGET_BLOCK", - "DISPATCHER_SMALL", - "DISPATCHER_LARGE", - "LOOP", - "LOOP_SAFETY", - "CONDITION_OPTIMISED", - "CONDITION_UNOPTIMISED", - "CONDITION_UNOPTIMISED_PF", - "CONDITION_UNOPTIMISED_UNHANDLED_L", - "CONDITION_UNOPTIMISED_UNHANDLED_LE", - "FAILED_PAGE_CHANGE", - "SAFE_READ_FAST", - "SAFE_READ_SLOW_PAGE_CROSSED", - "SAFE_READ_SLOW_NOT_VALID", - "SAFE_READ_SLOW_NOT_USER", - "SAFE_READ_SLOW_IN_MAPPED_RANGE", - "SAFE_WRITE_FAST", - "SAFE_WRITE_SLOW_PAGE_CROSSED", - "SAFE_WRITE_SLOW_NOT_VALID", - "SAFE_WRITE_SLOW_NOT_USER", - "SAFE_WRITE_SLOW_IN_MAPPED_RANGE", - "SAFE_WRITE_SLOW_READ_ONLY", - "SAFE_WRITE_SLOW_HAS_CODE", - "SAFE_READ_WRITE_FAST", - "SAFE_READ_WRITE_SLOW_PAGE_CROSSED", - "SAFE_READ_WRITE_SLOW_NOT_VALID", - "SAFE_READ_WRITE_SLOW_NOT_USER", - "SAFE_READ_WRITE_SLOW_IN_MAPPED_RANGE", - "SAFE_READ_WRITE_SLOW_READ_ONLY", - "SAFE_READ_WRITE_SLOW_HAS_CODE", - "PAGE_FAULT", - "TLB_MISS", - "MAIN_LOOP", - "MAIN_LOOP_IDLE", - "DO_MANY_CYCLES", - "CYCLE_INTERNAL", - "INVALIDATE_ALL_MODULES_NO_FREE_WASM_INDICES", - "INVALIDATE_MODULE_WRITTEN_WHILE_COMPILED", - "INVALIDATE_MODULE_UNUSED_AFTER_OVERWRITE", - "INVALIDATE_MODULE_DIRTY_PAGE", - "INVALIDATE_PAGE_HAD_CODE", - "INVALIDATE_PAGE_HAD_ENTRY_POINTS", - "DIRTY_PAGE_DID_NOT_HAVE_CODE", - "RUN_FROM_CACHE_EXIT_SAME_PAGE", - "RUN_FROM_CACHE_EXIT_NEAR_END_OF_PAGE", - "RUN_FROM_CACHE_EXIT_DIFFERENT_PAGE", - "CLEAR_TLB", - "FULL_CLEAR_TLB", - "TLB_FULL", - "TLB_GLOBAL_FULL", - "MODRM_SIMPLE_REG", - "MODRM_SIMPLE_REG_WITH_OFFSET", - "MODRM_SIMPLE_CONST_OFFSET", - "MODRM_COMPLEX", - "SEG_OFFSET_OPTIMISED", - "SEG_OFFSET_NOT_OPTIMISED", - "SEG_OFFSET_NOT_OPTIMISED_ES", - "SEG_OFFSET_NOT_OPTIMISED_FS", - "SEG_OFFSET_NOT_OPTIMISED_GS", - "SEG_OFFSET_NOT_OPTIMISED_NOT_FLAT", - ]; - - let j = 0; - const stat_values = {}; - for(let i = 0; i < stat_names.length; i++) + for(let fixed_g = 0; fixed_g < 8; fixed_g++) { - const name = stat_names[i]; - let value; - if(name.includes("/")) + for(const is_mem of [false, true]) { - j++; // skip profiler_stat_get - const [left, right] = name.split("/"); - value = stat_values[left] / stat_values[right]; + const count = cpu.wm.exports["get_opstats_buffer"](compiled, jit_exit, unguarded_register, wasm_size, opcode, false, is_mem, fixed_g); + counts.push({ opcode, count, is_mem, fixed_g }); + + const count_0f = cpu.wm.exports["get_opstats_buffer"](compiled, jit_exit, unguarded_register, wasm_size, opcode, true, is_mem, fixed_g); + counts.push({ opcode: 0x0f00 | opcode, count: count_0f, is_mem, fixed_g }); } - else - { - const stat = stat_values[name] = cpu.wm.exports["profiler_stat_get"](i - j); - value = stat >= 100e6 ? Math.round(stat / 1e6) + "m" : stat >= 100e3 ? Math.round(stat / 1e3) + "k" : stat; - } - text += name + "=" + value + "\n"; } + } - text += "\n"; - - const tlb_entries = cpu.wm.exports["get_valid_tlb_entries_count"](); - const global_tlb_entries = cpu.wm.exports["get_valid_global_tlb_entries_count"](); - const nonglobal_tlb_entries = tlb_entries - global_tlb_entries; - - text += "TLB_ENTRIES=" + tlb_entries + " (" + global_tlb_entries + " global, " + nonglobal_tlb_entries + " non-global)\n"; - text += "WASM_TABLE_FREE=" + cpu.wm.exports["jit_get_wasm_table_index_free_list_count"]() + "\n"; - text += "JIT_CACHE_SIZE=" + cpu.wm.exports["jit_get_cache_size"]() + "\n"; - text += "FLAT_SEGMENTS=" + cpu.wm.exports["has_flat_segmentation"]() + "\n"; - - text += "wasm memory size: " + (cpu.wasm_memory.buffer.byteLength >> 20) + "m\n"; - - text += "Config:\n"; - text += "JIT_DISABLED=" + cpu.wm.exports["get_jit_config"](0) + "\n"; - text += "MAX_PAGES=" + cpu.wm.exports["get_jit_config"](1) + "\n"; - text += "JIT_USE_LOOP_SAFETY=" + Boolean(cpu.wm.exports["get_jit_config"](2)) + "\n"; - text += "MAX_EXTRA_BASIC_BLOCKS=" + cpu.wm.exports["get_jit_config"](3) + "\n"; - - return text; - }, - - print_instruction_counts: function(cpu) + let total = 0; + const prefixes = new Set([ + 0x26, 0x2E, 0x36, 0x3E, + 0x64, 0x65, 0x66, 0x67, + 0xF0, 0xF2, 0xF3, + ]); + for(const { count, opcode } of counts) { - return [ - print_stats.print_instruction_counts_offset(cpu, false, false, false, false), - print_stats.print_instruction_counts_offset(cpu, true, false, false, false), - print_stats.print_instruction_counts_offset(cpu, false, true, false, false), - print_stats.print_instruction_counts_offset(cpu, false, false, true, false), - print_stats.print_instruction_counts_offset(cpu, false, false, false, true), - ].join("\n\n"); - }, + if(!prefixes.has(opcode)) + { + total += count; + } + } - print_instruction_counts_offset: function(cpu, compiled, jit_exit, unguarded_register, wasm_size) + if(total === 0) { - let text = ""; + return ""; + } - const counts = []; + const per_opcode = new Uint32Array(0x100); + const per_opcode0f = new Uint32Array(0x100); - const label = - compiled ? "compiled" : - jit_exit ? "jit exit" : - unguarded_register ? "unguarded register" : - wasm_size ? "wasm size" : - "executed"; - - for(let opcode = 0; opcode < 0x100; opcode++) + for(const { opcode, count } of counts) + { + if((opcode & 0xFF00) === 0x0F00) { - for(let fixed_g = 0; fixed_g < 8; fixed_g++) - { - for(const is_mem of [false, true]) - { - const count = cpu.wm.exports["get_opstats_buffer"](compiled, jit_exit, unguarded_register, wasm_size, opcode, false, is_mem, fixed_g); - counts.push({ opcode, count, is_mem, fixed_g }); - - const count_0f = cpu.wm.exports["get_opstats_buffer"](compiled, jit_exit, unguarded_register, wasm_size, opcode, true, is_mem, fixed_g); - counts.push({ opcode: 0x0f00 | opcode, count: count_0f, is_mem, fixed_g }); - } - } + per_opcode0f[opcode & 0xFF] += count; } - - let total = 0; - const prefixes = new Set([ - 0x26, 0x2E, 0x36, 0x3E, - 0x64, 0x65, 0x66, 0x67, - 0xF0, 0xF2, 0xF3, - ]); - for(const { count, opcode } of counts) + else { - if(!prefixes.has(opcode)) - { - total += count; - } + per_opcode[opcode & 0xFF] += count; } + } - if(total === 0) - { - return ""; - } + text += "------------------\n"; + text += "Total: " + total + "\n"; - const per_opcode = new Uint32Array(0x100); - const per_opcode0f = new Uint32Array(0x100); + const factor = total > 1e7 ? 1000 : 1; - for(const { opcode, count } of counts) - { - if((opcode & 0xFF00) === 0x0F00) - { - per_opcode0f[opcode & 0xFF] += count; - } - else - { - per_opcode[opcode & 0xFF] += count; - } - } + const max_count = Math.max.apply(Math, + counts.map(({ count }) => Math.round(count / factor)) + ); + const pad_length = String(max_count).length; - text += "------------------\n"; - text += "Total: " + total + "\n"; + text += `Instruction counts ${label} (in ${factor}):\n`; - const factor = total > 1e7 ? 1000 : 1; + for(let i = 0; i < 0x100; i++) + { + text += i.toString(16).padStart(2, "0") + ":" + pads(Math.round(per_opcode[i] / factor), pad_length); - const max_count = Math.max.apply(Math, - counts.map(({ count }) => Math.round(count / factor)) - ); - const pad_length = String(max_count).length; + if(i % 16 === 15) + text += "\n"; + else + text += " "; + } - text += `Instruction counts ${label} (in ${factor}):\n`; + text += "\n"; + text += `Instruction counts ${label} (0f, in ${factor}):\n`; - for(let i = 0; i < 0x100; i++) - { - text += i.toString(16).padStart(2, "0") + ":" + pads(Math.round(per_opcode[i] / factor), pad_length); + for(let i = 0; i < 0x100; i++) + { + text += (i & 0xFF).toString(16).padStart(2, "0") + ":" + pads(Math.round(per_opcode0f[i] / factor), pad_length); - if(i % 16 === 15) - text += "\n"; - else - text += " "; - } + if(i % 16 === 15) + text += "\n"; + else + text += " "; + } + text += "\n"; - text += "\n"; - text += `Instruction counts ${label} (0f, in ${factor}):\n`; + const top_counts = counts.filter(({ count }) => count).sort(({ count: count1 }, { count: count2 }) => count2 - count1); - for(let i = 0; i < 0x100; i++) - { - text += (i & 0xFF).toString(16).padStart(2, "0") + ":" + pads(Math.round(per_opcode0f[i] / factor), pad_length); + for(const { opcode, is_mem, fixed_g, count } of top_counts.slice(0, 200)) + { + const opcode_description = opcode.toString(16) + "_" + fixed_g + (is_mem ? "_m" : "_r"); + text += opcode_description + ":" + (count / total * 100).toFixed(2) + " "; + } + text += "\n"; - if(i % 16 === 15) - text += "\n"; - else - text += " "; - } - text += "\n"; - - const top_counts = counts.filter(({ count }) => count).sort(({ count: count1 }, { count: count2 }) => count2 - count1); - - for(const { opcode, is_mem, fixed_g, count } of top_counts.slice(0, 200)) - { - const opcode_description = opcode.toString(16) + "_" + fixed_g + (is_mem ? "_m" : "_r"); - text += opcode_description + ":" + (count / total * 100).toFixed(2) + " "; - } - text += "\n"; - - return text; - }, -}; + return text; +} diff --git a/src/browser/starter.js b/src/browser/starter.js index c57b48cb..56cfd944 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -2,7 +2,7 @@ import { v86 } from "../main.js"; import { LOG_CPU, WASM_TABLE_OFFSET, WASM_TABLE_SIZE } from "../const.js"; import { get_rand_int, load_file, read_sized_string_from_mem } from "../lib.js"; import { dbg_assert, dbg_trace, dbg_log, set_log_level } from "../log.js"; -import { print_stats } from "./print_stats.js"; +import * as print_stats from "./print_stats.js"; import { Bus } from "../bus.js"; import { BOOT_ORDER_FD_FIRST, BOOT_ORDER_HD_FIRST, BOOT_ORDER_CD_FIRST } from "../rtc.js"; diff --git a/src/main.js b/src/main.js index 000e3430..f739f508 100644 --- a/src/main.js +++ b/src/main.js @@ -1,7 +1,7 @@ import { CPU } from "./cpu.js"; import { save_state, restore_state } from "./state.js"; export { V86 } from "./browser/starter.js"; -export { print_stats } from "./browser/print_stats.js"; +export { stats_to_string } from "./browser/print_stats.js"; /** * @constructor diff --git a/tests/benchmark/arch-bytemark.js b/tests/benchmark/arch-bytemark.js index 267fd4df..0e1cdffe 100755 --- a/tests/benchmark/arch-bytemark.js +++ b/tests/benchmark/arch-bytemark.js @@ -5,7 +5,7 @@ import url from "node:url"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const BENCH_COLLECT_STATS = +process.env.BENCH_COLLECT_STATS; -const { V86, print_stats } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); +const { V86, stats_to_string } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); const V86_ROOT = path.join(__dirname, "../.."); @@ -78,7 +78,7 @@ emulator.add_listener("serial0-output-byte", function(byte) if(BENCH_COLLECT_STATS) { const cpu = emulator.v86.cpu; - console.log(print_stats.stats_to_string(cpu)); + console.log(stats_to_string(cpu)); } } }); diff --git a/tests/benchmark/arch-python.js b/tests/benchmark/arch-python.js index 6d40d8a8..638b2df5 100755 --- a/tests/benchmark/arch-python.js +++ b/tests/benchmark/arch-python.js @@ -6,7 +6,7 @@ import url from "node:url"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const BENCH_COLLECT_STATS = +process.env.BENCH_COLLECT_STATS; -const { V86, print_stats } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); +const { V86, stats_to_string } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); const V86_ROOT = path.join(__dirname, "../.."); @@ -64,7 +64,7 @@ emulator.add_listener("serial0-output-byte", function(byte) if(BENCH_COLLECT_STATS) { const cpu = emulator.v86.cpu; - console.log(print_stats.stats_to_string(cpu)); + console.log(stats_to_string(cpu)); } } diff --git a/tests/benchmark/linux-boot.js b/tests/benchmark/linux-boot.js index 22ea8c40..84b1322b 100755 --- a/tests/benchmark/linux-boot.js +++ b/tests/benchmark/linux-boot.js @@ -7,7 +7,7 @@ import url from "node:url"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const BENCH_COLLECT_STATS = +process.env.BENCH_COLLECT_STATS; -const { V86, print_stats } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); +const { V86, stats_to_string } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); const V86_ROOT = path.join(__dirname, "../.."); @@ -78,7 +78,7 @@ emulator.add_listener("serial0-output-byte", function(byte) if(BENCH_COLLECT_STATS) { const cpu = emulator.v86.cpu; - console.log(print_stats.stats_to_string(cpu)); + console.log(stats_to_string(cpu)); } } }); diff --git a/tests/benchmark/snapshot.js b/tests/benchmark/snapshot.js index ec65599e..6cb3d821 100755 --- a/tests/benchmark/snapshot.js +++ b/tests/benchmark/snapshot.js @@ -4,7 +4,7 @@ import path from "node:path"; import url from "node:url"; const BENCH_COLLECT_STATS = +process.env.BENCH_COLLECT_STATS; -const { V86, print_stats } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); +const { V86, stats_to_string } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const V86_ROOT = path.join(__dirname, "../.."); @@ -57,7 +57,7 @@ emulator.add_listener("serial0-output-byte", function(byte) if(BENCH_COLLECT_STATS) { const cpu = emulator.v86.cpu; - console.log(print_stats.stats_to_string(cpu)); + console.log(stats_to_string(cpu)); } } }); From 7936bcd17d47271019c6c94eddca1757c3284051 Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 8 Apr 2025 17:19:49 +0700 Subject: [PATCH 037/301] put window/self exports back --- src/browser/starter.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/browser/starter.js b/src/browser/starter.js index 56cfd944..6c0dbd56 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -1468,10 +1468,19 @@ function FileNotFoundError(message) } FileNotFoundError.prototype = Error.prototype; -/* global module */ +/* global module, self */ if(typeof module !== "undefined" && typeof module.exports !== "undefined") { module.exports["V86"] = V86; module.exports["print_stats"] = print_stats; } +else if(typeof window !== "undefined") +{ + window["V86"] = V86; +} +else if(typeof importScripts === "function") +{ + // web worker + self["V86"] = V86; +} From ee9be5a2666314060b85aa99caa2aa9d8a9a1349 Mon Sep 17 00:00:00 2001 From: Fabian Date: Wed, 9 Apr 2025 15:27:56 +0700 Subject: [PATCH 038/301] move stats_to_string into V86, remove the export --- src/browser/main.js | 4 +--- src/browser/starter.js | 6 +++++- src/main.js | 1 - tests/benchmark/arch-bytemark.js | 5 ++--- tests/benchmark/arch-python.js | 5 ++--- tests/benchmark/linux-boot.js | 5 ++--- tests/benchmark/snapshot.js | 5 ++--- 7 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/browser/main.js b/src/browser/main.js index 16f1e65b..a702f022 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1,6 +1,5 @@ import { V86 } from "./starter.js"; import { LOG_NAMES } from "../const.js"; -import * as print_stats from "./print_stats.js"; import { SyncFileBuffer } from "../buffer.js"; import { pad0, pads, hex_dump, dump_file, download, round_up_to_next_power_of_2 } from "../lib.js"; import { log_data, LOG_LEVEL, set_log_level } from "../log.js"; @@ -2047,8 +2046,7 @@ function start_emulation(profile, query_args) return; } - const text = print_stats.stats_to_string(emulator.v86.cpu); - panel.textContent = text; + panel.textContent = emulator.get_instruction_stats(); CLEAR_STATS && emulator.v86.cpu.clear_opstats(); }, CLEAR_STATS ? 5000 : 1000); diff --git a/src/browser/starter.js b/src/browser/starter.js index 6c0dbd56..45b0b9cb 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -1444,6 +1444,11 @@ V86.prototype.set_serial_container_xtermjs = function(element) this.serial_adapter.show(); }; +V86.prototype.get_instruction_stats = function() +{ + return print_stats.stats_to_string(this.v86.cpu); +}; + /** * @ignore * @constructor @@ -1473,7 +1478,6 @@ FileNotFoundError.prototype = Error.prototype; if(typeof module !== "undefined" && typeof module.exports !== "undefined") { module.exports["V86"] = V86; - module.exports["print_stats"] = print_stats; } else if(typeof window !== "undefined") { diff --git a/src/main.js b/src/main.js index f739f508..d410056b 100644 --- a/src/main.js +++ b/src/main.js @@ -1,7 +1,6 @@ import { CPU } from "./cpu.js"; import { save_state, restore_state } from "./state.js"; export { V86 } from "./browser/starter.js"; -export { stats_to_string } from "./browser/print_stats.js"; /** * @constructor diff --git a/tests/benchmark/arch-bytemark.js b/tests/benchmark/arch-bytemark.js index 0e1cdffe..5ffe091a 100755 --- a/tests/benchmark/arch-bytemark.js +++ b/tests/benchmark/arch-bytemark.js @@ -5,7 +5,7 @@ import url from "node:url"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const BENCH_COLLECT_STATS = +process.env.BENCH_COLLECT_STATS; -const { V86, stats_to_string } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); +const { V86 } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); const V86_ROOT = path.join(__dirname, "../.."); @@ -77,8 +77,7 @@ emulator.add_listener("serial0-output-byte", function(byte) if(BENCH_COLLECT_STATS) { - const cpu = emulator.v86.cpu; - console.log(stats_to_string(cpu)); + console.log(emulator.get_instruction_stats()); } } }); diff --git a/tests/benchmark/arch-python.js b/tests/benchmark/arch-python.js index 638b2df5..c6f54c64 100755 --- a/tests/benchmark/arch-python.js +++ b/tests/benchmark/arch-python.js @@ -6,7 +6,7 @@ import url from "node:url"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const BENCH_COLLECT_STATS = +process.env.BENCH_COLLECT_STATS; -const { V86, stats_to_string } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); +const { V86 } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); const V86_ROOT = path.join(__dirname, "../.."); @@ -63,8 +63,7 @@ emulator.add_listener("serial0-output-byte", function(byte) if(BENCH_COLLECT_STATS) { - const cpu = emulator.v86.cpu; - console.log(stats_to_string(cpu)); + console.log(emulator.get_instruction_stats()); } } diff --git a/tests/benchmark/linux-boot.js b/tests/benchmark/linux-boot.js index 84b1322b..547dc5d6 100755 --- a/tests/benchmark/linux-boot.js +++ b/tests/benchmark/linux-boot.js @@ -7,7 +7,7 @@ import url from "node:url"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const BENCH_COLLECT_STATS = +process.env.BENCH_COLLECT_STATS; -const { V86, stats_to_string } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); +const { V86 } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); const V86_ROOT = path.join(__dirname, "../.."); @@ -77,8 +77,7 @@ emulator.add_listener("serial0-output-byte", function(byte) if(BENCH_COLLECT_STATS) { - const cpu = emulator.v86.cpu; - console.log(stats_to_string(cpu)); + console.log(emulator.get_instruction_stats()); } } }); diff --git a/tests/benchmark/snapshot.js b/tests/benchmark/snapshot.js index 6cb3d821..179ba183 100755 --- a/tests/benchmark/snapshot.js +++ b/tests/benchmark/snapshot.js @@ -4,7 +4,7 @@ import path from "node:path"; import url from "node:url"; const BENCH_COLLECT_STATS = +process.env.BENCH_COLLECT_STATS; -const { V86, stats_to_string } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); +const { V86 } = await import(BENCH_COLLECT_STATS ? "../../src/main.js" : "../../build/libv86.mjs"); const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const V86_ROOT = path.join(__dirname, "../.."); @@ -56,8 +56,7 @@ emulator.add_listener("serial0-output-byte", function(byte) if(BENCH_COLLECT_STATS) { - const cpu = emulator.v86.cpu; - console.log(stats_to_string(cpu)); + console.log(emulator.get_instruction_stats()); } } }); From 58471e809b2e9cfaf1440e89c67bfb9f934e6514 Mon Sep 17 00:00:00 2001 From: Fabian Date: Wed, 9 Apr 2025 16:25:09 +0700 Subject: [PATCH 039/301] add 2 GB memory test --- tests/api/2g-mem.js | 87 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100755 tests/api/2g-mem.js diff --git a/tests/api/2g-mem.js b/tests/api/2g-mem.js new file mode 100755 index 00000000..1d26f579 --- /dev/null +++ b/tests/api/2g-mem.js @@ -0,0 +1,87 @@ +#!/usr/bin/env node + +import url from "node:url"; +import fs from "node:fs"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + +const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); + +process.on("unhandledRejection", exn => { throw exn; }); + +const config = { + bios: { url: __dirname + "/../../bios/seabios.bin" }, + vga_bios: { url: __dirname + "/../../bios/vgabios.bin" }, + bzimage: { url: __dirname + "/../../images/buildroot-bzimage68.bin" }, + network_relay_url: "", + autostart: true, + memory_size: 2 * 1024 * 1024 * 1024, + filesystem: {}, + log_level: 0, + disable_jit: +process.env.DISABLE_JIT, +}; + +const emulator = new V86(config); + +emulator.bus.register("emulator-started", function() +{ + console.log("Booting now, please stand by"); + + emulator.create_file("test.lua", Buffer.from(` +local t = {} +local m = 1 +while collectgarbage("count") < 1.8 * 1024 * 1024 do + t[m] = string.rep("A", 4096) + m = m + 1 + if m % 10000 == 0 then + print(m, " ", collectgarbage("count")) + end +end +print("memory usage (kB) ", collectgarbage("count")) +print("page count ", m) +local ref = string.rep("A", 4096) +for i = 1, m - 1 do + assert(t[i] == ref) +end +print("ok") +`)); +}); + +let ran_command = false; +let line = ""; + +emulator.add_listener("serial0-output-byte", async function(byte) +{ + const chr = String.fromCharCode(byte); + + if(chr < " " && chr !== "\n" && chr !== "\t" || chr > "~") + { + return; + } + + if(chr === "\n") + { + var new_line = line; + console.error("Serial: %s", line); + line = ""; + } + else if(chr >= " " && chr <= "~") + { + line += chr; + } + + if(!ran_command && line.endsWith("~% ")) + { + ran_command = true; + + emulator.serial0_send("free -m\n"); + emulator.serial0_send("time -v lua /mnt/test.lua\n"); + emulator.serial0_send("echo test fini''shed\n"); + } + + if(chr === "\n" && new_line.startsWith("test finished")) + { + emulator.destroy(); + } +}); From 611bc7d3948dd52962eb57322625cdda1b78e70f Mon Sep 17 00:00:00 2001 From: Fabian Date: Thu, 10 Apr 2025 16:57:15 +0700 Subject: [PATCH 040/301] use newer arch state for benchs --- examples/lang.html | 2 +- tests/benchmark/arch-bytemark.js | 2 +- tests/benchmark/arch-python.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/lang.html b/examples/lang.html index 8404901f..e60a0aae 100644 --- a/examples/lang.html +++ b/examples/lang.html @@ -26,7 +26,7 @@ window.onload = function() url: "../bios/vgabios.bin", }, initial_state: { - url: "../images/arch_state.bin.zst", + url: "../images/arch_state-v2.bin.zst", }, filesystem: { baseurl: "../images/arch/", diff --git a/tests/benchmark/arch-bytemark.js b/tests/benchmark/arch-bytemark.js index 5ffe091a..8f3c73d0 100755 --- a/tests/benchmark/arch-bytemark.js +++ b/tests/benchmark/arch-bytemark.js @@ -19,7 +19,7 @@ const emulator = new V86({ type: "virtio", relay_url: "", }, - initial_state: { url: path.join(V86_ROOT, "/images/arch_state.bin") }, + initial_state: { url: path.join(V86_ROOT, "/images/arch_state-v2.bin.zst") }, filesystem: { baseurl: path.join(V86_ROOT, "/images/arch/") }, disable_jit: +process.env.DISABLE_JIT, log_level: 0, diff --git a/tests/benchmark/arch-python.js b/tests/benchmark/arch-python.js index c6f54c64..11448e90 100755 --- a/tests/benchmark/arch-python.js +++ b/tests/benchmark/arch-python.js @@ -20,7 +20,7 @@ const emulator = new V86({ type: "virtio", relay_url: "", }, - initial_state: { url: path.join(V86_ROOT, "/images/arch_state.bin") }, + initial_state: { url: path.join(V86_ROOT, "/images/arch_state-v2.bin.zst") }, filesystem: { baseurl: path.join(V86_ROOT, "/images/arch/") }, disable_jit: +process.env.DISABLE_JIT, log_level: 0, From 121a5170f2982c399f93c2b2ce636a3ebed0fa99 Mon Sep 17 00:00:00 2001 From: Fabian Date: Thu, 10 Apr 2025 18:36:12 +0700 Subject: [PATCH 041/301] minor refactoring (remove readfile, use direct import for libv86.js) --- examples/nodejs.js | 21 +++++++++------------ examples/nodejs_state.js | 16 ++++------------ tests/kvm-unit-tests/run.mjs | 25 +++---------------------- tools/docker/alpine/build-state.js | 2 +- 4 files changed, 17 insertions(+), 47 deletions(-) diff --git a/examples/nodejs.js b/examples/nodejs.js index 2a7d0fb3..2d0ee4c9 100755 --- a/examples/nodejs.js +++ b/examples/nodejs.js @@ -1,20 +1,12 @@ #!/usr/bin/env node - +import path from "node:path"; import fs from "node:fs"; import url from "node:url"; -import { V86 } from "../build/libv86.js"; +import { V86 } from "../build/libv86.mjs"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); -function readfile(path) -{ - return new Uint8Array(fs.readFileSync(path)).buffer; -} - -var bios = readfile(__dirname + "/../bios/seabios.bin"); -var linux = readfile(__dirname + "/../images/linux4.iso"); - process.stdin.setRawMode(true); process.stdin.resume(); process.stdin.setEncoding("utf8"); @@ -22,9 +14,14 @@ process.stdin.setEncoding("utf8"); console.log("Now booting, please stand by ..."); var emulator = new V86({ - bios: { buffer: bios }, - cdrom: { buffer: linux }, + bios: { url: __dirname + "/../bios/seabios.bin" }, + vga_bios: { url: __dirname + "/../bios/vgabios.bin" }, + cdrom: { url: __dirname + "/../images/linux4.iso" }, autostart: true, + net_device: { + type: "virtio", + relay_url: "fetch", + }, }); emulator.add_listener("serial0-output-byte", function(byte) diff --git a/examples/nodejs_state.js b/examples/nodejs_state.js index 834f3b5b..a11ca4fe 100755 --- a/examples/nodejs_state.js +++ b/examples/nodejs_state.js @@ -1,21 +1,13 @@ #!/usr/bin/env node - import fs from "node:fs"; import url from "node:url"; -var V86 = await import("../build/libv86.js").V86; +import { V86 } from "../build/libv86.mjs"; + const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); -function readfile(path) -{ - return new Uint8Array(fs.readFileSync(path)).buffer; -} - console.log("Use F2 to save the state and F3 to restore."); -var bios = readfile(__dirname + "/../bios/seabios.bin"); -var linux = readfile(__dirname + "/../images/linux4.iso"); - process.stdin.setRawMode(true); process.stdin.resume(); process.stdin.setEncoding("utf8"); @@ -23,8 +15,8 @@ process.stdin.setEncoding("utf8"); console.log("Now booting, please stand by ..."); var emulator = new V86({ - bios: { buffer: bios }, - cdrom: { buffer: linux }, + bios: { url: __dirname + "/../bios/seabios.bin" }, + cdrom: { url: __dirname + "/../images/linux4.iso" }, autostart: true, }); diff --git a/tests/kvm-unit-tests/run.mjs b/tests/kvm-unit-tests/run.mjs index 76ec8970..10bbbe4b 100755 --- a/tests/kvm-unit-tests/run.mjs +++ b/tests/kvm-unit-tests/run.mjs @@ -11,29 +11,10 @@ process.on("unhandledRejection", exn => { throw exn; }); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); -function readfile(path) -{ - return new Uint8Array(fs.readFileSync(path)).buffer; -} - -function Loader(path) -{ - this.buffer = readfile(path); - this.byteLength = this.buffer.byteLength; -} - -Loader.prototype.load = function() -{ - this.onload && this.onload({}); -}; - -var bios = readfile(__dirname + "/../../bios/seabios.bin"); -var vga_bios = readfile(__dirname + "/../../bios/vgabios.bin"); - var emulator = new V86({ - bios: { buffer: bios }, - vga_bios: { buffer: vga_bios }, - multiboot: new Loader(process.argv[2]), + bios: { url: __dirname + "/../../bios/seabios.bin" }, + vga_bios: { url: __dirname + "/../../bios/vgabios.bin" }, + multiboot: { url: process.argv[2] }, autostart: true, memory_size: 64 * 1024 * 1024, disable_jit: +process.env.DISABLE_JIT, diff --git a/tools/docker/alpine/build-state.js b/tools/docker/alpine/build-state.js index 63409fbe..0991552b 100755 --- a/tools/docker/alpine/build-state.js +++ b/tools/docker/alpine/build-state.js @@ -3,10 +3,10 @@ import path from "node:path"; import fs from "node:fs"; import url from "node:url"; +import { V86 } from "../../../build/libv86.mjs"; console.log("Don't forget to run `make all` before running this script"); -const { V86 } = await import("./../../../build/libv86.js"); const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const V86_ROOT = path.join(__dirname, "../../.."); From 73ac9154218faa3f869904e574af77f0ec289a6a Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 14 Apr 2025 17:46:17 +0700 Subject: [PATCH 042/301] defensive programming --- src/browser/fake_network.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/browser/fake_network.js b/src/browser/fake_network.js index 5b39b28b..e358f921 100644 --- a/src/browser/fake_network.js +++ b/src/browser/fake_network.js @@ -94,6 +94,7 @@ class GrowableRingbuffer const total_length = this.length + src_length; let capacity = this.buffer.length; if(capacity < total_length) { + dbg_assert(capacity > 0); while(capacity < total_length) { capacity *= 2; } @@ -221,8 +222,8 @@ function calc_inet_checksum(length, checksum, view, out) if(length & 1) { checksum += eth_frame[uint16_end] << 8; } - while(checksum >> 16) { - checksum = (checksum & 0xffff) + (checksum >> 16); + while(checksum >>> 16) { + checksum = (checksum & 0xffff) + (checksum >>> 16); } return ~checksum & 0xffff; } From 5d928d4cc78f283b256679e4e12d824273aae6bb Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 14 Apr 2025 18:04:20 +0700 Subject: [PATCH 043/301] remove useless @const --- src/acpi.js | 1 - src/apic.js | 18 ++--- src/browser/keyboard.js | 26 ++---- src/browser/mouse.js | 3 +- src/browser/speaker.js | 17 ++-- src/buffer.js | 3 +- src/const.js | 171 +++++++++++++++++----------------------- src/externs.js | 3 +- src/ide.js | 6 +- src/ioapic.js | 18 ----- src/log.js | 7 +- src/main.js | 3 +- src/ne2k.js | 87 ++++++++++---------- src/pci.js | 5 +- src/pit.js | 5 +- src/ps2.js | 1 - src/rtc.js | 78 +++++++++--------- src/sb16.js | 51 ++++++------ src/state.js | 27 ++----- src/uart.js | 44 +++++------ src/vga.js | 1 - 21 files changed, 238 insertions(+), 337 deletions(-) diff --git a/src/acpi.js b/src/acpi.js index f38150d6..436f4057 100644 --- a/src/acpi.js +++ b/src/acpi.js @@ -8,7 +8,6 @@ import { dbg_log, dbg_assert } from "./log.js"; // For Types Only import { CPU } from "./cpu.js"; -/** @const */ const PMTIMER_FREQ_SECONDS = 3579545; /** diff --git a/src/apic.js b/src/apic.js index 40192734..df50de49 100644 --- a/src/apic.js +++ b/src/apic.js @@ -9,29 +9,22 @@ import { IOAPIC_CONFIG_MASKED, IOAPIC_DELIVERY_INIT, IOAPIC_DELIVERY_NMI, IOAPIC // For Types Only import { CPU } from "./cpu.js"; -/** @const */ export const APIC_LOG_VERBOSE = false; // should probably be kept in sync with TSC_RATE in cpu.rs const APIC_TIMER_FREQ = 1 * 1000 * 1000; -/** @const */ -var APIC_ADDRESS = 0xFEE00000; +const APIC_ADDRESS = 0xFEE00000; -/** @const */ -var APIC_TIMER_MODE_MASK = 3 << 17; +const APIC_TIMER_MODE_MASK = 3 << 17; -/** @const */ -var APIC_TIMER_MODE_ONE_SHOT = 0; +const APIC_TIMER_MODE_ONE_SHOT = 0; -/** @const */ -var APIC_TIMER_MODE_PERIODIC = 1 << 17; +const APIC_TIMER_MODE_PERIODIC = 1 << 17; -/** @const */ -var APIC_TIMER_MODE_TSC = 2 << 17; +const APIC_TIMER_MODE_TSC = 2 << 17; -/** @const */ export const DELIVERY_MODES = [ "Fixed (0)", "Lowest Prio (1)", @@ -43,7 +36,6 @@ export const DELIVERY_MODES = [ "ExtINT (7)", ]; -/** @const */ export const DESTINATION_MODES = ["physical", "logical"]; diff --git a/src/browser/keyboard.js b/src/browser/keyboard.js index 0465b68f..c58595e5 100644 --- a/src/browser/keyboard.js +++ b/src/browser/keyboard.js @@ -1,13 +1,9 @@ // For Types Only import { BusConnector } from "../bus.js"; -/** @const */ -var SHIFT_SCAN_CODE = 0x2A; +const SHIFT_SCAN_CODE = 0x2A; +const SCAN_CODE_RELEASE = 0x80; -/** @const */ -var SCAN_CODE_RELEASE = 0x80; - -/** @const */ const PLATFOM_WINDOWS = typeof window !== "undefined" && window.navigator.platform.toString().toLowerCase().search("win") >= 0; /** @@ -49,12 +45,9 @@ export function KeyboardAdapter(bus) */ this.emu_enabled = true; - /** - * Format: - * Javascript event.keyCode -> make code - * @const - */ - var charmap = new Uint16Array([ + // Format: + // Javascript event.keyCode -> make code + const charmap = new Uint16Array([ 0, 0, 0, 0, 0, 0, 0, 0, // 0x08: backspace, tab, enter 0x0E, 0x0F, 0, 0, 0, 0x1C, 0, 0, @@ -129,12 +122,9 @@ export function KeyboardAdapter(bus) ]); - /** - * ascii -> javascript event code (US layout) - * @const - */ - var asciimap = {8: 8, 10: 13, 32: 32, 39: 222, 44: 188, 45: 189, 46: 190, 47: 191, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52, 53: 53, 54: 54, 55: 55, 56: 56, 57: 57, 59: 186, 61: 187, 91: 219, 92: 220, 93: 221, 96: 192, 97: 65, 98: 66, 99: 67, 100: 68, 101: 69, 102: 70, 103: 71, 104: 72, 105: 73, 106: 74, 107: 75, 108: 76, 109: 77, 110: 78, 111: 79, 112: 80, 113: 81, 114: 82, 115: 83, 116: 84, 117: 85, 118: 86, 119: 87, 120: 88, 121: 89, 122: 90}; - var asciimap_shift = {33: 49, 34: 222, 35: 51, 36: 52, 37: 53, 38: 55, 40: 57, 41: 48, 42: 56, 43: 187, 58: 186, 60: 188, 62: 190, 63: 191, 64: 50, 65: 65, 66: 66, 67: 67, 68: 68, 69: 69, 70: 70, 71: 71, 72: 72, 73: 73, 74: 74, 75: 75, 76: 76, 77: 77, 78: 78, 79: 79, 80: 80, 81: 81, 82: 82, 83: 83, 84: 84, 85: 85, 86: 86, 87: 87, 88: 88, 89: 89, 90: 90, 94: 54, 95: 189, 123: 219, 124: 220, 125: 221, 126: 192}; + // ascii -> javascript event code (US layout) + const asciimap = {8: 8, 10: 13, 32: 32, 39: 222, 44: 188, 45: 189, 46: 190, 47: 191, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52, 53: 53, 54: 54, 55: 55, 56: 56, 57: 57, 59: 186, 61: 187, 91: 219, 92: 220, 93: 221, 96: 192, 97: 65, 98: 66, 99: 67, 100: 68, 101: 69, 102: 70, 103: 71, 104: 72, 105: 73, 106: 74, 107: 75, 108: 76, 109: 77, 110: 78, 111: 79, 112: 80, 113: 81, 114: 82, 115: 83, 116: 84, 117: 85, 118: 86, 119: 87, 120: 88, 121: 89, 122: 90}; + const asciimap_shift = {33: 49, 34: 222, 35: 51, 36: 52, 37: 53, 38: 55, 40: 57, 41: 48, 42: 56, 43: 187, 58: 186, 60: 188, 62: 190, 63: 191, 64: 50, 65: 65, 66: 66, 67: 67, 68: 68, 69: 69, 70: 70, 71: 71, 72: 72, 73: 73, 74: 74, 75: 75, 76: 76, 77: 77, 78: 78, 79: 79, 80: 80, 81: 81, 82: 82, 83: 83, 84: 84, 85: 85, 86: 86, 87: 87, 88: 88, 89: 89, 90: 90, 94: 54, 95: 189, 123: 219, 124: 220, 125: 221, 126: 192}; // From: // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code#Code_values_on_Linux_%28X11%29_%28When_scancode_is_available%29 diff --git a/src/browser/mouse.js b/src/browser/mouse.js index 9d43edbd..7147162b 100644 --- a/src/browser/mouse.js +++ b/src/browser/mouse.js @@ -10,8 +10,7 @@ import { BusConnector } from "../bus.js"; */ export function MouseAdapter(bus, screen_container) { - /** @const */ - var SPEED_FACTOR = 0.15; + const SPEED_FACTOR = 0.15; var left_down = false, right_down = false, diff --git a/src/browser/speaker.js b/src/browser/speaker.js index 5a6273dd..42e0a714 100644 --- a/src/browser/speaker.js +++ b/src/browser/speaker.js @@ -11,11 +11,9 @@ import { BusConnector } from "../bus.js"; /* global registerProcessor, sampleRate */ -/** @const */ -var DAC_QUEUE_RESERVE = 0.2; +const DAC_QUEUE_RESERVE = 0.2; -/** @const */ -var AUDIOBUFFER_MINIMUM_SAMPLING_RATE = 8000; +const AUDIOBUFFER_MINIMUM_SAMPLING_RATE = 8000; /** * @constructor @@ -473,14 +471,9 @@ function SpeakerWorkletDAC(bus, audio_context, mixer) function worklet() { - /** @const */ - var RENDER_QUANTUM = 128; - - /** @const */ - var MINIMUM_BUFFER_SIZE = 2 * RENDER_QUANTUM; - - /** @const */ - var QUEUE_RESERVE = 1024; + const RENDER_QUANTUM = 128; + const MINIMUM_BUFFER_SIZE = 2 * RENDER_QUANTUM; + const QUEUE_RESERVE = 1024; function sinc(x) { diff --git a/src/buffer.js b/src/buffer.js index a2b97b9b..189b673c 100644 --- a/src/buffer.js +++ b/src/buffer.js @@ -543,8 +543,7 @@ SyncFileBuffer.prototype.load = function() */ SyncFileBuffer.prototype.load_next = function(start) { - /** @const */ - var PART_SIZE = 4 << 20; + const PART_SIZE = 4 << 20; var filereader = new FileReader(); diff --git a/src/const.js b/src/const.js index f0189fb5..76c570be 100644 --- a/src/const.js +++ b/src/const.js @@ -1,36 +1,35 @@ export const -/** @const */ LOG_ALL = -1, -/** @const */ LOG_NONE = 0, + LOG_ALL = -1, + LOG_NONE = 0, -/** @const */ LOG_OTHER = 0x0000001, -/** @const */ LOG_CPU = 0x0000002, -/** @const */ LOG_FPU = 0x0000004, -/** @const */ LOG_MEM = 0x0000008, -/** @const */ LOG_DMA = 0x0000010, -/** @const */ LOG_IO = 0x0000020, -/** @const */ LOG_PS2 = 0x0000040, -/** @const */ LOG_PIC = 0x0000080, -/** @const */ LOG_VGA = 0x0000100, -/** @const */ LOG_PIT = 0x0000200, -/** @const */ LOG_MOUSE = 0x0000400, -/** @const */ LOG_PCI = 0x0000800, -/** @const */ LOG_BIOS = 0x0001000, -/** @const */ LOG_FLOPPY = 0x0002000, -/** @const */ LOG_SERIAL = 0x0004000, -/** @const */ LOG_DISK = 0x0008000, -/** @const */ LOG_RTC = 0x0010000, -// unused 0x0020000, -/** @const */ LOG_ACPI = 0x0040000, -/** @const */ LOG_APIC = 0x0080000, -/** @const */ LOG_NET = 0x0100000, -/** @const */ LOG_VIRTIO = 0x0200000, -/** @const */ LOG_9P = 0x0400000, -/** @const */ LOG_SB16 = 0x0800000, -/** @const */ LOG_FETCH = 0x1000000; + LOG_OTHER = 0x0000001, + LOG_CPU = 0x0000002, + LOG_FPU = 0x0000004, + LOG_MEM = 0x0000008, + LOG_DMA = 0x0000010, + LOG_IO = 0x0000020, + LOG_PS2 = 0x0000040, + LOG_PIC = 0x0000080, + LOG_VGA = 0x0000100, + LOG_PIT = 0x0000200, + LOG_MOUSE = 0x0000400, + LOG_PCI = 0x0000800, + LOG_BIOS = 0x0001000, + LOG_FLOPPY = 0x0002000, + LOG_SERIAL = 0x0004000, + LOG_DISK = 0x0008000, + LOG_RTC = 0x0010000, + // unused 0x0020000, + LOG_ACPI = 0x0040000, + LOG_APIC = 0x0080000, + LOG_NET = 0x0100000, + LOG_VIRTIO = 0x0200000, + LOG_9P = 0x0400000, + LOG_SB16 = 0x0800000, + LOG_FETCH = 0x1000000; /** - * @const * @type {Array>} */ export const LOG_NAMES = [ @@ -61,104 +60,80 @@ export const LOG_NAMES = [ ]; export const + // flags register bitflags + FLAG_CARRY = 1, + FLAG_PARITY = 4, + FLAG_ADJUST = 16, + FLAG_ZERO = 64, + FLAG_SIGN = 128, + FLAG_TRAP = 256, + FLAG_INTERRUPT = 512, + FLAG_DIRECTION = 1024, + FLAG_OVERFLOW = 2048, + FLAG_IOPL = 1 << 12 | 1 << 13, + FLAG_NT = 1 << 14, + FLAG_RF = 1 << 16, + FLAG_VM = 1 << 17, + FLAG_AC = 1 << 18, + FLAG_VIF = 1 << 19, + FLAG_VIP = 1 << 20, + FLAG_ID = 1 << 21, -// flags register bitflags -/** @const */ FLAG_CARRY = 1, -/** @const */ FLAG_PARITY = 4, -/** @const */ FLAG_ADJUST = 16, -/** @const */ FLAG_ZERO = 64, -/** @const */ FLAG_SIGN = 128, -/** @const */ FLAG_TRAP = 256, -/** @const */ FLAG_INTERRUPT = 512, -/** @const */ FLAG_DIRECTION = 1024, -/** @const */ FLAG_OVERFLOW = 2048, -/** @const */ FLAG_IOPL = 1 << 12 | 1 << 13, -/** @const */ FLAG_NT = 1 << 14, -/** @const */ FLAG_RF = 1 << 16, -/** @const */ FLAG_VM = 1 << 17, -/** @const */ FLAG_AC = 1 << 18, -/** @const */ FLAG_VIF = 1 << 19, -/** @const */ FLAG_VIP = 1 << 20, -/** @const */ FLAG_ID = 1 << 21, + // default values of reserved flags bits + FLAGS_DEFAULT = 1 << 1, -/** - * default values of reserved flags bits - * @const - */ -FLAGS_DEFAULT = 1 << 1, + REG_EAX = 0, + REG_ECX = 1, + REG_EDX = 2, + REG_EBX = 3, + REG_ESP = 4, + REG_EBP = 5, + REG_ESI = 6, + REG_EDI = 7, + REG_ES = 0, + REG_CS = 1, + REG_SS = 2, + REG_DS = 3, + REG_FS = 4, + REG_GS = 5, -/** @const */ REG_EAX = 0, -/** @const */ REG_ECX = 1, -/** @const */ REG_EDX = 2, -/** @const */ REG_EBX = 3, -/** @const */ REG_ESP = 4, -/** @const */ REG_EBP = 5, -/** @const */ REG_ESI = 6, -/** @const */ REG_EDI = 7, - -/** @const */ REG_ES = 0, -/** @const */ REG_CS = 1, -/** @const */ REG_SS = 2, -/** @const */ REG_DS = 3, -/** @const */ REG_FS = 4, -/** @const */ REG_GS = 5, - -/** @const */ REG_LDTR = 7; // local descriptor table register + REG_LDTR = 7; // local descriptor table register export const - /** - * The minimum number of bytes that can be memory-mapped - * by one device. - * - * @const - */ + // The minimum number of bytes that can be memory-mapped by one device. MMAP_BLOCK_BITS = 17, - /** @const */ MMAP_BLOCK_SIZE = 1 << MMAP_BLOCK_BITS, - /** @const */ MMAP_MAX = 0x100000000; -/** @const */ export const CR0_PG = 1 << 31; -/** @const */ export const CR4_PAE = 1 << 5; // https://github.com/qemu/seabios/blob/14221cd86eadba82255fdc55ed174d401c7a0a04/src/fw/paravirt.c#L205-L219 -/** @const */ export const FW_CFG_SIGNATURE = 0x00; -/** @const */ export const FW_CFG_ID = 0x01; -/** @const */ export const FW_CFG_RAM_SIZE = 0x03; -/** @const */ export const FW_CFG_NB_CPUS = 0x05; -/** @const */ export const FW_CFG_MAX_CPUS = 0x0F; -/** @const */ export const FW_CFG_NUMA = 0x0D; -/** @const */ export const FW_CFG_FILE_DIR = 0x19; +export const FW_CFG_SIGNATURE = 0x00; +export const FW_CFG_ID = 0x01; +export const FW_CFG_RAM_SIZE = 0x03; +export const FW_CFG_NB_CPUS = 0x05; +export const FW_CFG_MAX_CPUS = 0x0F; +export const FW_CFG_NUMA = 0x0D; +export const FW_CFG_FILE_DIR = 0x19; -/** @const */ export const FW_CFG_CUSTOM_START = 0x8000; +export const FW_CFG_CUSTOM_START = 0x8000; // This value is specific to v86, choosen to hopefully not collide with other indexes -/** @const */ export const FW_CFG_FILE_START = 0xC000; - -/** @const */ export const FW_CFG_SIGNATURE_QEMU = 0x554D4551; +export const FW_CFG_FILE_START = 0xC000; +export const FW_CFG_SIGNATURE_QEMU = 0x554D4551; // See same constant in jit.rs -/** @const */ export const WASM_TABLE_SIZE = 900; -/** @const */ export const WASM_TABLE_OFFSET = 1024; - -/** @const */ export const MIXER_CHANNEL_LEFT = 0; -/** @const */ export const MIXER_CHANNEL_RIGHT = 1; -/** @const */ export const MIXER_CHANNEL_BOTH = 2; -/** @const */ export const MIXER_SRC_MASTER = 0; -/** @const */ export const MIXER_SRC_PCSPEAKER = 1; -/** @const */ export const MIXER_SRC_DAC = 2; diff --git a/src/externs.js b/src/externs.js index 9bd63a32..68463967 100644 --- a/src/externs.js +++ b/src/externs.js @@ -7,8 +7,7 @@ var process = { hrtime: function() {} }; */ var registerProcessor = function(name, processor) {}; -/** @const */ -var sampleRate = 0; +const sampleRate = 0; var WabtModule = { readWasm: function(buf, opt) {}, diff --git a/src/ide.js b/src/ide.js index 5498ef1c..5210dc0c 100644 --- a/src/ide.js +++ b/src/ide.js @@ -7,10 +7,8 @@ import { CMOS_BIOS_DISKTRANSFLAG, CMOS_DISK_DATA, CMOS_DISK_DRIVE1_CYL } from ". import { CPU } from "./cpu.js"; import { BusConnector } from "./bus.js"; -/** @const */ -var CDROM_SECTOR_SIZE = 2048; -/** @const */ -var HD_SECTOR_SIZE = 512; +const CDROM_SECTOR_SIZE = 2048; +const HD_SECTOR_SIZE = 512; /** * @constructor diff --git a/src/ioapic.js b/src/ioapic.js index 1e622840..6a117dd6 100644 --- a/src/ioapic.js +++ b/src/ioapic.js @@ -9,47 +9,29 @@ import { DELIVERY_MODES, DESTINATION_MODES, APIC_LOG_VERBOSE } from "./apic.js"; import { CPU } from "./cpu.js"; -/** @const */ export const IOAPIC_ADDRESS = 0xFEC00000; -/** @const */ export const IOREGSEL = 0; -/** @const */ export const IOWIN = 0x10; -/** @const */ export const IOAPIC_IRQ_COUNT = 24; -/** @const */ export const IOAPIC_ID = 0; // must match value in seabios - -/** @const */ export const IOAPIC_CONFIG_TRIGGER_MODE_LEVEL = 1 << 15; -/** @const */ export const IOAPIC_CONFIG_MASKED = 1 << 16; -/** @const */ export const IOAPIC_CONFIG_DELIVS = 1 << 12; -/** @const */ export const IOAPIC_CONFIG_REMOTE_IRR = 1 << 14; -/** @const */ export const IOAPIC_CONFIG_READONLY_MASK = IOAPIC_CONFIG_REMOTE_IRR | IOAPIC_CONFIG_DELIVS | 0xFFFE0000; -/** @const */ export const IOAPIC_DELIVERY_FIXED = 0; - -/** @const */ export const IOAPIC_DELIVERY_LOWEST_PRIORITY = 1; - -/** @const */ export const IOAPIC_DELIVERY_NMI = 4; - -/** @const */ export const IOAPIC_DELIVERY_INIT = 5; diff --git a/src/log.js b/src/log.js index c8c60887..b377d5ea 100644 --- a/src/log.js +++ b/src/log.js @@ -36,17 +36,16 @@ function do_the_log(message) /** * @type {function((string|number), number=)} - * @const */ -export var dbg_log = (function() +export const dbg_log = (function() { if(!DEBUG) { return function() {}; } - /** @const @type {Object.} */ - var dbg_names = LOG_NAMES.reduce(function(a, x) + /** @type {Object.} */ + const dbg_names = LOG_NAMES.reduce(function(a, x) { a[x[0]] = x[1]; return a; diff --git a/src/main.js b/src/main.js index d410056b..81cc6103 100644 --- a/src/main.js +++ b/src/main.js @@ -155,8 +155,7 @@ else if(typeof Worker !== "undefined") // // TODO: Make this deactivatable, for other applications // // using postMessage // -// /** @const */ -// let MAGIC_POST_MESSAGE = 0xAA55; +// const MAGIC_POST_MESSAGE = 0xAA55; // // v86.prototype.yield = function(t) // { diff --git a/src/ne2k.js b/src/ne2k.js index 5668cd86..53397505 100644 --- a/src/ne2k.js +++ b/src/ne2k.js @@ -12,55 +12,55 @@ import { BusConnector } from "./bus.js"; const NE2K_LOG_VERBOSE = false; const NE2K_LOG_PACKETS = false; -/** @const */ var E8390_CMD = 0x00; /* The command register (for all pages) */ +const E8390_CMD = 0x00; /* The command register (for all pages) */ /* Page 0 register offsets. */ -/** @const */ var EN0_CLDALO = 0x01; /* Low byte of current local dma addr RD */ -/** @const */ var EN0_STARTPG = 0x01; /* Starting page of ring bfr WR */ -/** @const */ var EN0_CLDAHI = 0x02; /* High byte of current local dma addr RD */ -/** @const */ var EN0_STOPPG = 0x02; /* Ending page +1 of ring bfr WR */ -/** @const */ var EN0_BOUNDARY = 0x03; /* Boundary page of ring bfr RD WR */ -/** @const */ var EN0_TSR = 0x04; /* Transmit status reg RD */ -/** @const */ var EN0_TPSR = 0x04; /* Transmit starting page WR */ -/** @const */ var EN0_NCR = 0x05; /* Number of collision reg RD */ -/** @const */ var EN0_TCNTLO = 0x05; /* Low byte of tx byte count WR */ -/** @const */ var EN0_FIFO = 0x06; /* FIFO RD */ -/** @const */ var EN0_TCNTHI = 0x06; /* High byte of tx byte count WR */ -/** @const */ var EN0_ISR = 0x07; /* Interrupt status reg RD WR */ -/** @const */ var EN0_CRDALO = 0x08; /* low byte of current remote dma address RD */ -/** @const */ var EN0_RSARLO = 0x08; /* Remote start address reg 0 */ -/** @const */ var EN0_CRDAHI = 0x09; /* high byte, current remote dma address RD */ -/** @const */ var EN0_RSARHI = 0x09; /* Remote start address reg 1 */ -/** @const */ var EN0_RCNTLO = 0x0a; /* Remote byte count reg WR */ -/** @const */ var EN0_RCNTHI = 0x0b; /* Remote byte count reg WR */ -/** @const */ var EN0_RSR = 0x0c; /* rx status reg RD */ -/** @const */ var EN0_RXCR = 0x0c; /* RX configuration reg WR */ -/** @const */ var EN0_TXCR = 0x0d; /* TX configuration reg WR */ -/** @const */ var EN0_COUNTER0 = 0x0d; /* Rcv alignment error counter RD */ -/** @const */ var EN0_DCFG = 0x0e; /* Data configuration reg WR */ -/** @const */ var EN0_COUNTER1 = 0x0e; /* Rcv CRC error counter RD */ -/** @const */ var EN0_IMR = 0x0f; /* Interrupt mask reg WR */ -/** @const */ var EN0_COUNTER2 = 0x0f; /* Rcv missed frame error counter RD */ +const EN0_CLDALO = 0x01; /* Low byte of current local dma addr RD */ +const EN0_STARTPG = 0x01; /* Starting page of ring bfr WR */ +const EN0_CLDAHI = 0x02; /* High byte of current local dma addr RD */ +const EN0_STOPPG = 0x02; /* Ending page +1 of ring bfr WR */ +const EN0_BOUNDARY = 0x03; /* Boundary page of ring bfr RD WR */ +const EN0_TSR = 0x04; /* Transmit status reg RD */ +const EN0_TPSR = 0x04; /* Transmit starting page WR */ +const EN0_NCR = 0x05; /* Number of collision reg RD */ +const EN0_TCNTLO = 0x05; /* Low byte of tx byte count WR */ +const EN0_FIFO = 0x06; /* FIFO RD */ +const EN0_TCNTHI = 0x06; /* High byte of tx byte count WR */ +const EN0_ISR = 0x07; /* Interrupt status reg RD WR */ +const EN0_CRDALO = 0x08; /* low byte of current remote dma address RD */ +const EN0_RSARLO = 0x08; /* Remote start address reg 0 */ +const EN0_CRDAHI = 0x09; /* high byte, current remote dma address RD */ +const EN0_RSARHI = 0x09; /* Remote start address reg 1 */ +const EN0_RCNTLO = 0x0a; /* Remote byte count reg WR */ +const EN0_RCNTHI = 0x0b; /* Remote byte count reg WR */ +const EN0_RSR = 0x0c; /* rx status reg RD */ +const EN0_RXCR = 0x0c; /* RX configuration reg WR */ +const EN0_TXCR = 0x0d; /* TX configuration reg WR */ +const EN0_COUNTER0 = 0x0d; /* Rcv alignment error counter RD */ +const EN0_DCFG = 0x0e; /* Data configuration reg WR */ +const EN0_COUNTER1 = 0x0e; /* Rcv CRC error counter RD */ +const EN0_IMR = 0x0f; /* Interrupt mask reg WR */ +const EN0_COUNTER2 = 0x0f; /* Rcv missed frame error counter RD */ -/** @const */ var NE_DATAPORT = 0x10; /* NatSemi-defined port window offset. */ -/** @const */ var NE_RESET = 0x1f; /* Issue a read to reset, a write to clear. */ +const NE_DATAPORT = 0x10; /* NatSemi-defined port window offset. */ +const NE_RESET = 0x1f; /* Issue a read to reset, a write to clear. */ /* Bits in EN0_ISR - Interrupt status register */ -/** @const */ var ENISR_RX = 0x01; /* Receiver, no error */ -/** @const */ var ENISR_TX = 0x02; /* Transmitter, no error */ -/** @const */ var ENISR_RX_ERR = 0x04; /* Receiver, with error */ -/** @const */ var ENISR_TX_ERR = 0x08; /* Transmitter, with error */ -/** @const */ var ENISR_OVER = 0x10; /* Receiver overwrote the ring */ -/** @const */ var ENISR_COUNTERS = 0x20; /* Counters need emptying */ -/** @const */ var ENISR_RDC = 0x40; /* remote dma complete */ -/** @const */ var ENISR_RESET = 0x80; /* Reset completed */ -/** @const */ var ENISR_ALL = 0x3f; /* Interrupts we will enable */ +const ENISR_RX = 0x01; /* Receiver, no error */ +const ENISR_TX = 0x02; /* Transmitter, no error */ +const ENISR_RX_ERR = 0x04; /* Receiver, with error */ +const ENISR_TX_ERR = 0x08; /* Transmitter, with error */ +const ENISR_OVER = 0x10; /* Receiver overwrote the ring */ +const ENISR_COUNTERS = 0x20; /* Counters need emptying */ +const ENISR_RDC = 0x40; /* remote dma complete */ +const ENISR_RESET = 0x80; /* Reset completed */ +const ENISR_ALL = 0x3f; /* Interrupts we will enable */ -/** @const */ var ENRSR_RXOK = 0x01; /* Received a good packet */ +const ENRSR_RXOK = 0x01; /* Received a good packet */ -/** @const */ var START_PAGE = 0x40; -/** @const */ var START_RX_PAGE = 0x40 + 12; -/** @const */ var STOP_PAGE = 0x80; +const START_PAGE = 0x40; +const START_RX_PAGE = 0x40 + 12; +const STOP_PAGE = 0x80; // Search and replace MAC addresses in ethernet, arp and dhcp packets. @@ -318,8 +318,7 @@ export function Ne2k(cpu, bus, preserve_mac_from_state_image, mac_address_transl this.name = "ne2k"; - /** @const */ - var use_pci = true; + const use_pci = true; if(use_pci) { diff --git a/src/pci.js b/src/pci.js index 84d7a671..9cf5e08d 100644 --- a/src/pci.js +++ b/src/pci.js @@ -7,9 +7,8 @@ import { CPU } from "./cpu.js"; // http://wiki.osdev.org/PCI -export const -/** @const */ PCI_CONFIG_ADDRESS = 0xCF8, -/** @const */ PCI_CONFIG_DATA = 0xCFC; +export const PCI_CONFIG_ADDRESS = 0xCF8; +export const PCI_CONFIG_DATA = 0xCFC; /** * @constructor diff --git a/src/pit.js b/src/pit.js index 6d04107b..6c16b110 100644 --- a/src/pit.js +++ b/src/pit.js @@ -7,10 +7,7 @@ import { dbg_log } from "./log.js"; import { CPU } from "./cpu.js"; -/** - * @const - * In kHz - */ +// In kHz export const OSCILLATOR_FREQ = 1193.1816666; // 1.193182 MHz /** diff --git a/src/ps2.js b/src/ps2.js index 2c93e77a..3f7b2efe 100644 --- a/src/ps2.js +++ b/src/ps2.js @@ -7,7 +7,6 @@ import { CPU } from "./cpu.js"; import { BusConnector } from "./bus.js"; import { ByteQueue } from "./lib.js"; -/** @const */ const PS2_LOG_VERBOSE = false; /** diff --git a/src/rtc.js b/src/rtc.js index 147c9abd..a6a8b1d7 100644 --- a/src/rtc.js +++ b/src/rtc.js @@ -8,46 +8,46 @@ import { CPU } from "./cpu.js"; import { DMA } from "./dma.js"; -/** @const */ export const CMOS_RTC_SECONDS = 0x00; -/** @const */ export const CMOS_RTC_SECONDS_ALARM = 0x01; -/** @const */ export const CMOS_RTC_MINUTES = 0x02; -/** @const */ export const CMOS_RTC_MINUTES_ALARM = 0x03; -/** @const */ export const CMOS_RTC_HOURS = 0x04; -/** @const */ export const CMOS_RTC_HOURS_ALARM = 0x05; -/** @const */ export const CMOS_RTC_DAY_WEEK = 0x06; -/** @const */ export const CMOS_RTC_DAY_MONTH = 0x07; -/** @const */ export const CMOS_RTC_MONTH = 0x08; -/** @const */ export const CMOS_RTC_YEAR = 0x09; -/** @const */ export const CMOS_STATUS_A = 0x0a; -/** @const */ export const CMOS_STATUS_B = 0x0b; -/** @const */ export const CMOS_STATUS_C = 0x0c; -/** @const */ export const CMOS_STATUS_D = 0x0d; -/** @const */ export const CMOS_RESET_CODE = 0x0f; +export const CMOS_RTC_SECONDS = 0x00; +export const CMOS_RTC_SECONDS_ALARM = 0x01; +export const CMOS_RTC_MINUTES = 0x02; +export const CMOS_RTC_MINUTES_ALARM = 0x03; +export const CMOS_RTC_HOURS = 0x04; +export const CMOS_RTC_HOURS_ALARM = 0x05; +export const CMOS_RTC_DAY_WEEK = 0x06; +export const CMOS_RTC_DAY_MONTH = 0x07; +export const CMOS_RTC_MONTH = 0x08; +export const CMOS_RTC_YEAR = 0x09; +export const CMOS_STATUS_A = 0x0a; +export const CMOS_STATUS_B = 0x0b; +export const CMOS_STATUS_C = 0x0c; +export const CMOS_STATUS_D = 0x0d; +export const CMOS_RESET_CODE = 0x0f; -/** @const */ export const CMOS_FLOPPY_DRIVE_TYPE = 0x10; -/** @const */ export const CMOS_DISK_DATA = 0x12; -/** @const */ export const CMOS_EQUIPMENT_INFO = 0x14; -/** @const */ export const CMOS_MEM_BASE_LOW = 0x15; -/** @const */ export const CMOS_MEM_BASE_HIGH = 0x16; -/** @const */ export const CMOS_MEM_OLD_EXT_LOW = 0x17; -/** @const */ export const CMOS_MEM_OLD_EXT_HIGH = 0x18; -/** @const */ export const CMOS_DISK_DRIVE1_TYPE = 0x19; -/** @const */ export const CMOS_DISK_DRIVE2_TYPE = 0x1a; -/** @const */ export const CMOS_DISK_DRIVE1_CYL = 0x1b; -/** @const */ export const CMOS_DISK_DRIVE2_CYL = 0x24; -/** @const */ export const CMOS_MEM_EXTMEM_LOW = 0x30; -/** @const */ export const CMOS_MEM_EXTMEM_HIGH = 0x31; -/** @const */ export const CMOS_CENTURY = 0x32; -/** @const */ export const CMOS_MEM_EXTMEM2_LOW = 0x34; -/** @const */ export const CMOS_MEM_EXTMEM2_HIGH = 0x35; -/** @const */ export const CMOS_CENTURY2 = 0x37; -/** @const */ export const CMOS_BIOS_BOOTFLAG1 = 0x38; -/** @const */ export const CMOS_BIOS_DISKTRANSFLAG = 0x39; -/** @const */ export const CMOS_BIOS_BOOTFLAG2 = 0x3d; -/** @const */ export const CMOS_MEM_HIGHMEM_LOW = 0x5b; -/** @const */ export const CMOS_MEM_HIGHMEM_MID = 0x5c; -/** @const */ export const CMOS_MEM_HIGHMEM_HIGH = 0x5d; -/** @const */ export const CMOS_BIOS_SMP_COUNT = 0x5f; +export const CMOS_FLOPPY_DRIVE_TYPE = 0x10; +export const CMOS_DISK_DATA = 0x12; +export const CMOS_EQUIPMENT_INFO = 0x14; +export const CMOS_MEM_BASE_LOW = 0x15; +export const CMOS_MEM_BASE_HIGH = 0x16; +export const CMOS_MEM_OLD_EXT_LOW = 0x17; +export const CMOS_MEM_OLD_EXT_HIGH = 0x18; +export const CMOS_DISK_DRIVE1_TYPE = 0x19; +export const CMOS_DISK_DRIVE2_TYPE = 0x1a; +export const CMOS_DISK_DRIVE1_CYL = 0x1b; +export const CMOS_DISK_DRIVE2_CYL = 0x24; +export const CMOS_MEM_EXTMEM_LOW = 0x30; +export const CMOS_MEM_EXTMEM_HIGH = 0x31; +export const CMOS_CENTURY = 0x32; +export const CMOS_MEM_EXTMEM2_LOW = 0x34; +export const CMOS_MEM_EXTMEM2_HIGH = 0x35; +export const CMOS_CENTURY2 = 0x37; +export const CMOS_BIOS_BOOTFLAG1 = 0x38; +export const CMOS_BIOS_DISKTRANSFLAG = 0x39; +export const CMOS_BIOS_BOOTFLAG2 = 0x3d; +export const CMOS_MEM_HIGHMEM_LOW = 0x5b; +export const CMOS_MEM_HIGHMEM_MID = 0x5c; +export const CMOS_MEM_HIGHMEM_HIGH = 0x5d; +export const CMOS_BIOS_SMP_COUNT = 0x5f; // see CPU.prototype.fill_cmos export const BOOT_ORDER_CD_FIRST = 0x123; diff --git a/src/sb16.js b/src/sb16.js index b8c456c3..19fe9fcc 100644 --- a/src/sb16.js +++ b/src/sb16.js @@ -39,55 +39,54 @@ import { ByteQueue, FloatQueue } from "./lib.js"; // -> https://www.virtualbox.org/svn/vbox/trunk/src/VBox/Devices/Audio/DevSB16.cpp // -> https://github.com/mdaniel/virtualbox-org-svn-vbox-trunk/blob/master/src/VBox/Devices/Audio/DevSB16.cpp -var - +const // Used for drivers to identify device (DSP command 0xE3). -/** @const */ DSP_COPYRIGHT = "COPYRIGHT (C) CREATIVE TECHNOLOGY LTD, 1992.", + DSP_COPYRIGHT = "COPYRIGHT (C) CREATIVE TECHNOLOGY LTD, 1992.", // Value of the current DSP command that indicates that the // next command/data write in port 2xC should be interpreted // as a command number. -/** @const */ DSP_NO_COMMAND = 0, + DSP_NO_COMMAND = 0, // Size (bytes) of the DSP write/read buffers -/** @const */ DSP_BUFSIZE = 64, + DSP_BUFSIZE = 64, // Size (bytes) of the buffers containing floating point linear PCM audio. -/** @const */ DSP_DACSIZE = 65536, + DSP_DACSIZE = 65536, // Size (bytes) of the buffer in which DMA transfers are temporarily // stored before being processed. -/** @const */ SB_DMA_BUFSIZE = 65536, + SB_DMA_BUFSIZE = 65536, // Number of samples to attempt to retrieve per transfer. -/** @const */ SB_DMA_BLOCK_SAMPLES = 1024, + SB_DMA_BLOCK_SAMPLES = 1024, // Usable DMA channels. -/** @const */ SB_DMA0 = 0, -/** @const */ SB_DMA1 = 1, -/** @const */ SB_DMA3 = 3, -/** @const */ SB_DMA5 = 5, -/** @const */ SB_DMA6 = 6, -/** @const */ SB_DMA7 = 7, + SB_DMA0 = 0, + SB_DMA1 = 1, + SB_DMA3 = 3, + SB_DMA5 = 5, + SB_DMA6 = 6, + SB_DMA7 = 7, // Default DMA channels. -/** @const */ SB_DMA_CHANNEL_8BIT = SB_DMA1, -/** @const */ SB_DMA_CHANNEL_16BIT = SB_DMA5, + SB_DMA_CHANNEL_8BIT = SB_DMA1, + SB_DMA_CHANNEL_16BIT = SB_DMA5, // Usable IRQ channels. -/** @const */ SB_IRQ2 = 2, -/** @const */ SB_IRQ5 = 5, -/** @const */ SB_IRQ7 = 7, -/** @const */ SB_IRQ10 = 10, + SB_IRQ2 = 2, + SB_IRQ5 = 5, + SB_IRQ7 = 7, + SB_IRQ10 = 10, // Default IRQ channel. -/** @const */ SB_IRQ = SB_IRQ5, + SB_IRQ = SB_IRQ5, // Indices to the irq_triggered register. -/** @const */ SB_IRQ_8BIT = 0x1, -/** @const */ SB_IRQ_16BIT = 0x2, -/** @const */ SB_IRQ_MIDI = 0x1, -/** @const */ SB_IRQ_MPU = 0x4; + SB_IRQ_8BIT = 0x1, + SB_IRQ_16BIT = 0x2, + SB_IRQ_MIDI = 0x1, + SB_IRQ_MPU = 0x4; // Probably less efficient, but it's more maintainable, instead @@ -1540,7 +1539,7 @@ function between(start, end) return a; } -/** @const */ var SB_FM_OPERATORS_BY_OFFSET = new Uint8Array(32); +const SB_FM_OPERATORS_BY_OFFSET = new Uint8Array(32); SB_FM_OPERATORS_BY_OFFSET[0x00] = 0; SB_FM_OPERATORS_BY_OFFSET[0x01] = 1; SB_FM_OPERATORS_BY_OFFSET[0x02] = 2; diff --git a/src/state.js b/src/state.js index da9142c1..e34aaa6f 100644 --- a/src/state.js +++ b/src/state.js @@ -2,26 +2,13 @@ import { h } from "./lib.js"; import { dbg_assert, dbg_log } from "./log.js"; import { CPU } from "./cpu.js"; -/** @const */ -var STATE_VERSION = 6; - -/** @const */ -var STATE_MAGIC = 0x86768676|0; - -/** @const */ -var STATE_INDEX_MAGIC = 0; - -/** @const */ -var STATE_INDEX_VERSION = 1; - -/** @const */ -var STATE_INDEX_TOTAL_LEN = 2; - -/** @const */ -var STATE_INDEX_INFO_LEN = 3; - -/** @const */ -var STATE_INFO_BLOCK_START = 16; +const STATE_VERSION = 6; +const STATE_MAGIC = 0x86768676|0; +const STATE_INDEX_MAGIC = 0; +const STATE_INDEX_VERSION = 1; +const STATE_INDEX_TOTAL_LEN = 2; +const STATE_INDEX_INFO_LEN = 3; +const STATE_INFO_BLOCK_START = 16; const ZSTD_MAGIC = 0xFD2FB528; diff --git a/src/uart.js b/src/uart.js index 01ee766a..c1f47aa9 100644 --- a/src/uart.js +++ b/src/uart.js @@ -13,35 +13,33 @@ import { BusConnector } from "./bus.js"; * https://www.freebsd.org/doc/en/articles/serial-uart/ */ -/** @const */ -var DLAB = 0x80; +const DLAB = 0x80; +const UART_IER_MSI = 0x08; /* Modem Status Changed int. */ +const UART_IER_THRI = 0x02; /* Enable Transmitter holding register int. */ +const UART_IER_RDI = 0x01; /* Enable receiver data interrupt */ -/** @const */ var UART_IER_MSI = 0x08; /* Modem Status Changed int. */ -/** @const */ var UART_IER_THRI = 0x02; /* Enable Transmitter holding register int. */ -/** @const */ var UART_IER_RDI = 0x01; /* Enable receiver data interrupt */ +const UART_IIR_MSI = 0x00; /* Modem status interrupt (Low priority) */ +const UART_IIR_NO_INT = 0x01; +const UART_IIR_THRI = 0x02; /* Transmitter holding register empty */ +const UART_IIR_RDI = 0x04; /* Receiver data interrupt */ +const UART_IIR_RLSI = 0x06; /* Receiver line status interrupt (High p.) */ +const UART_IIR_CTI = 0x0c; /* Character timeout */ -/** @const */var UART_IIR_MSI = 0x00; /* Modem status interrupt (Low priority) */ -/** @const */var UART_IIR_NO_INT = 0x01; -/** @const */var UART_IIR_THRI = 0x02; /* Transmitter holding register empty */ -/** @const */var UART_IIR_RDI = 0x04; /* Receiver data interrupt */ -/** @const */var UART_IIR_RLSI = 0x06; /* Receiver line status interrupt (High p.) */ -/** @const */var UART_IIR_CTI = 0x0c; /* Character timeout */ - -/** @const */ var UART_LSR_DATA_READY = 0x1; // data available -/** @const */ var UART_LSR_TX_EMPTY = 0x20; // TX (THR) buffer is empty -/** @const */ var UART_LSR_TRANSMITTER_EMPTY = 0x40; // TX empty and line is idle +const UART_LSR_DATA_READY = 0x1; // data available +const UART_LSR_TX_EMPTY = 0x20; // TX (THR) buffer is empty +const UART_LSR_TRANSMITTER_EMPTY = 0x40; // TX empty and line is idle // Modem status register -/** @const */ var UART_MSR_DCD = 0x7; // Data Carrier Detect -/** @const */ var UART_MSR_RI = 0x6; // Ring Indicator -/** @const */ var UART_MSR_DSR = 0x5; // Data Set Ready -/** @const */ var UART_MSR_CTS = 0x4; // Clear To Send +const UART_MSR_DCD = 0x7; // Data Carrier Detect +const UART_MSR_RI = 0x6; // Ring Indicator +const UART_MSR_DSR = 0x5; // Data Set Ready +const UART_MSR_CTS = 0x4; // Clear To Send // Delta bits -/** @const */ var UART_MSR_DDCD = 0x3; // Delta DCD -/** @const */ var UART_MSR_TERI = 0x2; // Trailing Edge RI -/** @const */ var UART_MSR_DDSR = 0x1; // Delta DSR -/** @const */ var UART_MSR_DCTS = 0x0; // Delta CTS +const UART_MSR_DDCD = 0x3; // Delta DCD +const UART_MSR_TERI = 0x2; // Trailing Edge RI +const UART_MSR_DDSR = 0x1; // Delta DSR +const UART_MSR_DCTS = 0x0; // Delta CTS /** diff --git a/src/vga.js b/src/vga.js index 07b78bb0..7cb0e7ec 100644 --- a/src/vga.js +++ b/src/vga.js @@ -43,7 +43,6 @@ const VGA_HOST_MEMORY_SPACE_START = Uint32Array.from([ ]); /** - * @const * @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm#06} */ const VGA_HOST_MEMORY_SPACE_SIZE = Uint32Array.from([ From a3840e32b30d7005f2e7533a557b769333127425 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sat, 26 Apr 2025 20:36:15 +0200 Subject: [PATCH 044/301] distinguish 8-, 4- and 1-bit color modes for non-SVGA graphics modes --- src/vga.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vga.js b/src/vga.js index 7cb0e7ec..78bdb50d 100644 --- a/src/vga.js +++ b/src/vga.js @@ -1218,12 +1218,18 @@ VGAScreen.prototype.update_vga_size = function() // should always 8 pixels per character clock (except for 8 bit PEL width, in which // case 4 pixels). var virtual_width = this.offset_register << 4; + var bpp = 4; // Pixel Width / PEL Width / Clock Select if(this.attribute_mode & 0x40) { screen_width >>>= 1; virtual_width >>>= 1; + bpp = 8; + } + else if(this.attribute_mode & 0x2) + { + bpp = 1; } var screen_height = this.scan_line_to_screen_row(vertical_scans); @@ -1239,7 +1245,7 @@ VGAScreen.prototype.update_vga_size = function() const bytes_per_line = this.vga_bytes_per_line(); const virtual_height = bytes_per_line ? Math.ceil(available_bytes / bytes_per_line) : screen_height; - this.set_size_graphical(screen_width, screen_height, virtual_width, virtual_height, 8); + this.set_size_graphical(screen_width, screen_height, virtual_width, virtual_height, bpp); this.update_vertical_retrace(); this.update_layers(); From 22ef9ba14ba712979cde4bad976099f71f67b09a Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Mon, 7 Apr 2025 18:04:23 +0300 Subject: [PATCH 045/301] FetchNetworkAdapter.form_response_head() method --- src/browser/fetch_network.js | 59 ++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/src/browser/fetch_network.js b/src/browser/fetch_network.js index 0526c618..9289de6d 100644 --- a/src/browser/fetch_network.js +++ b/src/browser/fetch_network.js @@ -110,7 +110,14 @@ async function on_data_http(data) const header = this.net.parse_http_header(headers[i]); if(!header) { console.warn('The request contains an invalid header: "%s"', headers[i]); - this.write(new TextEncoder().encode("HTTP/1.1 400 Bad Request\r\nContent-Length: 0")); + const body = new TextEncoder().encode(`Invalid header in request: ${headers[i]}`); + const resp_headers = new Headers({ + "content-type": "text/plain", + "content-length": body.length.toString(10), + "connection": "close" + }); + this.writev([new TextEncoder().encode(this.net.form_response_head(400, "Bad Request", resp_headers)), body]); + this.close(); return; } if( header.key.toLowerCase() === "host" ) target.host = header.value; @@ -131,18 +138,16 @@ async function on_data_http(data) const encoder = new TextEncoder(); let response_started = false; this.net.fetch(fetch_url, opts).then((resp) => { - const header_lines = [ - `HTTP/1.1 ${resp.status} ${resp.statusText}`, - `x-was-fetch-redirected: ${!!resp.redirected}`, - `x-fetch-resp-url: ${resp.url}`, - "Connection: closed" - ]; - for(const [key, value] of resp.headers.entries()) { - if(!["content-encoding", "connection", "content-length", "transfer-encoding"].includes(key.toLowerCase())) { - header_lines.push(`${key}: ${value}`); + let resp_headers = new Headers(resp.headers); + resp_headers.set("x-was-fetch-redirected", `${!!resp.redirected}`); + resp_headers.set("x-fetch-resp-url", resp.url); + resp_headers.set("connection", "closed"); + ["content-encoding", "connection", "content-length", "transfer-encoding"].forEach(function(header) { + if(resp_headers.has(header)) { + resp_headers.delete(header); } - } - this.write(encoder.encode(header_lines.join("\r\n") + "\r\n\r\n")); + }); + this.write(encoder.encode(this.net.form_response_head(resp.status, resp.statusText, resp_headers))); response_started = true; if(resp.body && resp.body.getReader) { @@ -170,13 +175,12 @@ async function on_data_http(data) console.warn("Fetch Failed: " + fetch_url + "\n" + e); if(!response_started) { const body = encoder.encode(`Fetch ${fetch_url} failed:\n\n${e.stack || e.message}`); - const header_lines = [ - "HTTP/1.1 502 Fetch Error", - "Content-Type: text/plain", - `Content-Length: ${body.length}`, - "Connection: closed" - ]; - this.writev([encoder.encode(header_lines.join("\r\n") + "\r\n\r\n"), body]); + const resp_headers = new Headers({ + "Content-Type": "text/plain", + "Content-Length": body.length.toString(10), + "Connection": "closed" + }); + this.writev([encoder.encode(this.net.form_response_head(502, "Fetch Error", resp_headers)), body]); } this.close(); }); @@ -196,19 +200,30 @@ FetchNetworkAdapter.prototype.fetch = async function(url, options) catch(e) { console.warn("Fetch Failed: " + url + "\n" + e); - let headers = new Headers(); - headers.set("Content-Type", "text/plain"); return [ { status: 502, statusText: "Fetch Error", - headers: headers, + headers: new Headers({ "Content-Type": "text/plain" }), }, new TextEncoder().encode(`Fetch ${url} failed:\n\n${e.stack}`).buffer ]; } }; +FetchNetworkAdapter.prototype.form_response_head = function(status_code, status_text, headers) +{ + let lines = [ + `HTTP/1.1 ${status_code} ${status_text}` + ]; + + headers.forEach(function(value, key) { + lines.push(`${key}: ${value}`); + }); + + return lines.join("\r\n") + "\r\n\r\n"; +}; + FetchNetworkAdapter.prototype.parse_http_header = function(header) { const parts = header.match(/^([^:]*):(.*)$/); From 9a918b37492b91cecd12ecdbfc37c30c5c51c791 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Mon, 7 Apr 2025 18:23:48 +0300 Subject: [PATCH 046/301] fix "connection: closed" header --- src/browser/fetch_network.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/fetch_network.js b/src/browser/fetch_network.js index 9289de6d..d3f53716 100644 --- a/src/browser/fetch_network.js +++ b/src/browser/fetch_network.js @@ -142,7 +142,7 @@ async function on_data_http(data) resp_headers.set("x-was-fetch-redirected", `${!!resp.redirected}`); resp_headers.set("x-fetch-resp-url", resp.url); resp_headers.set("connection", "closed"); - ["content-encoding", "connection", "content-length", "transfer-encoding"].forEach(function(header) { + ["content-encoding", "content-length", "transfer-encoding"].forEach(function(header) { if(resp_headers.has(header)) { resp_headers.delete(header); } From 577b464aa4887995c0ee4c50288e889c5342f04b Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Mon, 7 Apr 2025 20:34:13 +0300 Subject: [PATCH 047/301] allow access from vm to localhost --- src/browser/fetch_network.js | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/browser/fetch_network.js b/src/browser/fetch_network.js index d3f53716..5d8109d9 100644 --- a/src/browser/fetch_network.js +++ b/src/browser/fetch_network.js @@ -39,6 +39,8 @@ export function FetchNetworkAdapter(bus, config) // Ex: 'https://corsproxy.io/?' this.cors_proxy = config.cors_proxy; + this.local_http = !!config.local_http; + this.bus.register("net" + this.id + "-mac", function(mac) { this.vm_mac = new Uint8Array(mac.split(":").map(function(x) { return parseInt(x, 16); })); }, this); @@ -124,6 +126,27 @@ async function on_data_http(data) else req_headers.append(header.key, header.value); } + if(this.net.local_http && !this.net.cors_proxy && (/\d+\.v86local\.http/).test(target.hostname)) { + dbg_log("Request to localhost: " + target.href, LOG_FETCH); + const localport = parseInt(target.hostname.split(".")[0], 10); + if(!isNaN(localport) && localport > 0 && localport < 65536) { + target.protocol = "http:"; + target.hostname = "localhost"; + target.port = localport.toString(10); + } else { + console.warn('Unknown port for localhost: "%s"', target.href); + const body = new TextEncoder().encode(`Unknown port for localhost: ${target.href}`); + const resp_headers = new Headers({ + "content-type": "text/plain", + "content-length": body.length.toString(10), + "connection": "close" + }); + this.writev([new TextEncoder().encode(this.net.form_response_head(400, "Bad Request", resp_headers)), body]); + this.close(); + return; + } + } + dbg_log("HTTP Dispatch: " + target.href, LOG_FETCH); this.name = target.href; let opts = { @@ -141,7 +164,7 @@ async function on_data_http(data) let resp_headers = new Headers(resp.headers); resp_headers.set("x-was-fetch-redirected", `${!!resp.redirected}`); resp_headers.set("x-fetch-resp-url", resp.url); - resp_headers.set("connection", "closed"); + resp_headers.set("connection", "close"); ["content-encoding", "content-length", "transfer-encoding"].forEach(function(header) { if(resp_headers.has(header)) { resp_headers.delete(header); @@ -178,7 +201,7 @@ async function on_data_http(data) const resp_headers = new Headers({ "Content-Type": "text/plain", "Content-Length": body.length.toString(10), - "Connection": "closed" + "Connection": "close" }); this.writev([encoder.encode(this.net.form_response_head(502, "Fetch Error", resp_headers)), body]); } From 6ddb163284f36f8e0aa9b07add3615a8056a47c9 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Mon, 7 Apr 2025 20:49:55 +0300 Subject: [PATCH 048/301] test with local server --- tests/devices/fetch_network.js | 56 ++++++++++++++++++++++++++++--- tests/devices/fetch_testserver.js | 45 +++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 tests/devices/fetch_testserver.js diff --git a/tests/devices/fetch_network.js b/tests/devices/fetch_network.js index 96111763..525468c9 100755 --- a/tests/devices/fetch_network.js +++ b/tests/devices/fetch_network.js @@ -2,16 +2,19 @@ import assert from "assert/strict"; import url from "node:url"; +import { Worker } from "node:worker_threads"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); process.on("unhandledRejection", exn => { throw exn; }); const USE_VIRTIO = !!process.env.USE_VIRTIO; +const SERVER_PORT = parseInt(process.env.SERVER_PORT, 10) || 8686; const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); const SHOW_LOGS = false; +const server = new Worker(__dirname + "fetch_testserver.js", { workerData: { port: SERVER_PORT, benchsize: 0 } }); function wait(time) { return new Promise((res) => setTimeout(res, time)); @@ -134,7 +137,7 @@ const tests = }, }, { - name: "Curl mocked.example.org", + name: "GET mocked.example.org", allow_failure: true, start: () => { @@ -148,7 +151,7 @@ const tests = }, }, { - name: "Curl example.org", + name: "GET example.org", allow_failure: true, start: () => { @@ -161,6 +164,49 @@ const tests = assert(/This domain is for use in illustrative examples in documents/.test(capture), "got example.org text"); }, }, + { + name: "GET local server", + allow_failure: true, + start: () => + { + emulator.serial0_send(`wget -T 10 -O - ${SERVER_PORT}.v86local.http\n`); + emulator.serial0_send("echo -e done\\\\tlocal server\n"); + }, + end_trigger: "done\tlocal server", + end: (capture) => + { + assert(/This text is from the local server/.test(capture), "got local server text"); + }, + }, + { + name: "GET local server with custom header", + allow_failure: true, + start: () => + { + emulator.serial0_send(`wget -S -T 10 --header='x-client-test: hello' -O - ${SERVER_PORT}.v86local.http/header\n`); + emulator.serial0_send("echo -e done\\\\tlocal server custom header\n"); + }, + end_trigger: "done\tlocal server custom header", + end: (capture) => + { + assert(/x-server-test: {1,}h_e_l_l_o/.test(capture), "got local server custom header"); + }, + }, + { + name: "GET local server with redirect", + allow_failure: true, + start: () => + { + emulator.serial0_send(`curl -m 10 -L -v ${SERVER_PORT}.v86local.http/redirect\n`); + emulator.serial0_send("echo -e done\\\\tlocal server redirect\n"); + }, + end_trigger: "done\tlocal server redirect", + end: (capture) => + { + assert(/x-was-fetch-redirected: {1,}true/.test(capture), "got local server redirect header"); + assert(/This text is from the local server/.test(capture), "got actual redirect"); + }, + }, { name: "Forbidden character in header name", start: () => @@ -191,7 +237,7 @@ const tests = name: "Header without separator", start: () => { - emulator.serial0_send("wget --spider --header='testheader' -T 10 -O - test.domain\n"); + emulator.serial0_send("wget --header='testheader' -T 10 -O - test.domain\n"); emulator.serial0_send("echo -e done\\\\theader without colon\n"); }, end_trigger: "done\theader without colon", @@ -212,6 +258,7 @@ const emulator = new V86({ net_device: { relay_url: "fetch", type: USE_VIRTIO ? "virtio" : "ne2k", + local_http: true }, log_level: SHOW_LOGS ? 0x400000 : 0, }); @@ -289,6 +336,7 @@ emulator.add_listener("serial0-output-byte", function(byte) else { console.error("[-] Test #%d failed: %s", test_num, tests[test_num].name); + server.terminate(); process.exit(1); } } @@ -304,7 +352,7 @@ emulator.add_listener("serial0-output-byte", function(byte) if(test_num >= tests.length) { emulator.destroy(); - + server.terminate(); console.log("Tests finished."); } else diff --git a/tests/devices/fetch_testserver.js b/tests/devices/fetch_testserver.js new file mode 100644 index 00000000..52345642 --- /dev/null +++ b/tests/devices/fetch_testserver.js @@ -0,0 +1,45 @@ +"use strict"; + +/** + * Endpoints: + * GET / - same as mocked.example.org + * GET /bench - large file for benchmark (tests/bench/fetch_download.js) + * GET /redirect - redirect to root endpoint + * GET /header - gets "x-client-test" header and returns "x-server-test" header + * + * todo: POST endpoints with JSON +*/ + +import { createServer } from "node:http"; +import { workerData } from "node:worker_threads"; + +const { port, benchsize } = workerData; +const benchfile = Buffer.alloc(benchsize); + +function get_handler(request, response) { + if(request.url === "/") { + response.write("This text is from the local server"); + } else if(request.url === "/bench") { + response.setHeader("content-type", "application/octet-stream"); + response.setHeader("content-length", benchsize.toString(10)); + response.write(benchfile); + } else if(request.url === "/header") { + response.setHeader("x-server-test", request.headers["x-client-test"].split("").join("_") || "none"); + } else if(request.url === "/redirect") { + response.writeHead(307, { "location": "/" }); + } else { + response.write("Unknown endpoint"); + } +} + +createServer(function(request, response) { + switch(request.method) { + case "GET": + get_handler(request, response); + break; + default: + response.write("Unknown method"); + break; + } + response.end(); +}).listen(port); From b0b5fbee1291a98f4f905cdf004f817f1ef62f43 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Mon, 7 Apr 2025 20:52:33 +0300 Subject: [PATCH 049/301] fix eslint --- tests/devices/fetch_testserver.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/devices/fetch_testserver.js b/tests/devices/fetch_testserver.js index 52345642..346a806c 100644 --- a/tests/devices/fetch_testserver.js +++ b/tests/devices/fetch_testserver.js @@ -1,12 +1,11 @@ "use strict"; -/** +/** * Endpoints: * GET / - same as mocked.example.org * GET /bench - large file for benchmark (tests/bench/fetch_download.js) * GET /redirect - redirect to root endpoint * GET /header - gets "x-client-test" header and returns "x-server-test" header - * * todo: POST endpoints with JSON */ From 5e16fa3d7b2fa09d7c04bcb658b2bbd1ae697442 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:45:54 +0300 Subject: [PATCH 050/301] docs about `local_http` --- docs/networking.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/networking.md b/docs/networking.md index 0bb0a7ba..ed4f0408 100644 --- a/docs/networking.md +++ b/docs/networking.md @@ -62,6 +62,7 @@ Backends `fetch` and `wisp` support a couple of special settings in `config.net_ | **dns_method** | str | DNS method to use, either `static` or `doh`. `static`: use built-in DNS server, `doh`: use [DNS-over-HTTPS](https://en.wikipedia.org/wiki/DNS_over_HTTPS) (DoH). Defaults to `static` for `fetch` and to `doh` for `wisp` backend. | | **doh_server** | str | Host name or IP address (and optional port number) of the DoH server if `dns_method` is `doh`. The value is expanded to the URL `https://DOH_SERVER/dns-query`. Default: `cloudflare-dns.com`. | | **cors_proxy** | str | CORS proxy server URL, do not use a proxy if undefined. Default: undefined (`fetch` backend only). | +| **local_http** | bool | Allow HTTP access from the guest to the host's `localhost` using the URL `http://.v86local.http` (e.g. `1234.v86local.http` -> `localhost:1234`). Default: `False` (`fetch` backend only). | #### Example `net_device` settings From c8391e5347d0af10e517aa1ba1c73f7d03def42a Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:59:49 +0300 Subject: [PATCH 051/301] fetch benchmark --- tests/benchmark/fetch-download.js | 74 +++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100755 tests/benchmark/fetch-download.js diff --git a/tests/benchmark/fetch-download.js b/tests/benchmark/fetch-download.js new file mode 100755 index 00000000..b9f203be --- /dev/null +++ b/tests/benchmark/fetch-download.js @@ -0,0 +1,74 @@ +#!/usr/bin/env node +"use strict"; + +import url from "node:url"; +import { Worker } from "node:worker_threads"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + +const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; +const USE_VIRTIO = !!process.env.USE_VIRTIO; +const SERVER_PORT = parseInt(process.env.SERVER_PORT, 10) || 8686; +const BENCHFILE_SIZE = (parseInt(process.env.BENCHFILE_SIZE_MB, 10) || 32) * 1024 * 1024; + +const { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); + +const LOG_SERIAL = true; +const SHOW_LOGS = false; + +const server = new Worker(__dirname + "../devices/fetch_testserver.js", { workerData: { port: SERVER_PORT, benchsize: BENCHFILE_SIZE } }); + +const emulator = new V86({ + bios: { url: __dirname + "/../../bios/seabios.bin" }, + vga_bios: { url: __dirname + "/../../bios/vgabios.bin" }, + bzimage: { url: __dirname + "/../../images/buildroot-bzimage68.bin" }, + autostart: true, + memory_size: 64 * 1024 * 1024, + net_device: { + relay_url: "fetch", + type: USE_VIRTIO ? "virtio" : "ne2k", + local_http: true + }, + log_level: SHOW_LOGS ? 0x400000 : 0, +}); + +emulator.bus.register("emulator-started", function() +{ + console.log("Booting now, please stand by"); +}); + +var serial_text = ""; +var booted = false; + +emulator.add_listener("serial0-output-byte", function(byte) +{ + var chr = String.fromCharCode(byte); + + if(LOG_SERIAL) process.stdout.write(chr); + + serial_text += chr; + + if(!booted && serial_text.endsWith("~% ")) + { + booted = true; + + emulator.serial0_send(`udhcpc;curl -o /dev/null -w '<%{speed_download}>%{exitcode}' ${SERVER_PORT}.v86local.http/bench\n`); + } + + if(serial_text.endsWith(">0")) + { + emulator.destroy(); + server.terminate(); + const speed = parseInt(/<([0-9]+)>0/.exec(serial_text)[1], 10); // in bytes + + if(isNaN(speed)) + { + console.error("Can't parse console log"); + process.exit(1); + } + + console.log(); + console.log("Average download speed: %s kB/s", (speed / 1024).toFixed(2)); + process.exit(0); + } +}); From 5f088cc0667329ad1daa9a087a7e9f289c126b26 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Mon, 14 Apr 2025 18:55:57 +0300 Subject: [PATCH 052/301] release build for bench, check non-zero exit codes --- tests/benchmark/fetch-download.js | 35 +++++++++++++++++++++---------- tests/devices/fetch_network.js | 1 + tests/devices/fetch_testserver.js | 4 +++- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/tests/benchmark/fetch-download.js b/tests/benchmark/fetch-download.js index b9f203be..4c77bcc8 100755 --- a/tests/benchmark/fetch-download.js +++ b/tests/benchmark/fetch-download.js @@ -6,17 +6,17 @@ import { Worker } from "node:worker_threads"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); -const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; const USE_VIRTIO = !!process.env.USE_VIRTIO; const SERVER_PORT = parseInt(process.env.SERVER_PORT, 10) || 8686; const BENCHFILE_SIZE = (parseInt(process.env.BENCHFILE_SIZE_MB, 10) || 32) * 1024 * 1024; -const { V86 } = await import(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.mjs`); +const { V86 } = await import("../../build/libv86.mjs"); const LOG_SERIAL = true; const SHOW_LOGS = false; const server = new Worker(__dirname + "../devices/fetch_testserver.js", { workerData: { port: SERVER_PORT, benchsize: BENCHFILE_SIZE } }); +server.onerror = (e) => { throw new Error("server: " + e); }; const emulator = new V86({ bios: { url: __dirname + "/../../bios/seabios.bin" }, @@ -28,8 +28,7 @@ const emulator = new V86({ relay_url: "fetch", type: USE_VIRTIO ? "virtio" : "ne2k", local_http: true - }, - log_level: SHOW_LOGS ? 0x400000 : 0, + } }); emulator.bus.register("emulator-started", function() @@ -52,23 +51,37 @@ emulator.add_listener("serial0-output-byte", function(byte) { booted = true; - emulator.serial0_send(`udhcpc;curl -o /dev/null -w '<%{speed_download}>%{exitcode}' ${SERVER_PORT}.v86local.http/bench\n`); + emulator.serial0_send(`udhcpc;curl --fail --connect-timeout 10 -s -o /dev/null -w '<%{exitcode}><%{speed_download}>\\t' http://${SERVER_PORT}.v86local.http/bench\n`); } - if(serial_text.endsWith(">0")) + if(serial_text.endsWith("\t")) { + console.log(); emulator.destroy(); server.terminate(); - const speed = parseInt(/<([0-9]+)>0/.exec(serial_text)[1], 10); // in bytes - if(isNaN(speed)) + const regex = /<(\d+)><(\d+)>\t/.exec(serial_text); + const exitcode = parseInt(regex[1], 10); + const speed = parseInt(regex[2], 10); // in bytes + + if(isNaN(exitcode)) { - console.error("Can't parse console log"); + console.error("Can't parse exit code"); + process.exit(1); + } + + if(exitcode !== 0) + { + console.error("Bench failed, curl returned non-zero exit code %s", exitcode); + process.exit(exitcode); + } + + if(isNaN(speed)) + { + console.error("Can't parse bench speed"); process.exit(1); } - console.log(); console.log("Average download speed: %s kB/s", (speed / 1024).toFixed(2)); - process.exit(0); } }); diff --git a/tests/devices/fetch_network.js b/tests/devices/fetch_network.js index 525468c9..702db471 100755 --- a/tests/devices/fetch_network.js +++ b/tests/devices/fetch_network.js @@ -15,6 +15,7 @@ const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : ".. const SHOW_LOGS = false; const server = new Worker(__dirname + "fetch_testserver.js", { workerData: { port: SERVER_PORT, benchsize: 0 } }); +server.onerror = (e) => { throw new Error("server: " + e); }; function wait(time) { return new Promise((res) => setTimeout(res, time)); diff --git a/tests/devices/fetch_testserver.js b/tests/devices/fetch_testserver.js index 346a806c..2e27ebdd 100644 --- a/tests/devices/fetch_testserver.js +++ b/tests/devices/fetch_testserver.js @@ -3,7 +3,7 @@ /** * Endpoints: * GET / - same as mocked.example.org - * GET /bench - large file for benchmark (tests/bench/fetch_download.js) + * GET /bench - large file for benchmark (tests/benchmark/fetch-download.js) * GET /redirect - redirect to root endpoint * GET /header - gets "x-client-test" header and returns "x-server-test" header * todo: POST endpoints with JSON @@ -27,6 +27,7 @@ function get_handler(request, response) { } else if(request.url === "/redirect") { response.writeHead(307, { "location": "/" }); } else { + response.writeHead(404); response.write("Unknown endpoint"); } } @@ -37,6 +38,7 @@ createServer(function(request, response) { get_handler(request, response); break; default: + response.writeHead(405); response.write("Unknown method"); break; } From 4fd2478d67beb2dade96bc3df5afa7b5823eff69 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Tue, 15 Apr 2025 00:00:55 +0300 Subject: [PATCH 053/301] fix headers and worker error handling --- src/browser/fetch_network.js | 15 ++++++++------- tests/benchmark/fetch-download.js | 2 +- tests/devices/fetch_network.js | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/browser/fetch_network.js b/src/browser/fetch_network.js index 5d8109d9..06b2e819 100644 --- a/src/browser/fetch_network.js +++ b/src/browser/fetch_network.js @@ -162,14 +162,15 @@ async function on_data_http(data) let response_started = false; this.net.fetch(fetch_url, opts).then((resp) => { let resp_headers = new Headers(resp.headers); + resp_headers.delete("content-encoding"); + resp_headers.delete("keep-alive"); + resp_headers.delete("content-length"); + resp_headers.delete("transfer-encoding"); + resp_headers.set("x-was-fetch-redirected", `${!!resp.redirected}`); resp_headers.set("x-fetch-resp-url", resp.url); resp_headers.set("connection", "close"); - ["content-encoding", "content-length", "transfer-encoding"].forEach(function(header) { - if(resp_headers.has(header)) { - resp_headers.delete(header); - } - }); + this.write(encoder.encode(this.net.form_response_head(resp.status, resp.statusText, resp_headers))); response_started = true; @@ -240,9 +241,9 @@ FetchNetworkAdapter.prototype.form_response_head = function(status_code, status_ `HTTP/1.1 ${status_code} ${status_text}` ]; - headers.forEach(function(value, key) { + for(const [key, value] of headers.entries()) { lines.push(`${key}: ${value}`); - }); + } return lines.join("\r\n") + "\r\n\r\n"; }; diff --git a/tests/benchmark/fetch-download.js b/tests/benchmark/fetch-download.js index 4c77bcc8..ef09f10d 100755 --- a/tests/benchmark/fetch-download.js +++ b/tests/benchmark/fetch-download.js @@ -16,7 +16,7 @@ const LOG_SERIAL = true; const SHOW_LOGS = false; const server = new Worker(__dirname + "../devices/fetch_testserver.js", { workerData: { port: SERVER_PORT, benchsize: BENCHFILE_SIZE } }); -server.onerror = (e) => { throw new Error("server: " + e); }; +server.on("error", (e) => { throw new Error("server: " + e); }); const emulator = new V86({ bios: { url: __dirname + "/../../bios/seabios.bin" }, diff --git a/tests/devices/fetch_network.js b/tests/devices/fetch_network.js index 702db471..72bb87c7 100755 --- a/tests/devices/fetch_network.js +++ b/tests/devices/fetch_network.js @@ -15,7 +15,7 @@ const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : ".. const SHOW_LOGS = false; const server = new Worker(__dirname + "fetch_testserver.js", { workerData: { port: SERVER_PORT, benchsize: 0 } }); -server.onerror = (e) => { throw new Error("server: " + e); }; +server.on("error", (e) => { throw new Error("server: " + e); }); function wait(time) { return new Promise((res) => setTimeout(res, time)); From 8c0fdb9f9d167272b7f87063fcd4dc37d7ac8fc2 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Tue, 15 Apr 2025 00:13:29 +0300 Subject: [PATCH 054/301] FetchNetworkAdapter.respond_text_and_close() method, `form_response_head` returns bytes instead of a string --- src/browser/fetch_network.js | 42 ++++++++++++++---------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/src/browser/fetch_network.js b/src/browser/fetch_network.js index 06b2e819..1561ad54 100644 --- a/src/browser/fetch_network.js +++ b/src/browser/fetch_network.js @@ -112,14 +112,7 @@ async function on_data_http(data) const header = this.net.parse_http_header(headers[i]); if(!header) { console.warn('The request contains an invalid header: "%s"', headers[i]); - const body = new TextEncoder().encode(`Invalid header in request: ${headers[i]}`); - const resp_headers = new Headers({ - "content-type": "text/plain", - "content-length": body.length.toString(10), - "connection": "close" - }); - this.writev([new TextEncoder().encode(this.net.form_response_head(400, "Bad Request", resp_headers)), body]); - this.close(); + this.net.respond_text_and_close(400, "Bad Request", `Invalid header in request: ${headers[i]}`); return; } if( header.key.toLowerCase() === "host" ) target.host = header.value; @@ -135,14 +128,7 @@ async function on_data_http(data) target.port = localport.toString(10); } else { console.warn('Unknown port for localhost: "%s"', target.href); - const body = new TextEncoder().encode(`Unknown port for localhost: ${target.href}`); - const resp_headers = new Headers({ - "content-type": "text/plain", - "content-length": body.length.toString(10), - "connection": "close" - }); - this.writev([new TextEncoder().encode(this.net.form_response_head(400, "Bad Request", resp_headers)), body]); - this.close(); + this.net.respond_text_and_close(400, "Bad Request", `Unknown port for localhost: ${target.href}`); return; } } @@ -166,12 +152,11 @@ async function on_data_http(data) resp_headers.delete("keep-alive"); resp_headers.delete("content-length"); resp_headers.delete("transfer-encoding"); - resp_headers.set("x-was-fetch-redirected", `${!!resp.redirected}`); resp_headers.set("x-fetch-resp-url", resp.url); resp_headers.set("connection", "close"); - this.write(encoder.encode(this.net.form_response_head(resp.status, resp.statusText, resp_headers))); + this.write(this.net.form_response_head(resp.status, resp.statusText, resp_headers)); response_started = true; if(resp.body && resp.body.getReader) { @@ -198,13 +183,7 @@ async function on_data_http(data) .catch((e) => { console.warn("Fetch Failed: " + fetch_url + "\n" + e); if(!response_started) { - const body = encoder.encode(`Fetch ${fetch_url} failed:\n\n${e.stack || e.message}`); - const resp_headers = new Headers({ - "Content-Type": "text/plain", - "Content-Length": body.length.toString(10), - "Connection": "close" - }); - this.writev([encoder.encode(this.net.form_response_head(502, "Fetch Error", resp_headers)), body]); + this.net.respond_text_and_close(502, "Fetch Error", `Fetch ${fetch_url} failed:\n\n${e.stack || e.message}`); } this.close(); }); @@ -245,7 +224,18 @@ FetchNetworkAdapter.prototype.form_response_head = function(status_code, status_ lines.push(`${key}: ${value}`); } - return lines.join("\r\n") + "\r\n\r\n"; + return new TextEncoder().encode(lines.join("\r\n") + "\r\n\r\n"); +}; + +FetchNetworkAdapter.prototype.respond_text_and_close = function(status_code, status_text, body) +{ + const headers = new Headers({ + "content-type": "text/plain", + "content-length": body.length.toString(10), + "connection": "close" + }); + this.write([this.net.form_response_head(status_code, status_text, headers), new TextEncoder().encode(body)]); + this.close(); }; FetchNetworkAdapter.prototype.parse_http_header = function(header) From 5dc5cd249b0febe4f8815ea921a2854b2aaf82e4 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Tue, 15 Apr 2025 00:37:22 +0300 Subject: [PATCH 055/301] fix closure compiler --- src/browser/fetch_network.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/browser/fetch_network.js b/src/browser/fetch_network.js index 1561ad54..da70092f 100644 --- a/src/browser/fetch_network.js +++ b/src/browser/fetch_network.js @@ -227,6 +227,9 @@ FetchNetworkAdapter.prototype.form_response_head = function(status_code, status_ return new TextEncoder().encode(lines.join("\r\n") + "\r\n\r\n"); }; +/** + * @this {TCPConnection} + */ FetchNetworkAdapter.prototype.respond_text_and_close = function(status_code, status_text, body) { const headers = new Headers({ @@ -234,7 +237,7 @@ FetchNetworkAdapter.prototype.respond_text_and_close = function(status_code, sta "content-length": body.length.toString(10), "connection": "close" }); - this.write([this.net.form_response_head(status_code, status_text, headers), new TextEncoder().encode(body)]); + this.writev([this.net.form_response_head(status_code, status_text, headers), new TextEncoder().encode(body)]); this.close(); }; From fc5d381c7f46d1f6bba4ca0300c72b7df111313b Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Tue, 15 Apr 2025 01:11:34 +0300 Subject: [PATCH 056/301] another fix --- src/browser/fetch_network.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/browser/fetch_network.js b/src/browser/fetch_network.js index da70092f..20e99b57 100644 --- a/src/browser/fetch_network.js +++ b/src/browser/fetch_network.js @@ -112,7 +112,7 @@ async function on_data_http(data) const header = this.net.parse_http_header(headers[i]); if(!header) { console.warn('The request contains an invalid header: "%s"', headers[i]); - this.net.respond_text_and_close(400, "Bad Request", `Invalid header in request: ${headers[i]}`); + this.net.respond_text_and_close(this, 400, "Bad Request", `Invalid header in request: ${headers[i]}`); return; } if( header.key.toLowerCase() === "host" ) target.host = header.value; @@ -128,7 +128,7 @@ async function on_data_http(data) target.port = localport.toString(10); } else { console.warn('Unknown port for localhost: "%s"', target.href); - this.net.respond_text_and_close(400, "Bad Request", `Unknown port for localhost: ${target.href}`); + this.net.respond_text_and_close(this, 400, "Bad Request", `Unknown port for localhost: ${target.href}`); return; } } @@ -183,7 +183,7 @@ async function on_data_http(data) .catch((e) => { console.warn("Fetch Failed: " + fetch_url + "\n" + e); if(!response_started) { - this.net.respond_text_and_close(502, "Fetch Error", `Fetch ${fetch_url} failed:\n\n${e.stack || e.message}`); + this.net.respond_text_and_close(this, 502, "Fetch Error", `Fetch ${fetch_url} failed:\n\n${e.stack || e.message}`); } this.close(); }); @@ -227,18 +227,15 @@ FetchNetworkAdapter.prototype.form_response_head = function(status_code, status_ return new TextEncoder().encode(lines.join("\r\n") + "\r\n\r\n"); }; -/** - * @this {TCPConnection} - */ -FetchNetworkAdapter.prototype.respond_text_and_close = function(status_code, status_text, body) +FetchNetworkAdapter.prototype.respond_text_and_close = function(conn, status_code, status_text, body) { const headers = new Headers({ "content-type": "text/plain", "content-length": body.length.toString(10), "connection": "close" }); - this.writev([this.net.form_response_head(status_code, status_text, headers), new TextEncoder().encode(body)]); - this.close(); + conn.writev([this.form_response_head(status_code, status_text, headers), new TextEncoder().encode(body)]); + conn.close(); }; FetchNetworkAdapter.prototype.parse_http_header = function(header) From 06f33e4a76ab991ef3068c4f7467d1dc5e8764f4 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Mon, 21 Apr 2025 10:06:36 +0300 Subject: [PATCH 057/301] remove "use strict" after sync --- tests/benchmark/fetch-download.js | 1 - tests/devices/fetch_testserver.js | 2 -- 2 files changed, 3 deletions(-) diff --git a/tests/benchmark/fetch-download.js b/tests/benchmark/fetch-download.js index ef09f10d..3d77cc49 100755 --- a/tests/benchmark/fetch-download.js +++ b/tests/benchmark/fetch-download.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -"use strict"; import url from "node:url"; import { Worker } from "node:worker_threads"; diff --git a/tests/devices/fetch_testserver.js b/tests/devices/fetch_testserver.js index 2e27ebdd..94656219 100644 --- a/tests/devices/fetch_testserver.js +++ b/tests/devices/fetch_testserver.js @@ -1,5 +1,3 @@ -"use strict"; - /** * Endpoints: * GET / - same as mocked.example.org From 3367bedec84e1a6817ef859ab449d1905ed9608d Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Mon, 21 Apr 2025 10:35:11 +0300 Subject: [PATCH 058/301] use random port for server, fix console parsing for benchmark --- tests/benchmark/fetch-download.js | 30 ++++++++++++++++++------------ tests/devices/fetch_network.js | 14 +++++++++++--- tests/devices/fetch_testserver.js | 20 +++++++++++++------- 3 files changed, 42 insertions(+), 22 deletions(-) diff --git a/tests/benchmark/fetch-download.js b/tests/benchmark/fetch-download.js index 3d77cc49..715d0032 100755 --- a/tests/benchmark/fetch-download.js +++ b/tests/benchmark/fetch-download.js @@ -6,7 +6,6 @@ import { Worker } from "node:worker_threads"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const USE_VIRTIO = !!process.env.USE_VIRTIO; -const SERVER_PORT = parseInt(process.env.SERVER_PORT, 10) || 8686; const BENCHFILE_SIZE = (parseInt(process.env.BENCHFILE_SIZE_MB, 10) || 32) * 1024 * 1024; const { V86 } = await import("../../build/libv86.mjs"); @@ -14,6 +13,7 @@ const { V86 } = await import("../../build/libv86.mjs"); const LOG_SERIAL = true; const SHOW_LOGS = false; +var SERVER_PORT = parseInt(process.env.SERVER_PORT, 10) || 0; const server = new Worker(__dirname + "../devices/fetch_testserver.js", { workerData: { port: SERVER_PORT, benchsize: BENCHFILE_SIZE } }); server.on("error", (e) => { throw new Error("server: " + e); }); @@ -30,6 +30,17 @@ const emulator = new V86({ } }); +emulator.bus.register("emulator-ready", function() +{ + server.on("message", function(msg) { + if(msg.port) + { + SERVER_PORT = msg.port; + console.log("Server started on port " + SERVER_PORT); + } + }); +}); + emulator.bus.register("emulator-started", function() { console.log("Booting now, please stand by"); @@ -55,32 +66,27 @@ emulator.add_listener("serial0-output-byte", function(byte) if(serial_text.endsWith("\t")) { - console.log(); + console.log("\n---\n"); emulator.destroy(); server.terminate(); const regex = /<(\d+)><(\d+)>\t/.exec(serial_text); - const exitcode = parseInt(regex[1], 10); - const speed = parseInt(regex[2], 10); // in bytes - if(isNaN(exitcode)) + if(!regex) { - console.error("Can't parse exit code"); + console.error("Can't parse console log"); process.exit(1); } + const exitcode = parseInt(regex[1], 10); + const speed = parseInt(regex[2], 10); // in bytes + if(exitcode !== 0) { console.error("Bench failed, curl returned non-zero exit code %s", exitcode); process.exit(exitcode); } - if(isNaN(speed)) - { - console.error("Can't parse bench speed"); - process.exit(1); - } - console.log("Average download speed: %s kB/s", (speed / 1024).toFixed(2)); } }); diff --git a/tests/devices/fetch_network.js b/tests/devices/fetch_network.js index 72bb87c7..0647cefc 100755 --- a/tests/devices/fetch_network.js +++ b/tests/devices/fetch_network.js @@ -8,12 +8,12 @@ const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); process.on("unhandledRejection", exn => { throw exn; }); const USE_VIRTIO = !!process.env.USE_VIRTIO; -const SERVER_PORT = parseInt(process.env.SERVER_PORT, 10) || 8686; - const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; -const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); const SHOW_LOGS = false; + +var SERVER_PORT = parseInt(process.env.SERVER_PORT, 10) || 0; const server = new Worker(__dirname + "fetch_testserver.js", { workerData: { port: SERVER_PORT, benchsize: 0 } }); server.on("error", (e) => { throw new Error("server: " + e); }); @@ -265,6 +265,14 @@ const emulator = new V86({ }); emulator.add_listener("emulator-ready", function () { + server.on("message", function(msg) { + if(msg.port) + { + SERVER_PORT = msg.port; + console.log("Server started on port " + SERVER_PORT); + } + }); + let network_adapter = emulator.network_adapter; let original_fetch = network_adapter.fetch; network_adapter.fetch = (url, opts) => { diff --git a/tests/devices/fetch_testserver.js b/tests/devices/fetch_testserver.js index 94656219..9c92cbe7 100644 --- a/tests/devices/fetch_testserver.js +++ b/tests/devices/fetch_testserver.js @@ -15,30 +15,36 @@ const benchfile = Buffer.alloc(benchsize); function get_handler(request, response) { if(request.url === "/") { - response.write("This text is from the local server"); + response.end("This text is from the local server"); } else if(request.url === "/bench") { response.setHeader("content-type", "application/octet-stream"); response.setHeader("content-length", benchsize.toString(10)); response.write(benchfile); + response.end(); } else if(request.url === "/header") { - response.setHeader("x-server-test", request.headers["x-client-test"].split("").join("_") || "none"); + response.writeHead(200, { "x-server-test": request.headers["x-client-test"].split("").join("_") || "none" }); + response.end(); } else if(request.url === "/redirect") { response.writeHead(307, { "location": "/" }); + response.end(); } else { response.writeHead(404); - response.write("Unknown endpoint"); + response.end("Unknown endpoint"); } } -createServer(function(request, response) { +var server = createServer(function(request, response) { switch(request.method) { case "GET": get_handler(request, response); break; default: response.writeHead(405); - response.write("Unknown method"); + response.end("Unknown method"); break; } - response.end(); -}).listen(port); +}); + +server.listen(port, () => { + parentPort.postMessage({ port: server.address().port }); +}); From ad77448248c51c042b0e6ad5a21e5a63075ea248 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Mon, 21 Apr 2025 10:39:10 +0300 Subject: [PATCH 059/301] fix parentPort --- tests/devices/fetch_testserver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/devices/fetch_testserver.js b/tests/devices/fetch_testserver.js index 9c92cbe7..50cb2c45 100644 --- a/tests/devices/fetch_testserver.js +++ b/tests/devices/fetch_testserver.js @@ -8,7 +8,7 @@ */ import { createServer } from "node:http"; -import { workerData } from "node:worker_threads"; +import { workerData, parentPort } from "node:worker_threads"; const { port, benchsize } = workerData; const benchfile = Buffer.alloc(benchsize); From 6ce3b69be07ba9e857eab978bbb1619f0a294924 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Mon, 21 Apr 2025 11:11:27 +0300 Subject: [PATCH 060/301] run a server when the emulator is ready (fixes ci) --- tests/benchmark/fetch-download.js | 5 +++-- tests/devices/fetch_network.js | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/benchmark/fetch-download.js b/tests/benchmark/fetch-download.js index 715d0032..7a567378 100755 --- a/tests/benchmark/fetch-download.js +++ b/tests/benchmark/fetch-download.js @@ -14,8 +14,7 @@ const LOG_SERIAL = true; const SHOW_LOGS = false; var SERVER_PORT = parseInt(process.env.SERVER_PORT, 10) || 0; -const server = new Worker(__dirname + "../devices/fetch_testserver.js", { workerData: { port: SERVER_PORT, benchsize: BENCHFILE_SIZE } }); -server.on("error", (e) => { throw new Error("server: " + e); }); +var server = null; const emulator = new V86({ bios: { url: __dirname + "/../../bios/seabios.bin" }, @@ -32,6 +31,8 @@ const emulator = new V86({ emulator.bus.register("emulator-ready", function() { + server = new Worker(__dirname + "../devices/fetch_testserver.js", { workerData: { port: SERVER_PORT, benchsize: BENCHFILE_SIZE } }); + server.on("error", (e) => { throw new Error("server: " + e); }); server.on("message", function(msg) { if(msg.port) { diff --git a/tests/devices/fetch_network.js b/tests/devices/fetch_network.js index 0647cefc..ba84598f 100755 --- a/tests/devices/fetch_network.js +++ b/tests/devices/fetch_network.js @@ -14,8 +14,7 @@ const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : ".. const SHOW_LOGS = false; var SERVER_PORT = parseInt(process.env.SERVER_PORT, 10) || 0; -const server = new Worker(__dirname + "fetch_testserver.js", { workerData: { port: SERVER_PORT, benchsize: 0 } }); -server.on("error", (e) => { throw new Error("server: " + e); }); +var server = null; function wait(time) { return new Promise((res) => setTimeout(res, time)); @@ -265,6 +264,8 @@ const emulator = new V86({ }); emulator.add_listener("emulator-ready", function () { + server = new Worker(__dirname + "fetch_testserver.js", { workerData: { port: SERVER_PORT, benchsize: 0 } }); + server.on("error", (e) => { throw new Error("server: " + e); }); server.on("message", function(msg) { if(msg.port) { From f344f0f40c8f15e130a2b289564afa5b48c3bb82 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Sun, 25 May 2025 19:59:08 +0300 Subject: [PATCH 061/301] change `v86local.http` to `external`, inline all server code into test and bench scripts, always use random available port --- docs/networking.md | 2 +- src/browser/fetch_network.js | 2 +- tests/benchmark/fetch-download.js | 135 ++++++++++++++++-------------- tests/devices/fetch_network.js | 85 +++++++++++++------ tests/devices/fetch_testserver.js | 50 ----------- 5 files changed, 135 insertions(+), 139 deletions(-) delete mode 100644 tests/devices/fetch_testserver.js diff --git a/docs/networking.md b/docs/networking.md index ed4f0408..b1c423eb 100644 --- a/docs/networking.md +++ b/docs/networking.md @@ -62,7 +62,7 @@ Backends `fetch` and `wisp` support a couple of special settings in `config.net_ | **dns_method** | str | DNS method to use, either `static` or `doh`. `static`: use built-in DNS server, `doh`: use [DNS-over-HTTPS](https://en.wikipedia.org/wiki/DNS_over_HTTPS) (DoH). Defaults to `static` for `fetch` and to `doh` for `wisp` backend. | | **doh_server** | str | Host name or IP address (and optional port number) of the DoH server if `dns_method` is `doh`. The value is expanded to the URL `https://DOH_SERVER/dns-query`. Default: `cloudflare-dns.com`. | | **cors_proxy** | str | CORS proxy server URL, do not use a proxy if undefined. Default: undefined (`fetch` backend only). | -| **local_http** | bool | Allow HTTP access from the guest to the host's `localhost` using the URL `http://.v86local.http` (e.g. `1234.v86local.http` -> `localhost:1234`). Default: `False` (`fetch` backend only). | +| **local_http** | bool | Allow HTTP access from the guest to the host's `localhost` using the URL `http://.v86local.http` (e.g. `1234.external` -> `localhost:1234`). Default: `False` (`fetch` backend only). | #### Example `net_device` settings diff --git a/src/browser/fetch_network.js b/src/browser/fetch_network.js index 20e99b57..0e84b414 100644 --- a/src/browser/fetch_network.js +++ b/src/browser/fetch_network.js @@ -119,7 +119,7 @@ async function on_data_http(data) else req_headers.append(header.key, header.value); } - if(this.net.local_http && !this.net.cors_proxy && (/\d+\.v86local\.http/).test(target.hostname)) { + if(this.net.local_http && !this.net.cors_proxy && (/\d+\.external/).test(target.hostname)) { dbg_log("Request to localhost: " + target.href, LOG_FETCH); const localport = parseInt(target.hostname.split(".")[0], 10); if(!isNaN(localport) && localport > 0 && localport < 65536) { diff --git a/tests/benchmark/fetch-download.js b/tests/benchmark/fetch-download.js index 7a567378..02f2d803 100755 --- a/tests/benchmark/fetch-download.js +++ b/tests/benchmark/fetch-download.js @@ -1,9 +1,11 @@ #!/usr/bin/env node import url from "node:url"; -import { Worker } from "node:worker_threads"; +import { createServer } from "node:http"; +import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); +const __filename = url.fileURLToPath(import.meta.url); const USE_VIRTIO = !!process.env.USE_VIRTIO; const BENCHFILE_SIZE = (parseInt(process.env.BENCHFILE_SIZE_MB, 10) || 32) * 1024 * 1024; @@ -13,81 +15,92 @@ const { V86 } = await import("../../build/libv86.mjs"); const LOG_SERIAL = true; const SHOW_LOGS = false; -var SERVER_PORT = parseInt(process.env.SERVER_PORT, 10) || 0; -var server = null; - -const emulator = new V86({ - bios: { url: __dirname + "/../../bios/seabios.bin" }, - vga_bios: { url: __dirname + "/../../bios/vgabios.bin" }, - bzimage: { url: __dirname + "/../../images/buildroot-bzimage68.bin" }, - autostart: true, - memory_size: 64 * 1024 * 1024, - net_device: { - relay_url: "fetch", - type: USE_VIRTIO ? "virtio" : "ne2k", - local_http: true - } -}); - -emulator.bus.register("emulator-ready", function() +if(isMainThread) { - server = new Worker(__dirname + "../devices/fetch_testserver.js", { workerData: { port: SERVER_PORT, benchsize: BENCHFILE_SIZE } }); - server.on("error", (e) => { throw new Error("server: " + e); }); - server.on("message", function(msg) { - if(msg.port) - { - SERVER_PORT = msg.port; - console.log("Server started on port " + SERVER_PORT); + const emulator = new V86({ + bios: { url: __dirname + "/../../bios/seabios.bin" }, + vga_bios: { url: __dirname + "/../../bios/vgabios.bin" }, + bzimage: { url: __dirname + "/../../images/buildroot-bzimage68.bin" }, + autostart: true, + memory_size: 64 * 1024 * 1024, + net_device: { + relay_url: "fetch", + type: USE_VIRTIO ? "virtio" : "ne2k", + local_http: true } }); -}); -emulator.bus.register("emulator-started", function() -{ - console.log("Booting now, please stand by"); -}); + const server = new Worker(__filename, { workerData: BENCHFILE_SIZE }); + server.on("error", (e) => { throw new Error("server: " + e); }); + server.on("message", function(msg) { + SERVER_PORT = msg; + console.log("Server started on port " + SERVER_PORT); + }); -var serial_text = ""; -var booted = false; + var SERVER_PORT = 0; + var serial_text = ""; + var booted = false; -emulator.add_listener("serial0-output-byte", function(byte) -{ - var chr = String.fromCharCode(byte); - - if(LOG_SERIAL) process.stdout.write(chr); - - serial_text += chr; - - if(!booted && serial_text.endsWith("~% ")) + emulator.bus.register("emulator-started", function() { - booted = true; + console.log("Booting now, please stand by"); + }); - emulator.serial0_send(`udhcpc;curl --fail --connect-timeout 10 -s -o /dev/null -w '<%{exitcode}><%{speed_download}>\\t' http://${SERVER_PORT}.v86local.http/bench\n`); - } - - if(serial_text.endsWith("\t")) + emulator.add_listener("serial0-output-byte", function(byte) { - console.log("\n---\n"); - emulator.destroy(); - server.terminate(); + var chr = String.fromCharCode(byte); - const regex = /<(\d+)><(\d+)>\t/.exec(serial_text); + if(LOG_SERIAL) process.stdout.write(chr); - if(!regex) + serial_text += chr; + + if(!booted && serial_text.endsWith("~% ")) { - console.error("Can't parse console log"); - process.exit(1); + booted = true; + emulator.serial0_send(`udhcpc;curl --fail --connect-timeout 10 -s -o /dev/null -w '<%{exitcode}><%{speed_download}>\\t' http://${SERVER_PORT}.external\n`); } - const exitcode = parseInt(regex[1], 10); - const speed = parseInt(regex[2], 10); // in bytes - - if(exitcode !== 0) + if(serial_text.endsWith("\t")) { - console.error("Bench failed, curl returned non-zero exit code %s", exitcode); - process.exit(exitcode); + console.log("\n---\n"); + emulator.destroy(); + server.terminate(); + parse_console(serial_text); } + }); +} +else +{ + const benchsize = workerData; + const benchfile = Buffer.alloc(benchsize); - console.log("Average download speed: %s kB/s", (speed / 1024).toFixed(2)); + var server = createServer(function(_, response) { + response.setHeader("content-type", "application/octet-stream"); + response.setHeader("content-length", benchsize.toString(10)); + response.write(benchfile); + response.end(); + }); + + server.listen(0, () => parentPort.postMessage(server.address().port)); +} + +function parse_console(output) { + const regex = /<(\d+)><(\d+)>\t/.exec(output); + + if(!regex) + { + console.error("Can't parse console log"); + process.exit(1); } -}); + + const exitcode = parseInt(regex[1], 10); + const speed = parseInt(regex[2], 10); // in bytes + + if(exitcode !== 0) + { + console.error("Bench failed, curl returned non-zero exit code %s", exitcode); + process.exit(exitcode); + } + + console.log("Average download speed: %s kB/s", (speed / 1024).toFixed(2)); +} diff --git a/tests/devices/fetch_network.js b/tests/devices/fetch_network.js index ba84598f..8f0548a1 100755 --- a/tests/devices/fetch_network.js +++ b/tests/devices/fetch_network.js @@ -2,9 +2,11 @@ import assert from "assert/strict"; import url from "node:url"; -import { Worker } from "node:worker_threads"; +import { Worker, isMainThread, parentPort } from "node:worker_threads"; +import { createServer } from "node:http"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); +const __filename = url.fileURLToPath(import.meta.url); process.on("unhandledRejection", exn => { throw exn; }); const USE_VIRTIO = !!process.env.USE_VIRTIO; @@ -13,8 +15,8 @@ const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); const SHOW_LOGS = false; -var SERVER_PORT = parseInt(process.env.SERVER_PORT, 10) || 0; -var server = null; +var SERVER_PORT = 0; +var emulator = null, server = null; function wait(time) { return new Promise((res) => setTimeout(res, time)); @@ -169,7 +171,7 @@ const tests = allow_failure: true, start: () => { - emulator.serial0_send(`wget -T 10 -O - ${SERVER_PORT}.v86local.http\n`); + emulator.serial0_send(`wget -T 10 -O - ${SERVER_PORT}.external\n`); emulator.serial0_send("echo -e done\\\\tlocal server\n"); }, end_trigger: "done\tlocal server", @@ -183,7 +185,7 @@ const tests = allow_failure: true, start: () => { - emulator.serial0_send(`wget -S -T 10 --header='x-client-test: hello' -O - ${SERVER_PORT}.v86local.http/header\n`); + emulator.serial0_send(`wget -S -T 10 --header='x-client-test: hello' -O - ${SERVER_PORT}.external/header\n`); emulator.serial0_send("echo -e done\\\\tlocal server custom header\n"); }, end_trigger: "done\tlocal server custom header", @@ -197,7 +199,7 @@ const tests = allow_failure: true, start: () => { - emulator.serial0_send(`curl -m 10 -L -v ${SERVER_PORT}.v86local.http/redirect\n`); + emulator.serial0_send(`curl -m 10 -L -v ${SERVER_PORT}.external/redirect\n`); emulator.serial0_send("echo -e done\\\\tlocal server redirect\n"); }, end_trigger: "done\tlocal server redirect", @@ -248,32 +250,63 @@ const tests = }, ]; -const emulator = new V86({ - bios: { url: __dirname + "/../../bios/seabios.bin" }, - vga_bios: { url: __dirname + "/../../bios/vgabios.bin" }, - bzimage: { url: __dirname + "/../../images/buildroot-bzimage68.bin" }, - autostart: true, - memory_size: 64 * 1024 * 1024, - disable_jit: +process.env.DISABLE_JIT, - net_device: { - relay_url: "fetch", - type: USE_VIRTIO ? "virtio" : "ne2k", - local_http: true - }, - log_level: SHOW_LOGS ? 0x400000 : 0, -}); +if(isMainThread) +{ + emulator = new V86({ + bios: { url: __dirname + "/../../bios/seabios.bin" }, + vga_bios: { url: __dirname + "/../../bios/vgabios.bin" }, + bzimage: { url: __dirname + "/../../images/buildroot-bzimage68.bin" }, + autostart: true, + memory_size: 64 * 1024 * 1024, + disable_jit: +process.env.DISABLE_JIT, + net_device: { + relay_url: "fetch", + type: USE_VIRTIO ? "virtio" : "ne2k", + local_http: true + }, + log_level: SHOW_LOGS ? 0x400000 : 0, + }); -emulator.add_listener("emulator-ready", function () { - server = new Worker(__dirname + "fetch_testserver.js", { workerData: { port: SERVER_PORT, benchsize: 0 } }); + server = new Worker(__filename); server.on("error", (e) => { throw new Error("server: " + e); }); server.on("message", function(msg) { - if(msg.port) - { - SERVER_PORT = msg.port; - console.log("Server started on port " + SERVER_PORT); + SERVER_PORT = msg; + console.log("Server started on port " + SERVER_PORT); + }); +} +else +{ + // placeholder for main thread handlers + emulator = { bus: {} }; + emulator.add_listener = emulator.bus.register = () => null; + + server = createServer(function(request, response) { + switch(request.method) { + case "GET": + if(request.url === "/") { + response.end("This text is from the local server"); + } else if(request.url === "/header") { + response.writeHead(200, { "x-server-test": request.headers["x-client-test"].split("").join("_") || "none" }); + response.end(); + } else if(request.url === "/redirect") { + response.writeHead(307, { "location": "/" }); + response.end(); + } else { + response.writeHead(404); + response.end("Unknown endpoint"); + } + break; + default: + response.writeHead(405); + response.end("Unknown method"); + break; } }); + server.listen(0, () => parentPort.postMessage(server.address().port)); +} + +emulator.add_listener("emulator-ready", function () { let network_adapter = emulator.network_adapter; let original_fetch = network_adapter.fetch; network_adapter.fetch = (url, opts) => { diff --git a/tests/devices/fetch_testserver.js b/tests/devices/fetch_testserver.js deleted file mode 100644 index 50cb2c45..00000000 --- a/tests/devices/fetch_testserver.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Endpoints: - * GET / - same as mocked.example.org - * GET /bench - large file for benchmark (tests/benchmark/fetch-download.js) - * GET /redirect - redirect to root endpoint - * GET /header - gets "x-client-test" header and returns "x-server-test" header - * todo: POST endpoints with JSON -*/ - -import { createServer } from "node:http"; -import { workerData, parentPort } from "node:worker_threads"; - -const { port, benchsize } = workerData; -const benchfile = Buffer.alloc(benchsize); - -function get_handler(request, response) { - if(request.url === "/") { - response.end("This text is from the local server"); - } else if(request.url === "/bench") { - response.setHeader("content-type", "application/octet-stream"); - response.setHeader("content-length", benchsize.toString(10)); - response.write(benchfile); - response.end(); - } else if(request.url === "/header") { - response.writeHead(200, { "x-server-test": request.headers["x-client-test"].split("").join("_") || "none" }); - response.end(); - } else if(request.url === "/redirect") { - response.writeHead(307, { "location": "/" }); - response.end(); - } else { - response.writeHead(404); - response.end("Unknown endpoint"); - } -} - -var server = createServer(function(request, response) { - switch(request.method) { - case "GET": - get_handler(request, response); - break; - default: - response.writeHead(405); - response.end("Unknown method"); - break; - } -}); - -server.listen(port, () => { - parentPort.postMessage({ port: server.address().port }); -}); From a1c0f12961edf46d94f6afaf637acc870d5f086c Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Mon, 26 May 2025 10:42:29 +0300 Subject: [PATCH 062/301] move remaining code into if statement --- tests/devices/fetch_network.js | 676 ++++++++++++++++----------------- 1 file changed, 336 insertions(+), 340 deletions(-) diff --git a/tests/devices/fetch_network.js b/tests/devices/fetch_network.js index 8f0548a1..92e1fdcc 100755 --- a/tests/devices/fetch_network.js +++ b/tests/devices/fetch_network.js @@ -16,243 +16,243 @@ const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : ".. const SHOW_LOGS = false; var SERVER_PORT = 0; -var emulator = null, server = null; function wait(time) { return new Promise((res) => setTimeout(res, time)); } -const tests = -[ - { - name: "DHCP", - start: () => - { - emulator.serial0_send("udhcpc\n"); - emulator.serial0_send("echo -e done\\\\tudhcpc\n"); - }, - end_trigger: "done\tudhcpc", - end: (capture) => - { - assert(/lease of 192.168.86.100 obtained/.test(capture), "lease of 192.168.86.100 obtained"); - }, - }, - { - name: "lspci", - timeout: 60, - start: () => - { - emulator.serial0_send("lspci -k\n"); - emulator.serial0_send("echo -e done\\\\tlspci\n"); - }, - end_trigger: "done\tlspci", - end: (capture) => - { - if(!USE_VIRTIO) { - assert(/ne2k/.test(capture), "ne2k missing from lspci"); - } else { - assert(!/ne2k/.test(capture), "ne2k in lspci"); - } - }, - }, - { - name: "ifconfig", - start: () => - { - emulator.serial0_send("ifconfig\n"); - emulator.serial0_send("echo -e done\\\\tifconfig\n"); - }, - end_trigger: "done\tifconfig", - end: (capture) => - { - assert(/192.168.86.100/.test(capture), "192.168.86.100"); - }, - }, - { - name: "route", - start: () => - { - emulator.serial0_send("ip route\n"); - emulator.serial0_send("echo -e done\\\\troute\n"); - }, - end_trigger: "done\troute", - end: (capture) => - { - assert(/192.168.86.1/.test(capture), "192.168.86.100"); - }, - }, - { - name: "ping 1.2.3.4", - start: () => - { - emulator.serial0_send("ping -c 2 1.2.3.4\n"); - emulator.serial0_send("echo -e done\\\\tping\n"); - }, - end_trigger: "done\tping", - end: (capture) => - { - assert(/2 packets transmitted, 2 (packets )?received, 0% packet loss/.test(capture), "2 packets transmitted, 2 packets received, 0% packet loss"); - assert(/from 1\.2\.3\.4:/.test(capture), "got correct source ip"); - }, - }, - { - name: "arp -a", - start: () => - { - emulator.serial0_send("arp -a\n"); - emulator.serial0_send("echo -e done\\\\tarp\n"); - }, - end_trigger: "done\tarp", - end: (capture) => - { - assert(/.192.168.86.1. at 52:54:00:01:02:03 \[ether\] {2}on eth0/.test(capture), "(192.168.86.1) at 52:54:00:01:02:03 [ether] on eth0"); - }, - }, - { - name: "Accept incoming connection", - timeout: 60, - allow_failure: true, - start: async () => - { - let open = await emulator.network_adapter.tcp_probe(80); - assert(!open, "Probe shows port not open"); - emulator.serial0_send("echo -n hello | socat TCP4-LISTEN:80 - && echo -e done\\\\tlisten\n"); - await wait(1000); - open = await emulator.network_adapter.tcp_probe(80); - assert(open, "Probe shows port open, but does not show as a connection"); - await wait(1000); - let h = emulator.network_adapter.connect(80); - h.on("connect", () => { - h.write(new TextEncoder().encode("From VM: ")); - h.on("data", (d) => { - d.reverse(); - h.write(d); - h.write(new TextEncoder().encode("\n")); - h.close(); - }); - }); - }, - end_trigger: "done\tlisten", - end: (capture) => - { - assert(/From VM: olleh/.test(capture), "got From VM"); - }, - }, - { - name: "GET mocked.example.org", - allow_failure: true, - start: () => - { - emulator.serial0_send("wget -T 10 -O - mocked.example.org\n"); - emulator.serial0_send("echo -e done\\\\tmocked.example.org\n"); - }, - end_trigger: "done\tmocked.example.org", - end: (capture) => - { - assert(/This text is from the mock/.test(capture), "got mocked.example.org text"); - }, - }, - { - name: "GET example.org", - allow_failure: true, - start: () => - { - emulator.serial0_send("wget -T 10 -O - example.org\n"); - emulator.serial0_send("echo -e done\\\\texample.org\n"); - }, - end_trigger: "done\texample.org", - end: (capture) => - { - assert(/This domain is for use in illustrative examples in documents/.test(capture), "got example.org text"); - }, - }, - { - name: "GET local server", - allow_failure: true, - start: () => - { - emulator.serial0_send(`wget -T 10 -O - ${SERVER_PORT}.external\n`); - emulator.serial0_send("echo -e done\\\\tlocal server\n"); - }, - end_trigger: "done\tlocal server", - end: (capture) => - { - assert(/This text is from the local server/.test(capture), "got local server text"); - }, - }, - { - name: "GET local server with custom header", - allow_failure: true, - start: () => - { - emulator.serial0_send(`wget -S -T 10 --header='x-client-test: hello' -O - ${SERVER_PORT}.external/header\n`); - emulator.serial0_send("echo -e done\\\\tlocal server custom header\n"); - }, - end_trigger: "done\tlocal server custom header", - end: (capture) => - { - assert(/x-server-test: {1,}h_e_l_l_o/.test(capture), "got local server custom header"); - }, - }, - { - name: "GET local server with redirect", - allow_failure: true, - start: () => - { - emulator.serial0_send(`curl -m 10 -L -v ${SERVER_PORT}.external/redirect\n`); - emulator.serial0_send("echo -e done\\\\tlocal server redirect\n"); - }, - end_trigger: "done\tlocal server redirect", - end: (capture) => - { - assert(/x-was-fetch-redirected: {1,}true/.test(capture), "got local server redirect header"); - assert(/This text is from the local server/.test(capture), "got actual redirect"); - }, - }, - { - name: "Forbidden character in header name", - start: () => - { - emulator.serial0_send("wget --header='test.v86: 123' -T 10 -O - test.domain\n"); - emulator.serial0_send("echo -e done\\\\tincorrect header name\n"); - }, - end_trigger: "done\tincorrect header name", - end: (capture) => - { - assert(/400 Bad Request/.test(capture), "got error 400"); - }, - }, - { - name: "Empty header value", - start: () => - { - emulator.serial0_send("wget --header='test:' -T 10 -O - test.domain\n"); - emulator.serial0_send("echo -e done\\\\tempty header value\n"); - }, - end_trigger: "done\tempty header value", - end: (capture) => - { - assert(/400 Bad Request/.test(capture), "got error 400"); - }, - }, - { - name: "Header without separator", - start: () => - { - emulator.serial0_send("wget --header='testheader' -T 10 -O - test.domain\n"); - emulator.serial0_send("echo -e done\\\\theader without colon\n"); - }, - end_trigger: "done\theader without colon", - end: (capture) => - { - assert(/400 Bad Request/.test(capture), "got error 400"); - }, - }, -]; - if(isMainThread) { - emulator = new V86({ + const tests = + [ + { + name: "DHCP", + start: () => + { + emulator.serial0_send("udhcpc\n"); + emulator.serial0_send("echo -e done\\\\tudhcpc\n"); + }, + end_trigger: "done\tudhcpc", + end: (capture) => + { + assert(/lease of 192.168.86.100 obtained/.test(capture), "lease of 192.168.86.100 obtained"); + }, + }, + { + name: "lspci", + timeout: 60, + start: () => + { + emulator.serial0_send("lspci -k\n"); + emulator.serial0_send("echo -e done\\\\tlspci\n"); + }, + end_trigger: "done\tlspci", + end: (capture) => + { + if(!USE_VIRTIO) { + assert(/ne2k/.test(capture), "ne2k missing from lspci"); + } else { + assert(!/ne2k/.test(capture), "ne2k in lspci"); + } + }, + }, + { + name: "ifconfig", + start: () => + { + emulator.serial0_send("ifconfig\n"); + emulator.serial0_send("echo -e done\\\\tifconfig\n"); + }, + end_trigger: "done\tifconfig", + end: (capture) => + { + assert(/192.168.86.100/.test(capture), "192.168.86.100"); + }, + }, + { + name: "route", + start: () => + { + emulator.serial0_send("ip route\n"); + emulator.serial0_send("echo -e done\\\\troute\n"); + }, + end_trigger: "done\troute", + end: (capture) => + { + assert(/192.168.86.1/.test(capture), "192.168.86.100"); + }, + }, + { + name: "ping 1.2.3.4", + start: () => + { + emulator.serial0_send("ping -c 2 1.2.3.4\n"); + emulator.serial0_send("echo -e done\\\\tping\n"); + }, + end_trigger: "done\tping", + end: (capture) => + { + assert(/2 packets transmitted, 2 (packets )?received, 0% packet loss/.test(capture), "2 packets transmitted, 2 packets received, 0% packet loss"); + assert(/from 1\.2\.3\.4:/.test(capture), "got correct source ip"); + }, + }, + { + name: "arp -a", + start: () => + { + emulator.serial0_send("arp -a\n"); + emulator.serial0_send("echo -e done\\\\tarp\n"); + }, + end_trigger: "done\tarp", + end: (capture) => + { + assert(/.192.168.86.1. at 52:54:00:01:02:03 \[ether\] {2}on eth0/.test(capture), "(192.168.86.1) at 52:54:00:01:02:03 [ether] on eth0"); + }, + }, + { + name: "Accept incoming connection", + timeout: 60, + allow_failure: true, + start: async () => + { + let open = await emulator.network_adapter.tcp_probe(80); + assert(!open, "Probe shows port not open"); + emulator.serial0_send("echo -n hello | socat TCP4-LISTEN:80 - && echo -e done\\\\tlisten\n"); + await wait(5000); + open = await emulator.network_adapter.tcp_probe(80); + assert(open, "Probe shows port open, but does not show as a connection"); + await wait(1000); + let h = emulator.network_adapter.connect(80); + h.on("connect", () => { + h.write(new TextEncoder().encode("From VM: ")); + h.on("data", (d) => { + d.reverse(); + h.write(d); + h.write(new TextEncoder().encode("\n")); + h.close(); + }); + }); + }, + end_trigger: "done\tlisten", + end: (capture) => + { + assert(/From VM: olleh/.test(capture), "got From VM"); + }, + }, + { + name: "GET mocked.example.org", + allow_failure: true, + start: () => + { + emulator.serial0_send("wget -T 10 -O - mocked.example.org\n"); + emulator.serial0_send("echo -e done\\\\tmocked.example.org\n"); + }, + end_trigger: "done\tmocked.example.org", + end: (capture) => + { + assert(/This text is from the mock/.test(capture), "got mocked.example.org text"); + }, + }, + { + name: "GET example.org", + allow_failure: true, + start: () => + { + emulator.serial0_send("wget -T 10 -O - example.org\n"); + emulator.serial0_send("echo -e done\\\\texample.org\n"); + }, + end_trigger: "done\texample.org", + end: (capture) => + { + assert(/This domain is for use in illustrative examples in documents/.test(capture), "got example.org text"); + }, + }, + { + name: "GET local server", + allow_failure: true, + start: () => + { + emulator.serial0_send(`wget -T 10 -O - ${SERVER_PORT}.external\n`); + emulator.serial0_send("echo -e done\\\\tlocal server\n"); + }, + end_trigger: "done\tlocal server", + end: (capture) => + { + assert(/This text is from the local server/.test(capture), "got local server text"); + }, + }, + { + name: "GET local server with custom header", + allow_failure: true, + start: () => + { + emulator.serial0_send(`wget -S -T 10 --header='x-client-test: hello' -O - ${SERVER_PORT}.external/header\n`); + emulator.serial0_send("echo -e done\\\\tlocal server custom header\n"); + }, + end_trigger: "done\tlocal server custom header", + end: (capture) => + { + assert(/x-server-test: {1,}h_e_l_l_o/.test(capture), "got local server custom header"); + }, + }, + { + name: "GET local server with redirect", + allow_failure: true, + start: () => + { + emulator.serial0_send(`curl -m 10 -L -v ${SERVER_PORT}.external/redirect\n`); + emulator.serial0_send("echo -e done\\\\tlocal server redirect\n"); + }, + end_trigger: "done\tlocal server redirect", + end: (capture) => + { + assert(/x-was-fetch-redirected: {1,}true/.test(capture), "got local server redirect header"); + assert(/This text is from the local server/.test(capture), "got actual redirect"); + }, + }, + { + name: "Forbidden character in header name", + start: () => + { + emulator.serial0_send("wget --header='test.v86: 123' -T 10 -O - test.domain\n"); + emulator.serial0_send("echo -e done\\\\tincorrect header name\n"); + }, + end_trigger: "done\tincorrect header name", + end: (capture) => + { + assert(/400 Bad Request/.test(capture), "got error 400"); + }, + }, + { + name: "Empty header value", + start: () => + { + emulator.serial0_send("wget --header='test:' -T 10 -O - test.domain\n"); + emulator.serial0_send("echo -e done\\\\tempty header value\n"); + }, + end_trigger: "done\tempty header value", + end: (capture) => + { + assert(/400 Bad Request/.test(capture), "got error 400"); + }, + }, + { + name: "Header without separator", + start: () => + { + emulator.serial0_send("wget --header='testheader' -T 10 -O - test.domain\n"); + emulator.serial0_send("echo -e done\\\\theader without colon\n"); + }, + end_trigger: "done\theader without colon", + end: (capture) => + { + assert(/400 Bad Request/.test(capture), "got error 400"); + }, + }, + ]; + + + const emulator = new V86({ bios: { url: __dirname + "/../../bios/seabios.bin" }, vga_bios: { url: __dirname + "/../../bios/vgabios.bin" }, bzimage: { url: __dirname + "/../../images/buildroot-bzimage68.bin" }, @@ -267,20 +267,120 @@ if(isMainThread) log_level: SHOW_LOGS ? 0x400000 : 0, }); - server = new Worker(__filename); + const server = new Worker(__filename); server.on("error", (e) => { throw new Error("server: " + e); }); server.on("message", function(msg) { SERVER_PORT = msg; console.log("Server started on port " + SERVER_PORT); }); + + emulator.add_listener("emulator-ready", function () { + let network_adapter = emulator.network_adapter; + let original_fetch = network_adapter.fetch; + network_adapter.fetch = (url, opts) => { + if(/^http:\/\/mocked.example.org\/?/.test(url)) { + let contents = new TextEncoder().encode("This text is from the mock"); + let headers = new Headers(); + headers.append("Content-Type", "text/plain"); + headers.append("Content-Length", contents.length); + return new Promise(res => setTimeout(() => res(new Response(contents, { + headers + })), 50)); + } + return original_fetch(url, opts); + }; + }); + + let test_num = 0; + let booted = false; + let line = ""; + let capture = ""; + let end_trigger; + + emulator.bus.register("emulator-started", function() + { + console.log("Booting now, please stand by"); + }); + + emulator.add_listener("serial0-output-byte", function(byte) + { + const chr = String.fromCharCode(byte); + if(chr < " " && chr !== "\n" && chr !== "\t" || chr > "~") + { + return; + } + + let new_line = ""; + if(chr === "\n") + { + console.log(" Captured: %s", line); + new_line = line; + capture += line + "\n"; + line = ""; + } + else + { + line += chr; + } + + if(new_line === end_trigger) + { + let test_has_failed = false; + + try { + tests[test_num].end(capture); + } catch(e) { + console.log(e); + test_has_failed = true; + } + + if(!test_has_failed) + { + console.log("[+] Test #%d passed: %s", test_num, tests[test_num].name); + } + else + { + if(tests[test_num].allow_failure) + { + console.warn("[!] Test #%d failed: %s (failure allowed)", test_num, tests[test_num].name); + } + else + { + console.error("[-] Test #%d failed: %s", test_num, tests[test_num].name); + server.terminate(); + process.exit(1); + } + } + + test_num++; + + } + + if(!booted && line.endsWith("~% ") || new_line === end_trigger) + { + booted = true; + + if(test_num >= tests.length) + { + emulator.destroy(); + server.terminate(); + console.log("Tests finished."); + } + else + { + console.log("Starting test #%d: %s", test_num, tests[test_num].name); + + capture = ""; + end_trigger = tests[test_num].end_trigger; + + tests[test_num].start(); + } + } + }); } else { - // placeholder for main thread handlers - emulator = { bus: {} }; - emulator.add_listener = emulator.bus.register = () => null; - - server = createServer(function(request, response) { + const server = createServer(function(request, response) { switch(request.method) { case "GET": if(request.url === "/") { @@ -305,107 +405,3 @@ else server.listen(0, () => parentPort.postMessage(server.address().port)); } - -emulator.add_listener("emulator-ready", function () { - let network_adapter = emulator.network_adapter; - let original_fetch = network_adapter.fetch; - network_adapter.fetch = (url, opts) => { - if(/^http:\/\/mocked.example.org\/?/.test(url)) { - let contents = new TextEncoder().encode("This text is from the mock"); - let headers = new Headers(); - headers.append("Content-Type", "text/plain"); - headers.append("Content-Length", contents.length); - return new Promise(res => setTimeout(() => res(new Response(contents, { - headers - })), 50)); - } - return original_fetch(url, opts); - }; -}); - -let test_num = 0; -let booted = false; -let line = ""; -let capture = ""; -let end_trigger; - -emulator.bus.register("emulator-started", function() -{ - console.log("Booting now, please stand by"); -}); - -emulator.add_listener("serial0-output-byte", function(byte) -{ - const chr = String.fromCharCode(byte); - if(chr < " " && chr !== "\n" && chr !== "\t" || chr > "~") - { - return; - } - - let new_line = ""; - if(chr === "\n") - { - console.log(" Captured: %s", line); - new_line = line; - capture += line + "\n"; - line = ""; - } - else - { - line += chr; - } - - if(new_line === end_trigger) - { - let test_has_failed = false; - - try { - tests[test_num].end(capture); - } catch(e) { - console.log(e); - test_has_failed = true; - } - - if(!test_has_failed) - { - console.log("[+] Test #%d passed: %s", test_num, tests[test_num].name); - } - else - { - if(tests[test_num].allow_failure) - { - console.warn("[!] Test #%d failed: %s (failure allowed)", test_num, tests[test_num].name); - } - else - { - console.error("[-] Test #%d failed: %s", test_num, tests[test_num].name); - server.terminate(); - process.exit(1); - } - } - - test_num++; - - } - - if(!booted && line.endsWith("~% ") || new_line === end_trigger) - { - booted = true; - - if(test_num >= tests.length) - { - emulator.destroy(); - server.terminate(); - console.log("Tests finished."); - } - else - { - console.log("Starting test #%d: %s", test_num, tests[test_num].name); - - capture = ""; - end_trigger = tests[test_num].end_trigger; - - tests[test_num].start(); - } - } -}); From add1d741c520ca9f0c464d1270d8833a878cf269 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Mon, 26 May 2025 10:46:55 +0300 Subject: [PATCH 063/301] run emulator after server, small cleanup --- tests/benchmark/fetch-download.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/benchmark/fetch-download.js b/tests/benchmark/fetch-download.js index 02f2d803..74837329 100755 --- a/tests/benchmark/fetch-download.js +++ b/tests/benchmark/fetch-download.js @@ -13,7 +13,6 @@ const BENCHFILE_SIZE = (parseInt(process.env.BENCHFILE_SIZE_MB, 10) || 32) * 102 const { V86 } = await import("../../build/libv86.mjs"); const LOG_SERIAL = true; -const SHOW_LOGS = false; if(isMainThread) { @@ -21,7 +20,7 @@ if(isMainThread) bios: { url: __dirname + "/../../bios/seabios.bin" }, vga_bios: { url: __dirname + "/../../bios/vgabios.bin" }, bzimage: { url: __dirname + "/../../images/buildroot-bzimage68.bin" }, - autostart: true, + autostart: false, memory_size: 64 * 1024 * 1024, net_device: { relay_url: "fetch", @@ -35,6 +34,7 @@ if(isMainThread) server.on("message", function(msg) { SERVER_PORT = msg; console.log("Server started on port " + SERVER_PORT); + emulator.run(); }); var SERVER_PORT = 0; From a07e43bc3156d463445cc7fdd9b7b40614d84c2d Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Mon, 26 May 2025 11:14:53 +0300 Subject: [PATCH 064/301] remove `local_http` flag, small fixes --- docs/networking.md | 3 ++- src/browser/fetch_network.js | 4 +--- tests/benchmark/fetch-download.js | 1 - tests/devices/fetch_network.js | 3 +-- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/networking.md b/docs/networking.md index b1c423eb..d9ec124d 100644 --- a/docs/networking.md +++ b/docs/networking.md @@ -62,7 +62,6 @@ Backends `fetch` and `wisp` support a couple of special settings in `config.net_ | **dns_method** | str | DNS method to use, either `static` or `doh`. `static`: use built-in DNS server, `doh`: use [DNS-over-HTTPS](https://en.wikipedia.org/wiki/DNS_over_HTTPS) (DoH). Defaults to `static` for `fetch` and to `doh` for `wisp` backend. | | **doh_server** | str | Host name or IP address (and optional port number) of the DoH server if `dns_method` is `doh`. The value is expanded to the URL `https://DOH_SERVER/dns-query`. Default: `cloudflare-dns.com`. | | **cors_proxy** | str | CORS proxy server URL, do not use a proxy if undefined. Default: undefined (`fetch` backend only). | -| **local_http** | bool | Allow HTTP access from the guest to the host's `localhost` using the URL `http://.v86local.http` (e.g. `1234.external` -> `localhost:1234`). Default: `False` (`fetch` backend only). | #### Example `net_device` settings @@ -163,6 +162,8 @@ Starting with PR [#1233](https://github.com/copy/v86/pull/1233), the TCP guest l v86 guests are isolated from each other when using the `fetch` backend. +v86 guests have HTTP access to the host's `localhost` using the URL `http://.external` (e.g. `1234.external` -> `localhost:1234`). + **CORS proxy server** * **[cors-anywhere](https://github.com/Rob--W/cors-anywhere)** -- NodeJS diff --git a/src/browser/fetch_network.js b/src/browser/fetch_network.js index 0e84b414..5d0195a9 100644 --- a/src/browser/fetch_network.js +++ b/src/browser/fetch_network.js @@ -39,8 +39,6 @@ export function FetchNetworkAdapter(bus, config) // Ex: 'https://corsproxy.io/?' this.cors_proxy = config.cors_proxy; - this.local_http = !!config.local_http; - this.bus.register("net" + this.id + "-mac", function(mac) { this.vm_mac = new Uint8Array(mac.split(":").map(function(x) { return parseInt(x, 16); })); }, this); @@ -119,7 +117,7 @@ async function on_data_http(data) else req_headers.append(header.key, header.value); } - if(this.net.local_http && !this.net.cors_proxy && (/\d+\.external/).test(target.hostname)) { + if(!this.net.cors_proxy && /^\d+\.external$/.test(target.hostname)) { dbg_log("Request to localhost: " + target.href, LOG_FETCH); const localport = parseInt(target.hostname.split(".")[0], 10); if(!isNaN(localport) && localport > 0 && localport < 65536) { diff --git a/tests/benchmark/fetch-download.js b/tests/benchmark/fetch-download.js index 74837329..61264e0c 100755 --- a/tests/benchmark/fetch-download.js +++ b/tests/benchmark/fetch-download.js @@ -25,7 +25,6 @@ if(isMainThread) net_device: { relay_url: "fetch", type: USE_VIRTIO ? "virtio" : "ne2k", - local_http: true } }); diff --git a/tests/devices/fetch_network.js b/tests/devices/fetch_network.js index 92e1fdcc..7e0d6bb7 100755 --- a/tests/devices/fetch_network.js +++ b/tests/devices/fetch_network.js @@ -118,7 +118,7 @@ if(isMainThread) let open = await emulator.network_adapter.tcp_probe(80); assert(!open, "Probe shows port not open"); emulator.serial0_send("echo -n hello | socat TCP4-LISTEN:80 - && echo -e done\\\\tlisten\n"); - await wait(5000); + await wait(1000); open = await emulator.network_adapter.tcp_probe(80); assert(open, "Probe shows port open, but does not show as a connection"); await wait(1000); @@ -262,7 +262,6 @@ if(isMainThread) net_device: { relay_url: "fetch", type: USE_VIRTIO ? "virtio" : "ne2k", - local_http: true }, log_level: SHOW_LOGS ? 0x400000 : 0, }); From 45d045cc44ecff8c3f7d929da6a9f08f7e7b3e38 Mon Sep 17 00:00:00 2001 From: ConfusedPenguin <33284159+ConfusedPenguin@users.noreply.github.com> Date: Tue, 3 Jun 2025 03:11:13 +0100 Subject: [PATCH 065/301] Fix serial not handling end of line properly --- src/browser/serial.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/browser/serial.js b/src/browser/serial.js index 9d65b9b1..0f049b5b 100644 --- a/src/browser/serial.js +++ b/src/browser/serial.js @@ -230,6 +230,7 @@ export function SerialAdapterXtermJS(element, bus) var term = this.term = new window["Terminal"]({ "logLevel": "off", + "convertEol": "true", }); term.write("This is the serial console. Whatever you type or paste here will be sent to COM1"); From 760fff6af861672961dea030648619a7a72abf91 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Sun, 1 Jun 2025 16:05:16 +0300 Subject: [PATCH 066/301] rtc: implement update interrupt --- src/rtc.js | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/rtc.js b/src/rtc.js index a6a8b1d7..54495fe7 100644 --- a/src/rtc.js +++ b/src/rtc.js @@ -88,6 +88,9 @@ export function RTC(cpu) this.nmi_disabled = 0; + this.update_interrupt = false; + this.update_interrupt_time = 0; + cpu.io.register_write(0x70, this, function(out_byte) { this.cmos_index = out_byte & 0x7F; @@ -114,6 +117,8 @@ RTC.prototype.get_state = function() state[9] = this.cmos_b; state[10] = this.cmos_c; state[11] = this.nmi_disabled; + state[12] = this.update_interrupt; + state[13] = this.update_interrupt_time; return state; }; @@ -132,6 +137,8 @@ RTC.prototype.set_state = function(state) this.cmos_b = state[9]; this.cmos_c = state[10]; this.nmi_disabled = state[11]; + this.update_interrupt = state[12] || false; + this.update_interrupt_time = state[13] || 0; }; RTC.prototype.timer = function(time, legacy_mode) @@ -155,6 +162,13 @@ RTC.prototype.timer = function(time, legacy_mode) this.next_interrupt_alarm = 0; } + else if(this.update_interrupt && this.update_interrupt_time < time) + { + this.cpu.device_raise_irq(8); + this.cmos_c |= 1 << 4 | 1 << 7; + + this.update_interrupt_time = time + 1000; // 1 second + } let t = 100; @@ -166,6 +180,10 @@ RTC.prototype.timer = function(time, legacy_mode) { t = Math.min(t, Math.max(0, this.next_interrupt_alarm - time)); } + if(this.update_interrupt) + { + t = Math.min(t, Math.max(0, this.update_interrupt_time - time)); + } return t; }; @@ -318,6 +336,11 @@ RTC.prototype.cmos_port_write = function(data_byte) break; case 0xB: this.cmos_b = data_byte; + if(this.cmos_b & 0x80) + { + // remove update interrupt flag + this.cmos_b &= 0xEF; + } if(this.cmos_b & 0x40) { this.next_interrupt = Date.now(); @@ -344,7 +367,11 @@ RTC.prototype.cmos_port_write = function(data_byte) this.next_interrupt_alarm = +alarm_date; } - if(this.cmos_b & 0x10) dbg_log("Unimplemented: updated interrupt", LOG_RTC); + if(this.cmos_b & 0x10) + { + dbg_log("update interrupt", LOG_RTC); + this.update_interrupt_time = Date.now(); + } dbg_log("cmos b=" + h(this.cmos_b, 2), LOG_RTC); break; @@ -359,6 +386,7 @@ RTC.prototype.cmos_port_write = function(data_byte) dbg_log("cmos write index " + h(this.cmos_index) + ": " + h(data_byte), LOG_RTC); } + this.update_interrupt = (this.cmos_b & 0x10) === 0x10 && (this.cmos_a & 0xF) > 0; this.periodic_interrupt = (this.cmos_b & 0x40) === 0x40 && (this.cmos_a & 0xF) > 0; }; From 3c944a02e0e342f38726b98f009c0a17be0232e0 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sat, 3 May 2025 16:26:03 +0200 Subject: [PATCH 067/301] merge changes from JoeOsborn's last changeset into current master Merged all functionally relevant changes, omitted: 1. Left out almost all of the "dbg_log()" changes, they're like 90% of the original PR and make the essential changes in the diffs a bit hard to read, these can be added back in at a later point in time. 2. Also left out the "wants_cdrom" option and implemented its desired effect with "cdrom: { ejected: true }" as was suggested. 3. The test "tests/api/cdrom-insert-eject.js" is also left out, can be added later back in. This patch should behave like the last changeset from JoeOsborn in 2023. --- src/browser/starter.js | 49 ++++++++ src/ide.js | 266 ++++++++++++++++++++++++----------------- src/pci.js | 20 ++-- 3 files changed, 217 insertions(+), 118 deletions(-) diff --git a/src/browser/starter.js b/src/browser/starter.js index 45b0b9cb..e2445a39 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -119,6 +119,14 @@ import { EEXIST, ENOENT } from "../../lib/9p.js"; * } * ``` * + * In order to create a CD-ROM device with ejected disk use: + * + * ```javascript + * cdrom: { + * ejected: true + * } + * ``` + * * @param {{ disable_mouse: (boolean|undefined), disable_keyboard: (boolean|undefined), @@ -457,6 +465,13 @@ V86.prototype.continue_init = async function(emulator, options) return; } + if(name === "cdrom" && file.ejected) + { + // the "ejected file" is a special CD-ROM file object, pass it to settings.cdrom and let CPU.init() handle it + settings.cdrom = file; + return; + } + if(file.get && file.set && file.load) { files_to_load.push({ @@ -984,6 +999,40 @@ V86.prototype.eject_fda = function() this.v86.cpu.devices.fdc.eject_fda(); }; +/** + * Set the image inserted in the CD-ROM drive. Can be changed at runtime, as + * when physically changing the CD-ROM. + */ +V86.prototype.set_cdrom = async function(file) +{ + if(file.url && !file.async) + { + load_file(file.url, { + done: result => + { + this.v86.cpu.devices.cdrom.master.set_cdrom(new SyncBuffer(result)); + }, + }); + } + else + { + const image = buffer_from_object(file, this.zstd_decompress_worker.bind(this)); + image.onload = () => + { + this.v86.cpu.devices.cdrom.master.set_cdrom(image); + }; + await image.load(); + } +}; + +/** + * Eject the CD-ROM. + */ +V86.prototype.eject_cdrom = function() +{ + this.v86.cpu.devices.cdrom.master.eject(); +}; + /** * Send a sequence of scan codes to the emulated PS2 controller. A list of * codes can be found at http://stanislavs.org/helppc/make_codes.html. diff --git a/src/ide.js b/src/ide.js index 5210dc0c..0d47cbe3 100644 --- a/src/ide.js +++ b/src/ide.js @@ -1,7 +1,7 @@ import { LOG_DISK } from "./const.js"; import { h } from "./lib.js"; import { dbg_assert, dbg_log } from "./log.js"; -import { CMOS_BIOS_DISKTRANSFLAG, CMOS_DISK_DATA, CMOS_DISK_DRIVE1_CYL } from "./rtc.js"; +import { CMOS_BIOS_DISKTRANSFLAG, CMOS_DISK_DATA, CMOS_DISK_DRIVE1_CYL, CMOS_DISK_DRIVE2_CYL } from "./rtc.js"; // For Types Only import { CPU } from "./cpu.js"; @@ -19,6 +19,9 @@ const HD_SECTOR_SIZE = 512; * */ export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus) { + master_buffer = master_buffer && master_buffer.ejected ? undefined : master_buffer; + slave_buffer = slave_buffer && slave_buffer.ejected ? undefined : slave_buffer; + this.master = new IDEInterface(this, cpu, master_buffer, is_cd, nr, 0, bus); this.slave = new IDEInterface(this, cpu, slave_buffer, false, nr, 1, bus); @@ -51,7 +54,7 @@ export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus) this.ata_port_high = this.ata_port | 0x204; /** @type {number} */ - this.master_port = 0xB400; + const master_port = 0xB400 + nr * 0x100; this.pci_space = [ 0x86, 0x80, 0x10, 0x70, 0x05, 0x00, 0xA0, 0x02, @@ -60,7 +63,7 @@ export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus) this.ata_port_high & 0xFF | 1, this.ata_port_high >> 8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // second device 0x00, 0x00, 0x00, 0x00, // second device - this.master_port & 0xFF | 1, this.master_port >> 8, 0x00, 0x00, + master_port & 0xFF | 1, master_port >> 8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x10, 0xD4, 0x82, @@ -174,26 +177,38 @@ export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus) { dbg_log("1F2/bytecount: " + h(data), LOG_DISK); this.master.bytecount = (this.master.bytecount << 8 | data) & 0xFFFF; - this.slave.bytecount = (this.slave.bytecount << 8 | data) & 0xFFFF; + if(slave_buffer) + { + this.slave.bytecount = (this.slave.bytecount << 8 | data) & 0xFFFF; + } }); cpu.io.register_write(this.ata_port | 3, this, function(data) { dbg_log("1F3/sector: " + h(data), LOG_DISK); this.master.sector = (this.master.sector << 8 | data) & 0xFFFF; - this.slave.sector = (this.slave.sector << 8 | data) & 0xFFFF; + if(slave_buffer) + { + this.slave.sector = (this.slave.sector << 8 | data) & 0xFFFF; + } }); cpu.io.register_write(this.ata_port | 4, this, function(data) { dbg_log("1F4/sector low: " + h(data), LOG_DISK); this.master.cylinder_low = (this.master.cylinder_low << 8 | data) & 0xFFFF; - this.slave.cylinder_low = (this.slave.cylinder_low << 8 | data) & 0xFFFF; + if(slave_buffer) + { + this.slave.cylinder_low = (this.slave.cylinder_low << 8 | data) & 0xFFFF; + } }); cpu.io.register_write(this.ata_port | 5, this, function(data) { dbg_log("1F5/sector high: " + h(data), LOG_DISK); this.master.cylinder_high = (this.master.cylinder_high << 8 | data) & 0xFFFF; - this.slave.cylinder_high = (this.slave.cylinder_high << 8 | data) & 0xFFFF; + if(slave_buffer) + { + this.slave.cylinder_high = (this.slave.cylinder_high << 8 | data) & 0xFFFF; + } }); cpu.io.register_write(this.ata_port | 6, this, function(data) { @@ -231,24 +246,26 @@ export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus) { dbg_log("lower irq", LOG_DISK); this.cpu.device_lower_irq(this.irq); + // clear error and DF bits + this.current_interface.status &= ~(1 | (1 << 5)); this.current_interface.ata_command(data); }); - cpu.io.register_read(this.master_port | 4, this, undefined, undefined, this.dma_read_addr); - cpu.io.register_write(this.master_port | 4, this, undefined, undefined, this.dma_set_addr); + cpu.io.register_read(master_port | 4, this, undefined, undefined, this.dma_read_addr); + cpu.io.register_write(master_port | 4, this, undefined, undefined, this.dma_set_addr); - cpu.io.register_read(this.master_port, this, + cpu.io.register_read(master_port, this, this.dma_read_command8, undefined, this.dma_read_command); - cpu.io.register_write(this.master_port, this, + cpu.io.register_write(master_port, this, this.dma_write_command8, undefined, this.dma_write_command); - cpu.io.register_read(this.master_port | 2, this, this.dma_read_status); - cpu.io.register_write(this.master_port | 2, this, this.dma_write_status); + cpu.io.register_read(master_port | 2, this, this.dma_read_status); + cpu.io.register_write(master_port | 2, this, this.dma_write_status); - cpu.io.register_read(this.master_port | 0x8, this, function() { + cpu.io.register_read(master_port | 0x8, this, function() { dbg_log("DMA read 0x8", LOG_DISK); return 0; }); - cpu.io.register_read(this.master_port | 0xA, this, function() { + cpu.io.register_read(master_port | 0xA, this, function() { dbg_log("DMA read 0xA", LOG_DISK); return 0; }); @@ -259,16 +276,9 @@ export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus) IDEDevice.prototype.read_status = function() { - if(this.current_interface.buffer) - { - var ret = this.current_interface.status; - dbg_log("ATA read status: " + h(ret, 2), LOG_DISK); - return ret; - } - else - { - return 0; - } + const ret = this.current_interface.status; + dbg_log("dev "+this.name+" ATA read status: " + h(ret, 2), LOG_DISK); + return ret; }; IDEDevice.prototype.write_control = function(data) @@ -276,7 +286,7 @@ IDEDevice.prototype.write_control = function(data) dbg_log("set device control: " + h(data, 2) + " interrupts " + ((data & 2) ? "disabled" : "enabled"), LOG_DISK); - if(data & 4) + if(data & 0x04) { dbg_log("Reset via control port", LOG_DISK); @@ -337,7 +347,7 @@ IDEDevice.prototype.dma_write_command8 = function(value) dbg_log("DMA write command8: " + h(value), LOG_DISK); const old_command = this.dma_command; - this.dma_command = value & 0x9; + this.dma_command = value & 0x09; if((old_command & 1) === (value & 1)) { @@ -371,7 +381,11 @@ IDEDevice.prototype.dma_write_command8 = function(value) default: dbg_log("Spurious dma command write, current command: " + h(this.current_interface.current_command), LOG_DISK); - dbg_assert(false); + dbg_log("dev "+this.name+" DMA clear status 1 bit, set status 2 bit", LOG_DISK); + this.dma_status &= ~1; + this.dma_status |= 2; + this.push_irq(); + break; } }; @@ -394,7 +408,7 @@ IDEDevice.prototype.get_state = function() state[3] = this.irq; state[4] = this.pci_id; state[5] = this.ata_port_high; - state[6] = this.master_port; + // state[6] = this.master_port; state[7] = this.name; state[8] = this.device_control; state[9] = this.prdt_addr; @@ -412,7 +426,7 @@ IDEDevice.prototype.set_state = function(state) this.irq = state[3]; this.pci_id = state[4]; this.ata_port_high = state[5]; - this.master_port = state[6]; + // this.master_port = state[6]; this.name = state[7]; this.device_control = state[8]; this.prdt_addr = state[9]; @@ -441,7 +455,7 @@ function IDEInterface(device, cpu, buffer, is_cd, device_nr, interface_nr, bus) /** @const @type {CPU} */ this.cpu = cpu; - this.buffer = buffer; + this.buffer = null; /** @type {number} */ this.sector_size = is_cd ? CDROM_SECTOR_SIZE : HD_SECTOR_SIZE; @@ -453,7 +467,7 @@ function IDEInterface(device, cpu, buffer, is_cd, device_nr, interface_nr, bus) this.sector_count = 0; /** @type {number} */ - this.head_count = 0; + this.head_count = this.is_atapi ? 1 : 0; /** @type {number} */ this.sectors_per_track = 0; @@ -461,70 +475,6 @@ function IDEInterface(device, cpu, buffer, is_cd, device_nr, interface_nr, bus) /** @type {number} */ this.cylinder_count = 0; - if(this.buffer) - { - this.sector_count = this.buffer.byteLength / this.sector_size; - - if(this.sector_count !== (this.sector_count | 0)) - { - dbg_log("Warning: Disk size not aligned with sector size", LOG_DISK); - this.sector_count = Math.ceil(this.sector_count); - } - - if(is_cd) - { - this.head_count = 1; - this.sectors_per_track = 0; - } - else - { - // "default" values: 16/63 - // common: 255, 63 - this.head_count = 16; - this.sectors_per_track = 63; - } - - - this.cylinder_count = this.sector_count / this.head_count / this.sectors_per_track; - - if(this.cylinder_count !== (this.cylinder_count | 0)) - { - dbg_log("Warning: Rounding up cylinder count. Choose different head number", LOG_DISK); - this.cylinder_count = Math.floor(this.cylinder_count); - //this.sector_count = this.cylinder_count * this.head_count * - // this.sectors_per_track * this.sector_size; - } - - //if(this.cylinder_count > 16383) - //{ - // this.cylinder_count = 16383; - //} - - // disk translation: lba - var rtc = cpu.devices.rtc; - - // master - rtc.cmos_write(CMOS_BIOS_DISKTRANSFLAG, - rtc.cmos_read(CMOS_BIOS_DISKTRANSFLAG) | 1 << this.nr * 4); - rtc.cmos_write(CMOS_DISK_DATA, rtc.cmos_read(CMOS_DISK_DATA) & 0x0F | 0xF0); - - var reg = CMOS_DISK_DRIVE1_CYL; - rtc.cmos_write(reg + 0, this.cylinder_count & 0xFF); - rtc.cmos_write(reg + 1, this.cylinder_count >> 8 & 0xFF); - rtc.cmos_write(reg + 2, this.head_count & 0xFF); - rtc.cmos_write(reg + 3, 0xFF); - rtc.cmos_write(reg + 4, 0xFF); - rtc.cmos_write(reg + 5, 0xC8); - rtc.cmos_write(reg + 6, this.cylinder_count & 0xFF); - rtc.cmos_write(reg + 7, this.cylinder_count >> 8 & 0xFF); - rtc.cmos_write(reg + 8, this.sectors_per_track & 0xFF); - - //rtc.cmos_write(CMOS_BIOS_DISKTRANSFLAG, - // rtc.cmos_read(CMOS_BIOS_DISKTRANSFLAG) | 1 << (nr * 4 + 2)); // slave - } - - this.buffer = buffer; - /** @type {number} */ this.is_lba = 0; @@ -585,9 +535,91 @@ function IDEInterface(device, cpu, buffer, is_cd, device_nr, interface_nr, bus) this.in_progress_io_ids = new Set(); this.cancelled_io_ids = new Set(); + if(buffer) + { + this.set_cdrom(buffer); + } + Object.seal(this); } +IDEInterface.prototype.eject = function() +{ + if(this.is_atapi && this.buffer) + { + this.buffer = null; + this.status = 0x59; + this.error = 0x60; + this.push_irq(); + } +}; + +IDEInterface.prototype.set_cdrom = function(buffer) +{ + if(!buffer) + { + this.eject(); + return; + } + + this.buffer = buffer; + if(this.is_atapi) /// TODO: redundant here in set_cdrom()? is_atapi === is_cd === true + { + this.status = 0x59; + this.error = 0x60; + } + this.sector_count = this.buffer.byteLength / this.sector_size; + + if(this.sector_count !== (this.sector_count | 0)) + { + dbg_log("Warning: Disk size not aligned with sector size", LOG_DISK); + this.sector_count = Math.ceil(this.sector_count); + } + + if(this.is_atapi) /// TODO: redundant here in set_cdrom()? is_atapi === is_cd === true + { + // default values: 1/2048 + this.head_count = 1; + this.sectors_per_track = 2048; + } + else /// TODO: dead code? + { + // "default" values: 16/63 + // common: 255, 63 + this.head_count = 16; + this.sectors_per_track = 63; + } + + this.cylinder_count = this.sector_count / this.head_count / this.sectors_per_track; + + if(this.cylinder_count !== (this.cylinder_count | 0)) + { + dbg_log("Warning: Rounding up cylinder count. Choose different head number", LOG_DISK); + this.cylinder_count = Math.floor(this.cylinder_count); + } + + var rtc = this.cpu.devices.rtc; + // master + rtc.cmos_write(CMOS_BIOS_DISKTRANSFLAG, + rtc.cmos_read(CMOS_BIOS_DISKTRANSFLAG) | 1 << this.nr * 4); + rtc.cmos_write(CMOS_DISK_DATA, rtc.cmos_read(CMOS_DISK_DATA) & 0x0F | 0xF0); + + var reg = this.nr == 0 ? CMOS_DISK_DRIVE1_CYL : CMOS_DISK_DRIVE2_CYL; + rtc.cmos_write(reg + 0, this.cylinder_count & 0xFF); + rtc.cmos_write(reg + 1, this.cylinder_count >> 8 & 0xFF); + rtc.cmos_write(reg + 2, this.head_count & 0xFF); + rtc.cmos_write(reg + 3, 0xFF); + rtc.cmos_write(reg + 4, 0xFF); + rtc.cmos_write(reg + 5, 0xC8); + rtc.cmos_write(reg + 6, this.cylinder_count & 0xFF); + rtc.cmos_write(reg + 7, this.cylinder_count >> 8 & 0xFF); + rtc.cmos_write(reg + 8, this.sectors_per_track & 0xFF); + + if(this.device.cpu) { + this.push_irq(); + } +}; + IDEInterface.prototype.device_reset = function() { if(this.is_atapi) @@ -623,7 +655,7 @@ IDEInterface.prototype.ata_command = function(cmd) { dbg_log("ATA Command: " + h(cmd) + " slave=" + (this.drive_head >> 4 & 1), LOG_DISK); - if(!this.buffer) + if((!this.buffer && cmd != 0xA1 && cmd != 0xEC && cmd != 0xA0)) { dbg_log("abort: No buffer", LOG_DISK); this.error = 4; @@ -855,6 +887,21 @@ IDEInterface.prototype.atapi_handle = function() this.data_pointer = 0; this.current_atapi_command = this.data[0]; + if(!this.buffer && (this.current_atapi_command == 0x25 || + this.current_atapi_command == 0x28 || + this.current_atapi_command == 0x42 || + this.current_atapi_command == 0x43 || + this.current_atapi_command == 0x51)) + { + dbg_log("dev "+this.device.name+" CD read-related action: no buffer", LOG_DISK); + this.status = 0x51; + this.error = 0x21; + this.data_allocate(0); + this.data_end = this.data_length; + this.bytecount = this.bytecount & ~7 | 2 | 1; + this.push_irq(); + return; + } switch(this.current_atapi_command) { @@ -873,7 +920,7 @@ IDEInterface.prototype.atapi_handle = function() this.status = 0x58; this.data[0] = 0x80 | 0x70; - this.data[2] = 5; // illegal request + this.data[2] = this.error >> 4; this.data[7] = 8; break; @@ -1146,9 +1193,16 @@ IDEInterface.prototype.atapi_read = function(cmd) req_length = byte_count; } - if(start >= this.buffer.byteLength) + if(!this.buffer) { - dbg_assert(false, "CD read: Outside of disk end=" + h(start + byte_count) + + dbg_assert(false, "dev "+this.device.name+" CD read: no buffer", LOG_DISK); + this.status = 0xFF; + this.error = 0x41; + this.push_irq(); + } + else if(start >= this.buffer.byteLength) + { + dbg_assert(false, "dev "+this.device.name+" CD read: Outside of disk end=" + h(start + byte_count) + " size=" + h(this.buffer.byteLength), LOG_DISK); this.status = 0xFF; @@ -1643,10 +1697,9 @@ IDEInterface.prototype.do_ata_read_sectors_dma = function() this.device.dma_status &= ~1; this.current_command = -1; - this.push_irq(); - this.report_read_end(byte_count); - //}.bind(this), 10); + + this.push_irq(); }); }; @@ -1833,13 +1886,6 @@ IDEInterface.prototype.create_identify_packet = function() { // http://bochs.sourceforge.net/cgi-bin/lxr/source/iodev/harddrv.cc#L2821 - if(this.drive_head & 0x10) - { - // slave - this.data_allocate(0); - return; - } - for(var i = 0; i < 512; i++) { this.data[i] = 0; diff --git a/src/pci.js b/src/pci.js index 9cf5e08d..0f0792a6 100644 --- a/src/pci.js +++ b/src/pci.js @@ -394,7 +394,7 @@ PCI.prototype.pci_write32 = function(address, written) var bar_nr = addr - 0x10 >> 2; var bar = device.pci_bars[bar_nr]; - dbg_log("BAR" + bar_nr + " exists=" + (bar ? "y" : "n") + " changed to " + + dbg_log("BAR" + bar_nr + " exists=" + (bar ? "y" : "n") + " changed from " + h(space[addr >> 2]) + " to " + h(written >>> 0) + " dev=" + h(bdf >> 3, 2) + " (" + device.name + ") ", LOG_PCI); if(bar) @@ -518,6 +518,7 @@ PCI.prototype.register_device = function(device) var bar_base = bar_space[i]; var type = bar_base & 1; + dbg_log("device "+ device.name +" register bar of size "+bar.size +" at " + h(bar_base), LOG_PCI); bar.original_bar = bar_base; bar.entries = []; @@ -565,6 +566,9 @@ PCI.prototype.set_io_bars = function(bar, from, to) old_entry.write32 === this.io.empty_port_write) { // happens when a device doesn't register its full range (currently ne2k and virtio) + // but it also happens when aligned reads/writes are set up, + // e.g. a 16-bit read registered at 0xB400 will show up as + // no source mapping at 0xB401. dbg_log("Warning: Bad IO bar: Source not mapped, port=" + h(from + i, 4), LOG_PCI); } @@ -577,16 +581,16 @@ PCI.prototype.set_io_bars = function(bar, from, to) ports[to + i] = entry; } - if(empty_entry.read8 === this.io.empty_port_read8 || - empty_entry.read16 === this.io.empty_port_read16 || - empty_entry.read32 === this.io.empty_port_read32 || - empty_entry.write8 === this.io.empty_port_write || - empty_entry.write16 === this.io.empty_port_write || - empty_entry.write32 === this.io.empty_port_write) + if(empty_entry.read8 !== this.io.empty_port_read8 || + empty_entry.read16 !== this.io.empty_port_read16 || + empty_entry.read32 !== this.io.empty_port_read32 || + empty_entry.write8 !== this.io.empty_port_write || + empty_entry.write16 !== this.io.empty_port_write || + empty_entry.write32 !== this.io.empty_port_write) { // These can fail if the os maps an io port in multiple bars (indicating a bug) // XXX: Fails during restore_state - dbg_log("Warning: Bad IO bar: Target already mapped, port=" + h(to + i, 4), LOG_PCI); + dbg_log("Warning: Bad IO bar: Target already mapped, port=" + h(to + i, 4)+" from device "+entry.device.name+" to device "+empty_entry.device.name, LOG_PCI); } } }; From 1b90d2e74e6dfb6591dda292b3e3a5c5e5943e1f Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Fri, 9 May 2025 13:47:38 +0200 Subject: [PATCH 068/301] create a single PCI device instead of two Implement a single PCI IDE device in Compatibility Mode for up to 4 drives. Changes to ide.js: - added new exported class IDEPCIAdapter, the root class for up to 4 drives - introduced new config scheme for IDEPCIAdapter (see comment about adapter_config) - moved PCI-device related members from IDEDevice to IDEPCIAdapter - rewrote constructor of IDEDevice (no longer exported) - fixed (?) write-access to ata_port registers 1-6 (see comment below) - added missing declarations for BAR2 and BAR3 in pci_bars[] - made definition of the PCI configuration space a bit more more verbose - renamed "master_port" to "bus_master_port" to avoid confusion - added a few comments here and there Regarding write-access to ata_port registers 1-6: It is not clear why these 6 functions simultaneously modify master and slave attributes instead of just modifying this.current_interface (as is the case everywhere else in the code). This patch changes that to use this.current_interface, experimental until there is some explanation. Also changed cpu.js to now use IDEPCIAdapter instead of IDEDevice. TODO: 1. clear up the matter around write-access to ata_port registers 1-6 2. in ide.js, a single-device channel needs to be better supported, currently the code reports a Hard-Disk device of size 0 for an unconnected slave drive, and there must be a better way to signal this case. 3. in cpu.js, this.devices.hda and this.devices.cdrom point to a IDEDevice, they should point to an IDEInterface for this.devices.hdb to make sense. This needs to be better understood and fixed for slave drives to work. 4. getting some very early output under SeaBIOS, but the screen clears so quickly that I cannot read it and it's also not printed to the serial console --- src/cpu.js | 33 +++++-- src/ide.js | 264 ++++++++++++++++++++++++++++++++--------------------- 2 files changed, 186 insertions(+), 111 deletions(-) diff --git a/src/cpu.js b/src/cpu.js index 9690418a..224e1973 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -29,7 +29,8 @@ import { PS2 } from "./ps2.js"; import { read_elf } from "./elf.js"; import { FloppyController } from "./floppy.js"; -import { IDEDevice } from "./ide.js"; +/// import { IDEDevice } from "./ide.js"; +import { IDEPCIAdapter } from "./ide.js"; import { VirtioNet } from "./virtio_net.js"; import { VGAScreen } from "./vga.js"; import { VirtioBalloon } from "./virtio_balloon.js"; @@ -1105,16 +1106,30 @@ CPU.prototype.init = function(settings, device_bus) this.devices.fdc = new FloppyController(this, settings.fda, settings.fdb); - var ide_device_count = 0; + if(settings.hda || settings.cdrom) { + let ide_device_count = 0; + const ide_config = [[undefined, undefined], [undefined, undefined]]; + if(settings.hda) { + ide_config[ide_device_count][0] = {buffer: settings.hda}; + ide_config[ide_device_count][1] = {buffer: settings.hdb}; + ide_device_count++; + } + if(settings.cdrom) { + ide_config[ide_device_count][0] = { + is_cdrom: true, + buffer: settings.cdrom.ejected ? undefined : settings.cdrom + }; + } - if(settings.hda) - { - this.devices.hda = new IDEDevice(this, settings.hda, settings.hdb, false, ide_device_count++, device_bus); - } + this.devices.ide = new IDEPCIAdapter(this, device_bus, ide_config); - if(settings.cdrom) - { - this.devices.cdrom = new IDEDevice(this, settings.cdrom, undefined, true, ide_device_count++, device_bus); + if(settings.hda) { + this.devices.hda = this.devices.ide.primary; +// this.devices.hdb = ? // TODO: this.devices.hda/hdb/cdrom should point to IDEInterface, not IDEDevice objects?! + } + if(settings.cdrom) { + this.devices.cdrom = settings.hda ? this.devices.ide.secondary : this.devices.ide.primary; + } } this.devices.pit = new PIT(this, device_bus); diff --git a/src/ide.js b/src/ide.js index 0d47cbe3..eedf265c 100644 --- a/src/ide.js +++ b/src/ide.js @@ -13,63 +13,67 @@ const HD_SECTOR_SIZE = 512; /** * @constructor * @param {CPU} cpu - * @param {boolean} is_cd - * @param {number} nr * @param {BusConnector} bus + * + * adapter_config: [ [, ], [, ] ] + * - Each of the four arguments (primary-master, primary-slave, ...) is either undefined + * or an interface_config object of the form: + * interface_config := { buffer: Uint8Array, is_cdrom: bool } + * - is_cdrom: optional + * - if true a CD-ROM device using buffer is created (if buffer is undefined then CD is ejected) + * - else a Hard-Disk device using buffer is created (if buffer is undefined a HD of size 0 is created, that's still TODO) * */ -export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus) +export function IDEPCIAdapter(cpu, bus, adapter_config) { - master_buffer = master_buffer && master_buffer.ejected ? undefined : master_buffer; - slave_buffer = slave_buffer && slave_buffer.ejected ? undefined : slave_buffer; - - this.master = new IDEInterface(this, cpu, master_buffer, is_cd, nr, 0, bus); - this.slave = new IDEInterface(this, cpu, slave_buffer, false, nr, 1, bus); - - this.current_interface = this.master; + // Except for bus_master_port, all ports and IRQs are predefined and + // cannot be changed in Compatibility mode. + // The 16 registers of the Bus Master port are split in two, registers + // 0..7 are used for the primary channel, and 8..15 for the secondary. + const bus_master_port = 0xB400; + const pri = { ata_port: 0x1f0, ata_port_high: 0x3f4, irq: 14, bus_master_port: bus_master_port }; + const sec = { ata_port: 0x170, ata_port_high: 0x374, irq: 15, bus_master_port: bus_master_port | 0x8 }; this.cpu = cpu; + this.bus = bus; + this.primary = undefined; + this.secondary = undefined; - // gets set via PCI in seabios, likely doesn't matter - if(nr === 0) - { - this.ata_port = 0x1F0; - this.irq = 14; - - this.pci_id = 0x1E << 3; - } - else if(nr === 1) - { - this.ata_port = 0x170; - this.irq = 15; - - this.pci_id = 0x1F << 3; - } - else - { - dbg_assert(false, "IDE device with nr " + nr + " ignored", LOG_DISK); + const has_primary = adapter_config && adapter_config[0][0]; + const has_secondary = adapter_config && adapter_config[1][0]; + if(!has_primary && !has_secondary) { + DEBUG && Object.seal(this); + return; } - // alternate status, starting at 3f4/374 - /** @type {number} */ - this.ata_port_high = this.ata_port | 0x204; + if(has_primary) { + this.primary = new IDEDevice(this, adapter_config, 0, pri); + } - /** @type {number} */ - const master_port = 0xB400 + nr * 0x100; + if(has_secondary) { + this.secondary = new IDEDevice(this, adapter_config, 1, sec); + } + const vendor_id = 0x8086; // Intel Corporation + const device_id = 0x7010; // 82371SB PIIX3 IDE [Natoma/Triton II] + const class_code = 0x01; // Mass Storage Controller + const subclass = 0x01; // IDE Controller + const prog_if = 0x80; // ISA Compatibility mode-only controller, supports bus mastering + const interrupt_line = 0x00; // IRQs 14 and 15 are predefined in Compatibility mode and this field is ignored + + this.pci_id = 0x1F << 3; this.pci_space = [ - 0x86, 0x80, 0x10, 0x70, 0x05, 0x00, 0xA0, 0x02, - 0x00, 0x80, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, - this.ata_port & 0xFF | 1, this.ata_port >> 8, 0x00, 0x00, - this.ata_port_high & 0xFF | 1, this.ata_port_high >> 8, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, // second device - 0x00, 0x00, 0x00, 0x00, // second device - master_port & 0xFF | 1, master_port >> 8, 0x00, 0x00, + vendor_id & 0xFF, vendor_id >> 8, device_id & 0xFF, device_id >> 8, 0x05, 0x00, 0xA0, 0x02, + 0x00, prog_if, subclass, class_code, 0x00, 0x00, 0x00, 0x00, + pri.ata_port & 0xFF | 1, pri.ata_port >> 8, 0x00, 0x00, + pri.ata_port_high & 0xFF | 1, pri.ata_port_high >> 8, 0x00, 0x00, + sec.ata_port & 0xFF | 1, sec.ata_port >> 8, 0x00, 0x00, + sec.ata_port_high & 0xFF | 1, sec.ata_port_high >> 8, 0x00, 0x00, + bus_master_port & 0xFF | 1, bus_master_port >> 8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x10, 0xD4, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, this.irq, 0x01, 0x00, 0x00, - + 0x00, 0x00, 0x00, 0x00, interrupt_line, 0x01, 0x00, 0x00, // 0x40 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -87,23 +91,69 @@ export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus) ]; this.pci_bars = [ { - size: 8, + size: 8, // BAR0: Command block registers of primary ATA Channel }, { - size: 4, + size: 4, // BAR1: Control registers of primary ATA Channel }, - undefined, - undefined, { - size: 0x10, + size: 8, // BAR2: Command block registers of secondary ATA Channel }, + { + size: 4, // BAR3: Control registers of secondary ATA Channel + }, + { + size: 16, // BAR4: ATA Bus Master I/O registers (primary ATA Channel: 0..7, secondary: 8..15) + } ]; - this.name = "ide" + nr; + cpu.devices.pci.register_device(this); + + DEBUG && Object.seal(this); +} + +/** + * @constructor + * @param {IDEPCIAdapter} adapter + * @param {number} channel_nr + * */ +function IDEDevice(adapter, adapter_config, channel_nr, channel_config) +{ + this.adapter = adapter; + this.cpu = adapter.cpu; + this.bus = adapter.bus; + this.ata_port = channel_config.ata_port; + this.ata_port_high = channel_config.ata_port_high; + this.irq = channel_config.irq; + + const create_interface = interface_nr => { + const config = adapter_config && adapter_config[channel_nr] ? + adapter_config[channel_nr][interface_nr] : undefined; + const buffer = config ? config.buffer : undefined; + const is_cdrom = config ? !!config.is_cdrom : false; + return new IDEInterface(this, this.cpu, buffer, is_cdrom, channel_nr, interface_nr, this.bus); + }; + + this.master = create_interface(0); + this.slave = create_interface(1); + this.current_interface = this.master; + + this.name = "ide" + channel_nr; /** @type {number} */ this.device_control = 2; - // status + /** @type {number} */ + this.prdt_addr = 0; + + /** @type {number} */ + this.dma_status = 0; + + /** @type {number} */ + this.dma_command = 0; + + // Status Registers (across BAR0-BAR3) + const cpu = adapter.cpu; + cpu.io.register_read(this.ata_port | 7, this, function() { dbg_log("lower irq", LOG_DISK); this.cpu.device_lower_irq(this.irq); @@ -111,7 +161,12 @@ export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus) }); cpu.io.register_read(this.ata_port_high | 2, this, this.read_status); + // Control Register (BAR1/3) + cpu.io.register_write(this.ata_port_high | 2, this, this.write_control); + + // ATA/ATAPI Registers (BAR0/2) + cpu.io.register_read(this.ata_port | 0, this, function() { return this.current_interface.read_data(1); @@ -155,6 +210,7 @@ export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus) dbg_log("Read 1F6", LOG_DISK); return this.current_interface.drive_head & 0xFF; }); + // NOTE: read-handler for Status Register (this.ata_port | 7) is already defined above cpu.io.register_write(this.ata_port | 0, this, function(data) { @@ -170,45 +226,60 @@ export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus) cpu.io.register_write(this.ata_port | 1, this, function(data) { dbg_log("1F1/lba_count: " + h(data), LOG_DISK); - this.master.lba_count = (this.master.lba_count << 8 | data) & 0xFFFF; - this.slave.lba_count = (this.slave.lba_count << 8 | data) & 0xFFFF; +/*** + * this.master.lba_count = (this.master.lba_count << 8 | data) & 0xFFFF; + * this.slave.lba_count = (this.slave.lba_count << 8 | data) & 0xFFFF; + ***/ + this.current_interface.lba_count = (this.current_interface.lba_count << 8 | data) & 0xFFFF; }); cpu.io.register_write(this.ata_port | 2, this, function(data) { dbg_log("1F2/bytecount: " + h(data), LOG_DISK); - this.master.bytecount = (this.master.bytecount << 8 | data) & 0xFFFF; - if(slave_buffer) - { - this.slave.bytecount = (this.slave.bytecount << 8 | data) & 0xFFFF; - } +/*** + * this.master.bytecount = (this.master.bytecount << 8 | data) & 0xFFFF; + * if(slave_buffer) + * { + * this.slave.bytecount = (this.slave.bytecount << 8 | data) & 0xFFFF; + * } + ***/ + this.current_interface.bytecount = (this.current_interface.bytecount << 8 | data) & 0xFFFF; }); cpu.io.register_write(this.ata_port | 3, this, function(data) { dbg_log("1F3/sector: " + h(data), LOG_DISK); - this.master.sector = (this.master.sector << 8 | data) & 0xFFFF; - if(slave_buffer) - { - this.slave.sector = (this.slave.sector << 8 | data) & 0xFFFF; - } +/*** + * this.master.sector = (this.master.sector << 8 | data) & 0xFFFF; + * if(slave_buffer) + * { + * this.slave.sector = (this.slave.sector << 8 | data) & 0xFFFF; + * } + ***/ + this.current_interface.sector = (this.current_interface.sector << 8 | data) & 0xFFFF; }); cpu.io.register_write(this.ata_port | 4, this, function(data) { dbg_log("1F4/sector low: " + h(data), LOG_DISK); - this.master.cylinder_low = (this.master.cylinder_low << 8 | data) & 0xFFFF; - if(slave_buffer) - { - this.slave.cylinder_low = (this.slave.cylinder_low << 8 | data) & 0xFFFF; - } +/*** + * this.master.cylinder_low = (this.master.cylinder_low << 8 | data) & 0xFFFF; + * if(slave_buffer) + * { + * this.slave.cylinder_low = (this.slave.cylinder_low << 8 | data) & 0xFFFF; + * } + ***/ + this.current_interface.cylinder_low = (this.current_interface.cylinder_low << 8 | data) & 0xFFFF; }); cpu.io.register_write(this.ata_port | 5, this, function(data) { dbg_log("1F5/sector high: " + h(data), LOG_DISK); - this.master.cylinder_high = (this.master.cylinder_high << 8 | data) & 0xFFFF; - if(slave_buffer) - { - this.slave.cylinder_high = (this.slave.cylinder_high << 8 | data) & 0xFFFF; - } +/*** + * this.master.cylinder_high = (this.master.cylinder_high << 8 | data) & 0xFFFF; + * if(slave_buffer) + * { + * this.slave.cylinder_high = (this.slave.cylinder_high << 8 | data) & 0xFFFF; + * } + ***/ + this.current_interface.cylinder_high = (this.current_interface.cylinder_high << 8 | data) & 0xFFFF; }); cpu.io.register_write(this.ata_port | 6, this, function(data) { @@ -226,22 +297,17 @@ export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus) { this.current_interface = this.master; } - - this.master.drive_head = data; - this.slave.drive_head = data; - this.master.is_lba = this.slave.is_lba = data >> 6 & 1; - this.master.head = this.slave.head = data & 0xF; +/*** + * this.master.drive_head = data; + * this.slave.drive_head = data; + * this.master.is_lba = this.slave.is_lba = data >> 6 & 1; + * this.master.head = this.slave.head = data & 0xF; + ***/ + this.current_interface.drive_head = data; + this.current_interface.is_lba = data >> 6 & 1; + this.current_interface.head = data & 0xF; }); - /** @type {number} */ - this.prdt_addr = 0; - - /** @type {number} */ - this.dma_status = 0; - - /** @type {number} */ - this.dma_command = 0; - cpu.io.register_write(this.ata_port | 7, this, function(data) { dbg_log("lower irq", LOG_DISK); @@ -251,27 +317,21 @@ export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus) this.current_interface.ata_command(data); }); - cpu.io.register_read(master_port | 4, this, undefined, undefined, this.dma_read_addr); - cpu.io.register_write(master_port | 4, this, undefined, undefined, this.dma_set_addr); + // Bus Master Registers (BAR4) + const bus_master_port = channel_config.bus_master_port; - cpu.io.register_read(master_port, this, + cpu.io.register_read(bus_master_port, this, this.dma_read_command8, undefined, this.dma_read_command); - cpu.io.register_write(master_port, this, + cpu.io.register_write(bus_master_port, this, this.dma_write_command8, undefined, this.dma_write_command); - cpu.io.register_read(master_port | 2, this, this.dma_read_status); - cpu.io.register_write(master_port | 2, this, this.dma_write_status); + cpu.io.register_read(bus_master_port | 2, this, this.dma_read_status); + cpu.io.register_write(bus_master_port | 2, this, this.dma_write_status); - cpu.io.register_read(master_port | 0x8, this, function() { - dbg_log("DMA read 0x8", LOG_DISK); return 0; - }); - cpu.io.register_read(master_port | 0xA, this, function() { - dbg_log("DMA read 0xA", LOG_DISK); return 0; - }); + cpu.io.register_read(bus_master_port | 4, this, undefined, undefined, this.dma_read_addr); + cpu.io.register_write(bus_master_port | 4, this, undefined, undefined, this.dma_set_addr); - cpu.devices.pci.register_device(this); - - DEBUG && Object.seal(this); + Object.seal(this); } IDEDevice.prototype.read_status = function() @@ -408,7 +468,7 @@ IDEDevice.prototype.get_state = function() state[3] = this.irq; state[4] = this.pci_id; state[5] = this.ata_port_high; - // state[6] = this.master_port; + // state[6] = this.bus_master_port; state[7] = this.name; state[8] = this.device_control; state[9] = this.prdt_addr; @@ -426,7 +486,7 @@ IDEDevice.prototype.set_state = function(state) this.irq = state[3]; this.pci_id = state[4]; this.ata_port_high = state[5]; - // this.master_port = state[6]; + // this.bus_master_port = state[6]; this.name = state[7]; this.device_control = state[8]; this.prdt_addr = state[9]; From bcaf02af5fb29b0e1c6929c51592a4698acebde5 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Fri, 9 May 2025 16:03:06 +0200 Subject: [PATCH 069/301] added IDEPCIAdapter.channels[] and IDEDevice.interfaces[] for index-based access to IDEDevice and IDEInterface objects --- src/cpu.js | 15 +++++++-------- src/ide.js | 6 ++++-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/cpu.js b/src/cpu.js index 224e1973..0375f03b 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -29,7 +29,6 @@ import { PS2 } from "./ps2.js"; import { read_elf } from "./elf.js"; import { FloppyController } from "./floppy.js"; -/// import { IDEDevice } from "./ide.js"; import { IDEPCIAdapter } from "./ide.js"; import { VirtioNet } from "./virtio_net.js"; import { VGAScreen } from "./vga.js"; @@ -1107,15 +1106,15 @@ CPU.prototype.init = function(settings, device_bus) this.devices.fdc = new FloppyController(this, settings.fda, settings.fdb); if(settings.hda || settings.cdrom) { - let ide_device_count = 0; + let cdrom_channel = 0; const ide_config = [[undefined, undefined], [undefined, undefined]]; if(settings.hda) { - ide_config[ide_device_count][0] = {buffer: settings.hda}; - ide_config[ide_device_count][1] = {buffer: settings.hdb}; - ide_device_count++; + ide_config[0][0] = {buffer: settings.hda}; + ide_config[0][1] = {buffer: settings.hdb}; + cdrom_channel++; } if(settings.cdrom) { - ide_config[ide_device_count][0] = { + ide_config[cdrom_channel][0] = { is_cdrom: true, buffer: settings.cdrom.ejected ? undefined : settings.cdrom }; @@ -1124,11 +1123,11 @@ CPU.prototype.init = function(settings, device_bus) this.devices.ide = new IDEPCIAdapter(this, device_bus, ide_config); if(settings.hda) { - this.devices.hda = this.devices.ide.primary; + this.devices.hda = this.devices.ide.channels[0]; // this.devices.hdb = ? // TODO: this.devices.hda/hdb/cdrom should point to IDEInterface, not IDEDevice objects?! } if(settings.cdrom) { - this.devices.cdrom = settings.hda ? this.devices.ide.secondary : this.devices.ide.primary; + this.devices.cdrom = this.devices.ide.channels[cdrom_channel]; } } diff --git a/src/ide.js b/src/ide.js index eedf265c..8efd9e15 100644 --- a/src/ide.js +++ b/src/ide.js @@ -37,6 +37,7 @@ export function IDEPCIAdapter(cpu, bus, adapter_config) this.bus = bus; this.primary = undefined; this.secondary = undefined; + this.channels = [undefined, undefined]; const has_primary = adapter_config && adapter_config[0][0]; const has_secondary = adapter_config && adapter_config[1][0]; @@ -44,7 +45,6 @@ export function IDEPCIAdapter(cpu, bus, adapter_config) DEBUG && Object.seal(this); return; } - if(has_primary) { this.primary = new IDEDevice(this, adapter_config, 0, pri); } @@ -52,6 +52,7 @@ export function IDEPCIAdapter(cpu, bus, adapter_config) if(has_secondary) { this.secondary = new IDEDevice(this, adapter_config, 1, sec); } + this.channels = [this.primary, this.secondary]; const vendor_id = 0x8086; // Intel Corporation const device_id = 0x7010; // 82371SB PIIX3 IDE [Natoma/Triton II] @@ -116,7 +117,7 @@ export function IDEPCIAdapter(cpu, bus, adapter_config) * @param {IDEPCIAdapter} adapter * @param {number} channel_nr * */ -function IDEDevice(adapter, adapter_config, channel_nr, channel_config) +function IDEDevice(adapter, adapter_config, channel_nr, channel_config) // TODO: rename this class to IDEChannel { this.adapter = adapter; this.cpu = adapter.cpu; @@ -135,6 +136,7 @@ function IDEDevice(adapter, adapter_config, channel_nr, channel_config) this.master = create_interface(0); this.slave = create_interface(1); + this.interfaces = [this.master, this.slave]; this.current_interface = this.master; this.name = "ide" + channel_nr; From c64d1d2dbecf20b639cda5233debe1adf147418c Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Fri, 9 May 2025 18:01:01 +0200 Subject: [PATCH 070/301] code cleanup --- src/ide.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/ide.js b/src/ide.js index 8efd9e15..ba5f0f69 100644 --- a/src/ide.js +++ b/src/ide.js @@ -597,10 +597,8 @@ function IDEInterface(device, cpu, buffer, is_cd, device_nr, interface_nr, bus) this.in_progress_io_ids = new Set(); this.cancelled_io_ids = new Set(); - if(buffer) - { - this.set_cdrom(buffer); - } + // set optional buffer for both ATA and ATAPI + this.set_cdrom(buffer); Object.seal(this); } @@ -620,12 +618,11 @@ IDEInterface.prototype.set_cdrom = function(buffer) { if(!buffer) { - this.eject(); return; } this.buffer = buffer; - if(this.is_atapi) /// TODO: redundant here in set_cdrom()? is_atapi === is_cd === true + if(this.is_atapi) { this.status = 0x59; this.error = 0x60; @@ -638,13 +635,13 @@ IDEInterface.prototype.set_cdrom = function(buffer) this.sector_count = Math.ceil(this.sector_count); } - if(this.is_atapi) /// TODO: redundant here in set_cdrom()? is_atapi === is_cd === true + if(this.is_atapi) { // default values: 1/2048 this.head_count = 1; this.sectors_per_track = 2048; } - else /// TODO: dead code? + else { // "default" values: 16/63 // common: 255, 63 From a04af85852eaf53fc0e180ec3ee8a776df555270 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Fri, 9 May 2025 18:03:23 +0200 Subject: [PATCH 071/301] added hacky shortcut to indicate missing slave drive --- src/ide.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ide.js b/src/ide.js index ba5f0f69..88c53448 100644 --- a/src/ide.js +++ b/src/ide.js @@ -338,7 +338,9 @@ function IDEDevice(adapter, adapter_config, channel_nr, channel_config) // T IDEDevice.prototype.read_status = function() { - const ret = this.current_interface.status; + /// temporary hack to indicate missing slave drive to host + /// const ret = this.current_interface.status; + const ret = this.current_interface.is_atapi || this.current_interface.buffer ? this.current_interface.status : 0; dbg_log("dev "+this.name+" ATA read status: " + h(ret, 2), LOG_DISK); return ret; }; From f37e0eb5ad6db223e720fb69bf9fdedb8ce84181 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Fri, 9 May 2025 19:09:09 +0200 Subject: [PATCH 072/301] added boolean member IDEInterface.drive_connected --- src/ide.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ide.js b/src/ide.js index 88c53448..c76dc1dc 100644 --- a/src/ide.js +++ b/src/ide.js @@ -340,7 +340,7 @@ IDEDevice.prototype.read_status = function() { /// temporary hack to indicate missing slave drive to host /// const ret = this.current_interface.status; - const ret = this.current_interface.is_atapi || this.current_interface.buffer ? this.current_interface.status : 0; + const ret = this.current_interface.drive_connected ? this.current_interface.status : 0; dbg_log("dev "+this.name+" ATA read status: " + h(ret, 2), LOG_DISK); return ret; }; @@ -521,6 +521,9 @@ function IDEInterface(device, cpu, buffer, is_cd, device_nr, interface_nr, bus) this.buffer = null; + /** @type {boolean} */ + this.drive_connected = is_cd || buffer; + /** @type {number} */ this.sector_size = is_cd ? CDROM_SECTOR_SIZE : HD_SECTOR_SIZE; From f5e35644daf1bdc9899b31f37e5550e2857c62f1 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Fri, 9 May 2025 19:40:02 +0200 Subject: [PATCH 073/301] improved documentation --- src/ide.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/ide.js b/src/ide.js index c76dc1dc..3e2074ab 100644 --- a/src/ide.js +++ b/src/ide.js @@ -16,12 +16,18 @@ const HD_SECTOR_SIZE = 512; * @param {BusConnector} bus * * adapter_config: [ [, ], [, ] ] - * - Each of the four arguments (primary-master, primary-slave, ...) is either undefined - * or an interface_config object of the form: + * + * Each of the four arguments (, , ...) is + * either undefined or an interface_config object of the form: + * * interface_config := { buffer: Uint8Array, is_cdrom: bool } - * - is_cdrom: optional - * - if true a CD-ROM device using buffer is created (if buffer is undefined then CD is ejected) - * - else a Hard-Disk device using buffer is created (if buffer is undefined a HD of size 0 is created, that's still TODO) + * + * If is_cdrom is defined and true: + * - If buffer is defined: create an ATAPI CD-ROM device using buffer as inserted disk + * - If buffer is undefined: create an ATAPI CD-ROM device with ejectd disk + * If is_cdrom is undfined or false: + * - If buffer is defined: create an ATA Hard-Disk using buffer as disk image + * - If buffer is undefined: create a dummy ATA device representing a missing disk * */ export function IDEPCIAdapter(cpu, bus, adapter_config) { @@ -39,6 +45,7 @@ export function IDEPCIAdapter(cpu, bus, adapter_config) this.secondary = undefined; this.channels = [undefined, undefined]; + // In each channel, a slave drive can only exist if also a master drive exists. const has_primary = adapter_config && adapter_config[0][0]; const has_secondary = adapter_config && adapter_config[1][0]; if(!has_primary && !has_secondary) { @@ -48,7 +55,6 @@ export function IDEPCIAdapter(cpu, bus, adapter_config) if(has_primary) { this.primary = new IDEDevice(this, adapter_config, 0, pri); } - if(has_secondary) { this.secondary = new IDEDevice(this, adapter_config, 1, sec); } From 1e645e0f06900d50a28c073796b8ccd897bd25c8 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sat, 10 May 2025 15:33:38 +0200 Subject: [PATCH 074/301] improved single-device implementation - immplemented Command and Status register requirements from the standard, ignored all others - rearranged IDE register definitions, added a descriptive comment to each (standard lingo) - fixed the EXECUTE DEVICE DIAGNOSTIC (90h) ATA command --- src/ide.js | 99 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 76 insertions(+), 23 deletions(-) diff --git a/src/ide.js b/src/ide.js index 3e2074ab..497328a5 100644 --- a/src/ide.js +++ b/src/ide.js @@ -25,7 +25,7 @@ const HD_SECTOR_SIZE = 512; * If is_cdrom is defined and true: * - If buffer is defined: create an ATAPI CD-ROM device using buffer as inserted disk * - If buffer is undefined: create an ATAPI CD-ROM device with ejectd disk - * If is_cdrom is undfined or false: + * If is_cdrom is undefined or false: * - If buffer is defined: create an ATA Hard-Disk using buffer as disk image * - If buffer is undefined: create a dummy ATA device representing a missing disk * */ @@ -159,22 +159,13 @@ function IDEDevice(adapter, adapter_config, channel_nr, channel_config) // T /** @type {number} */ this.dma_command = 0; - // Status Registers (across BAR0-BAR3) const cpu = adapter.cpu; - cpu.io.register_read(this.ata_port | 7, this, function() { - dbg_log("lower irq", LOG_DISK); - this.cpu.device_lower_irq(this.irq); - return this.read_status(); - }); - cpu.io.register_read(this.ata_port_high | 2, this, this.read_status); - - // Control Register (BAR1/3) - - cpu.io.register_write(this.ata_port_high | 2, this, this.write_control); - - // ATA/ATAPI Registers (BAR0/2) + // + // Command Block Registers: ata_port + 0...7 (BAR0: 1F0h, BAR2: 170h) + // + // read Data register cpu.io.register_read(this.ata_port | 0, this, function() { return this.current_interface.read_data(1); @@ -186,40 +177,57 @@ function IDEDevice(adapter, adapter_config, channel_nr, channel_config) // T return this.current_interface.read_data(4); }); + // read Error register cpu.io.register_read(this.ata_port | 1, this, function() { dbg_log("Read error: " + h(this.current_interface.error & 0xFF) + " slave=" + (this.current_interface === this.slave), LOG_DISK); return this.current_interface.error & 0xFF; }); + + // read Sector Count register cpu.io.register_read(this.ata_port | 2, this, function() { dbg_log("Read bytecount: " + h(this.current_interface.bytecount & 0xFF), LOG_DISK); return this.current_interface.bytecount & 0xFF; }); + + // read LBA Low register cpu.io.register_read(this.ata_port | 3, this, function() { dbg_log("Read sector: " + h(this.current_interface.sector & 0xFF), LOG_DISK); return this.current_interface.sector & 0xFF; }); + // read LBA Mid register cpu.io.register_read(this.ata_port | 4, this, function() { dbg_log("Read 1F4: " + h(this.current_interface.cylinder_low & 0xFF), LOG_DISK); return this.current_interface.cylinder_low & 0xFF; }); + + // read LBA High register cpu.io.register_read(this.ata_port | 5, this, function() { dbg_log("Read 1F5: " + h(this.current_interface.cylinder_high & 0xFF), LOG_DISK); return this.current_interface.cylinder_high & 0xFF; }); + + // read Device register cpu.io.register_read(this.ata_port | 6, this, function() { dbg_log("Read 1F6", LOG_DISK); return this.current_interface.drive_head & 0xFF; }); - // NOTE: read-handler for Status Register (this.ata_port | 7) is already defined above + // read Status register + cpu.io.register_read(this.ata_port | 7, this, function() { + dbg_log("lower irq", LOG_DISK); + this.cpu.device_lower_irq(this.irq); + return this.read_status(); + }); + + // write Data register cpu.io.register_write(this.ata_port | 0, this, function(data) { this.current_interface.write_data_port8(data); @@ -231,6 +239,7 @@ function IDEDevice(adapter, adapter_config, channel_nr, channel_config) // T this.current_interface.write_data_port32(data); }); + // write Features register cpu.io.register_write(this.ata_port | 1, this, function(data) { dbg_log("1F1/lba_count: " + h(data), LOG_DISK); @@ -240,6 +249,8 @@ function IDEDevice(adapter, adapter_config, channel_nr, channel_config) // T ***/ this.current_interface.lba_count = (this.current_interface.lba_count << 8 | data) & 0xFFFF; }); + + // write Sector Count register cpu.io.register_write(this.ata_port | 2, this, function(data) { dbg_log("1F2/bytecount: " + h(data), LOG_DISK); @@ -252,6 +263,8 @@ function IDEDevice(adapter, adapter_config, channel_nr, channel_config) // T ***/ this.current_interface.bytecount = (this.current_interface.bytecount << 8 | data) & 0xFFFF; }); + + // write LBA Low register cpu.io.register_write(this.ata_port | 3, this, function(data) { dbg_log("1F3/sector: " + h(data), LOG_DISK); @@ -265,6 +278,7 @@ function IDEDevice(adapter, adapter_config, channel_nr, channel_config) // T this.current_interface.sector = (this.current_interface.sector << 8 | data) & 0xFFFF; }); + // write LBA Mid register cpu.io.register_write(this.ata_port | 4, this, function(data) { dbg_log("1F4/sector low: " + h(data), LOG_DISK); @@ -277,6 +291,8 @@ function IDEDevice(adapter, adapter_config, channel_nr, channel_config) // T ***/ this.current_interface.cylinder_low = (this.current_interface.cylinder_low << 8 | data) & 0xFFFF; }); + + // write LBA High register cpu.io.register_write(this.ata_port | 5, this, function(data) { dbg_log("1F5/sector high: " + h(data), LOG_DISK); @@ -289,6 +305,8 @@ function IDEDevice(adapter, adapter_config, channel_nr, channel_config) // T ***/ this.current_interface.cylinder_high = (this.current_interface.cylinder_high << 8 | data) & 0xFFFF; }); + + // write Device register cpu.io.register_write(this.ata_port | 6, this, function(data) { var slave = data & 0x10; @@ -316,6 +334,7 @@ function IDEDevice(adapter, adapter_config, channel_nr, channel_config) // T this.current_interface.head = data & 0xF; }); + // write Command register cpu.io.register_write(this.ata_port | 7, this, function(data) { dbg_log("lower irq", LOG_DISK); @@ -325,7 +344,20 @@ function IDEDevice(adapter, adapter_config, channel_nr, channel_config) // T this.current_interface.ata_command(data); }); - // Bus Master Registers (BAR4) + // + // Control Block Register: ata_port_high + 0...3 (BAR1: 3F4h, BAR3: 374h) + // + + // read Alternate Status register + cpu.io.register_read(this.ata_port_high | 2, this, this.read_status); + + // write Device Control register + cpu.io.register_write(this.ata_port_high | 2, this, this.write_control); + + // + // Bus Master Registers: bus_master_port + 0...16 (BAR4, lower 8 for primary and upper 8 for secondary channel) + // + const bus_master_port = channel_config.bus_master_port; cpu.io.register_read(bus_master_port, this, @@ -512,6 +544,7 @@ IDEDevice.prototype.set_state = function(state) function IDEInterface(device, cpu, buffer, is_cd, device_nr, interface_nr, bus) { this.device = device; + this.name = device.name + ":" + interface_nr; /** @const @type {BusConnector} */ this.bus = bus; @@ -522,6 +555,12 @@ function IDEInterface(device, cpu, buffer, is_cd, device_nr, interface_nr, bus) */ this.nr = device_nr; + /** + * @const + * @type {number} + */ + this.interface_nr = interface_nr; + /** @const @type {CPU} */ this.cpu = cpu; @@ -725,12 +764,9 @@ IDEInterface.prototype.ata_command = function(cmd) { dbg_log("ATA Command: " + h(cmd) + " slave=" + (this.drive_head >> 4 & 1), LOG_DISK); - if((!this.buffer && cmd != 0xA1 && cmd != 0xEC && cmd != 0xA0)) + if(!this.drive_connected && cmd != 0x90) { - dbg_log("abort: No buffer", LOG_DISK); - this.error = 4; - this.status = 0x41; - this.push_irq(); + dbg_log("ignored: No slave drive connected", LOG_DISK); return; } @@ -801,9 +837,26 @@ IDEInterface.prototype.ata_command = function(cmd) case 0x90: // execute device diagnostic - this.push_irq(); - this.error = 0x101; + // assign diagnostics code (used to be 0x101?) to error register + if(this.interface_nr === 0) { + // master drive is currently selected + if(this.device.slave.drive_connected) { + this.error = 0x01; // Master drive passed, slave passed or not present + } + else { + this.error = 0x81; // Master drive passed, slave failed + } + } + else { + if(this.device.slave.drive_connected) { + this.error = 0x01; // Slave drive passed + } + else { + this.error = 0x00; // Slave drive failed + } + } this.status = 0x50; + this.push_irq(); break; case 0x91: From 17d92c56d638aeead3f70af3689d22a4096bf337 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sat, 10 May 2025 16:07:12 +0200 Subject: [PATCH 075/301] fixed eslint errors --- src/ide.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ide.js b/src/ide.js index 497328a5..709734df 100644 --- a/src/ide.js +++ b/src/ide.js @@ -713,7 +713,7 @@ IDEInterface.prototype.set_cdrom = function(buffer) rtc.cmos_read(CMOS_BIOS_DISKTRANSFLAG) | 1 << this.nr * 4); rtc.cmos_write(CMOS_DISK_DATA, rtc.cmos_read(CMOS_DISK_DATA) & 0x0F | 0xF0); - var reg = this.nr == 0 ? CMOS_DISK_DRIVE1_CYL : CMOS_DISK_DRIVE2_CYL; + var reg = this.nr === 0 ? CMOS_DISK_DRIVE1_CYL : CMOS_DISK_DRIVE2_CYL; rtc.cmos_write(reg + 0, this.cylinder_count & 0xFF); rtc.cmos_write(reg + 1, this.cylinder_count >> 8 & 0xFF); rtc.cmos_write(reg + 2, this.head_count & 0xFF); @@ -764,7 +764,7 @@ IDEInterface.prototype.ata_command = function(cmd) { dbg_log("ATA Command: " + h(cmd) + " slave=" + (this.drive_head >> 4 & 1), LOG_DISK); - if(!this.drive_connected && cmd != 0x90) + if(!this.drive_connected && cmd !== 0x90) { dbg_log("ignored: No slave drive connected", LOG_DISK); return; @@ -1010,11 +1010,11 @@ IDEInterface.prototype.atapi_handle = function() this.data_pointer = 0; this.current_atapi_command = this.data[0]; - if(!this.buffer && (this.current_atapi_command == 0x25 || - this.current_atapi_command == 0x28 || - this.current_atapi_command == 0x42 || - this.current_atapi_command == 0x43 || - this.current_atapi_command == 0x51)) + if(!this.buffer && (this.current_atapi_command === 0x25 || + this.current_atapi_command === 0x28 || + this.current_atapi_command === 0x42 || + this.current_atapi_command === 0x43 || + this.current_atapi_command === 0x51)) { dbg_log("dev "+this.device.name+" CD read-related action: no buffer", LOG_DISK); this.status = 0x51; From 9c929e154762e56dcbcf5ea67487f719dcd12ae9 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sat, 10 May 2025 16:38:50 +0200 Subject: [PATCH 076/301] fixed set/get_state()-related error --- src/ide.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ide.js b/src/ide.js index 709734df..8036f4d6 100644 --- a/src/ide.js +++ b/src/ide.js @@ -49,7 +49,7 @@ export function IDEPCIAdapter(cpu, bus, adapter_config) const has_primary = adapter_config && adapter_config[0][0]; const has_secondary = adapter_config && adapter_config[1][0]; if(!has_primary && !has_secondary) { - DEBUG && Object.seal(this); + Object.seal(this); return; } if(has_primary) { @@ -115,7 +115,7 @@ export function IDEPCIAdapter(cpu, bus, adapter_config) ]; cpu.devices.pci.register_device(this); - DEBUG && Object.seal(this); + Object.seal(this); } /** @@ -371,7 +371,7 @@ function IDEDevice(adapter, adapter_config, channel_nr, channel_config) // T cpu.io.register_read(bus_master_port | 4, this, undefined, undefined, this.dma_read_addr); cpu.io.register_write(bus_master_port | 4, this, undefined, undefined, this.dma_set_addr); - Object.seal(this); + DEBUG && Object.seal(this); } IDEDevice.prototype.read_status = function() @@ -508,7 +508,7 @@ IDEDevice.prototype.get_state = function() state[1] = this.slave; state[2] = this.ata_port; state[3] = this.irq; - state[4] = this.pci_id; + // state[4] = this.pci_id; state[5] = this.ata_port_high; // state[6] = this.bus_master_port; state[7] = this.name; @@ -526,7 +526,7 @@ IDEDevice.prototype.set_state = function(state) this.slave.set_state(state[1]); this.ata_port = state[2]; this.irq = state[3]; - this.pci_id = state[4]; + // this.pci_id = state[4]; this.ata_port_high = state[5]; // this.bus_master_port = state[6]; this.name = state[7]; From e538b849ce74850bf805b6be48f0be7a9e83eb97 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sun, 11 May 2025 05:43:24 +0200 Subject: [PATCH 077/301] renamed class IDEDevice to IDEChannel In v86, the name "device" is already used in the context of PCI, and additionally, in the ATA/ATAPI standard the term "device" is used for disk drives. The name used for this entity (IDEDevice) in the ATA/ATAPI standard is "channel", and since "channel" is not otherwise used in ide.js it seems right to rename IDEDevice to IDEChannel. --- src/ide.js | 83 +++++++++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/src/ide.js b/src/ide.js index 8036f4d6..15a0d178 100644 --- a/src/ide.js +++ b/src/ide.js @@ -26,8 +26,10 @@ const HD_SECTOR_SIZE = 512; * - If buffer is defined: create an ATAPI CD-ROM device using buffer as inserted disk * - If buffer is undefined: create an ATAPI CD-ROM device with ejectd disk * If is_cdrom is undefined or false: - * - If buffer is defined: create an ATA Hard-Disk using buffer as disk image - * - If buffer is undefined: create a dummy ATA device representing a missing disk + * - If buffer is defined: create an ATA Hard-Disk device using buffer as disk image + * - If buffer is undefined: represents a missing device + * + * A slave drive can only exist if a master drive also exists. * */ export function IDEPCIAdapter(cpu, bus, adapter_config) { @@ -45,7 +47,6 @@ export function IDEPCIAdapter(cpu, bus, adapter_config) this.secondary = undefined; this.channels = [undefined, undefined]; - // In each channel, a slave drive can only exist if also a master drive exists. const has_primary = adapter_config && adapter_config[0][0]; const has_secondary = adapter_config && adapter_config[1][0]; if(!has_primary && !has_secondary) { @@ -53,10 +54,10 @@ export function IDEPCIAdapter(cpu, bus, adapter_config) return; } if(has_primary) { - this.primary = new IDEDevice(this, adapter_config, 0, pri); + this.primary = new IDEChannel(this, adapter_config, 0, pri); } if(has_secondary) { - this.secondary = new IDEDevice(this, adapter_config, 1, sec); + this.secondary = new IDEChannel(this, adapter_config, 1, sec); } this.channels = [this.primary, this.secondary]; @@ -123,7 +124,7 @@ export function IDEPCIAdapter(cpu, bus, adapter_config) * @param {IDEPCIAdapter} adapter * @param {number} channel_nr * */ -function IDEDevice(adapter, adapter_config, channel_nr, channel_config) // TODO: rename this class to IDEChannel +function IDEChannel(adapter, adapter_config, channel_nr, channel_config) { this.adapter = adapter; this.cpu = adapter.cpu; @@ -374,16 +375,14 @@ function IDEDevice(adapter, adapter_config, channel_nr, channel_config) // T DEBUG && Object.seal(this); } -IDEDevice.prototype.read_status = function() +IDEChannel.prototype.read_status = function() { - /// temporary hack to indicate missing slave drive to host - /// const ret = this.current_interface.status; const ret = this.current_interface.drive_connected ? this.current_interface.status : 0; dbg_log("dev "+this.name+" ATA read status: " + h(ret, 2), LOG_DISK); return ret; }; -IDEDevice.prototype.write_control = function(data) +IDEChannel.prototype.write_control = function(data) { dbg_log("set device control: " + h(data, 2) + " interrupts " + ((data & 2) ? "disabled" : "enabled"), LOG_DISK); @@ -401,42 +400,42 @@ IDEDevice.prototype.write_control = function(data) this.device_control = data; }; -IDEDevice.prototype.dma_read_addr = function() +IDEChannel.prototype.dma_read_addr = function() { dbg_log("dma get address: " + h(this.prdt_addr, 8), LOG_DISK); return this.prdt_addr; }; -IDEDevice.prototype.dma_set_addr = function(data) +IDEChannel.prototype.dma_set_addr = function(data) { dbg_log("dma set address: " + h(data, 8), LOG_DISK); this.prdt_addr = data; }; -IDEDevice.prototype.dma_read_status = function() +IDEChannel.prototype.dma_read_status = function() { dbg_log("DMA read status: " + h(this.dma_status), LOG_DISK); return this.dma_status; }; -IDEDevice.prototype.dma_write_status = function(value) +IDEChannel.prototype.dma_write_status = function(value) { dbg_log("DMA set status: " + h(value), LOG_DISK); this.dma_status &= ~(value & 6); }; -IDEDevice.prototype.dma_read_command = function() +IDEChannel.prototype.dma_read_command = function() { return this.dma_read_command8() | this.dma_read_status() << 16; }; -IDEDevice.prototype.dma_read_command8 = function() +IDEChannel.prototype.dma_read_command8 = function() { dbg_log("DMA read command: " + h(this.dma_command), LOG_DISK); return this.dma_command; }; -IDEDevice.prototype.dma_write_command = function(value) +IDEChannel.prototype.dma_write_command = function(value) { dbg_log("DMA write command: " + h(value), LOG_DISK); @@ -444,7 +443,7 @@ IDEDevice.prototype.dma_write_command = function(value) this.dma_write_status(value >> 16 & 0xFF); }; -IDEDevice.prototype.dma_write_command8 = function(value) +IDEChannel.prototype.dma_write_command8 = function(value) { dbg_log("DMA write command8: " + h(value), LOG_DISK); @@ -491,7 +490,7 @@ IDEDevice.prototype.dma_write_command8 = function(value) } }; -IDEDevice.prototype.push_irq = function() +IDEChannel.prototype.push_irq = function() { if((this.device_control & 2) === 0) { @@ -501,7 +500,7 @@ IDEDevice.prototype.push_irq = function() } }; -IDEDevice.prototype.get_state = function() +IDEChannel.prototype.get_state = function() { var state = []; state[0] = this.master; @@ -520,7 +519,7 @@ IDEDevice.prototype.get_state = function() return state; }; -IDEDevice.prototype.set_state = function(state) +IDEChannel.prototype.set_state = function(state) { this.master.set_state(state[0]); this.slave.set_state(state[1]); @@ -541,10 +540,10 @@ IDEDevice.prototype.set_state = function(state) /** * @constructor */ -function IDEInterface(device, cpu, buffer, is_cd, device_nr, interface_nr, bus) +function IDEInterface(channel, cpu, buffer, is_cd, channel_nr, interface_nr, bus) { - this.device = device; - this.name = device.name + ":" + interface_nr; + this.channel = channel; + this.name = channel.name + ":" + interface_nr; /** @const @type {BusConnector} */ this.bus = bus; @@ -553,7 +552,7 @@ function IDEInterface(device, cpu, buffer, is_cd, device_nr, interface_nr, bus) * @const * @type {number} */ - this.nr = device_nr; + this.nr = channel_nr; /** * @const @@ -724,7 +723,7 @@ IDEInterface.prototype.set_cdrom = function(buffer) rtc.cmos_write(reg + 7, this.cylinder_count >> 8 & 0xFF); rtc.cmos_write(reg + 8, this.sectors_per_track & 0xFF); - if(this.device.cpu) { + if(this.channel.cpu) { this.push_irq(); } }; @@ -757,7 +756,7 @@ IDEInterface.prototype.device_reset = function() IDEInterface.prototype.push_irq = function() { - this.device.push_irq(); + this.channel.push_irq(); }; IDEInterface.prototype.ata_command = function(cmd) @@ -840,7 +839,7 @@ IDEInterface.prototype.ata_command = function(cmd) // assign diagnostics code (used to be 0x101?) to error register if(this.interface_nr === 0) { // master drive is currently selected - if(this.device.slave.drive_connected) { + if(this.channel.slave.drive_connected) { this.error = 0x01; // Master drive passed, slave passed or not present } else { @@ -848,7 +847,7 @@ IDEInterface.prototype.ata_command = function(cmd) } } else { - if(this.device.slave.drive_connected) { + if(this.channel.slave.drive_connected) { this.error = 0x01; // Slave drive passed } else { @@ -1016,7 +1015,7 @@ IDEInterface.prototype.atapi_handle = function() this.current_atapi_command === 0x43 || this.current_atapi_command === 0x51)) { - dbg_log("dev "+this.device.name+" CD read-related action: no buffer", LOG_DISK); + dbg_log("dev "+this.channel.name+" CD read-related action: no buffer", LOG_DISK); this.status = 0x51; this.error = 0x21; this.data_allocate(0); @@ -1318,14 +1317,14 @@ IDEInterface.prototype.atapi_read = function(cmd) if(!this.buffer) { - dbg_assert(false, "dev "+this.device.name+" CD read: no buffer", LOG_DISK); + dbg_assert(false, "dev "+this.channel.name+" CD read: no buffer", LOG_DISK); this.status = 0xFF; this.error = 0x41; this.push_irq(); } else if(start >= this.buffer.byteLength) { - dbg_assert(false, "dev "+this.device.name+" CD read: Outside of disk end=" + h(start + byte_count) + + dbg_assert(false, "dev "+this.channel.name+" CD read: Outside of disk end=" + h(start + byte_count) + " size=" + h(this.buffer.byteLength), LOG_DISK); this.status = 0xFF; @@ -1412,7 +1411,7 @@ IDEInterface.prototype.atapi_read_dma = function(cmd) IDEInterface.prototype.do_atapi_dma = function() { - if((this.device.dma_status & 1) === 0) + if((this.channel.dma_status & 1) === 0) { dbg_log("do_atapi_dma: Status not set", LOG_DISK); return; @@ -1426,7 +1425,7 @@ IDEInterface.prototype.do_atapi_dma = function() dbg_log("atapi dma transfer len=" + this.data_length, LOG_DISK); - var prdt_start = this.device.prdt_addr; + var prdt_start = this.channel.prdt_addr; var offset = 0; var data = this.data; @@ -1461,7 +1460,7 @@ IDEInterface.prototype.do_atapi_dma = function() dbg_log("end offset=" + offset, LOG_DISK); this.status = 0x50; - this.device.dma_status &= ~1; + this.channel.dma_status &= ~1; this.bytecount = this.bytecount & ~7 | 3; this.push_irq(); }; @@ -1764,7 +1763,7 @@ IDEInterface.prototype.ata_read_sectors_dma = function(cmd) } this.status = 0x58; - this.device.dma_status |= 1; + this.channel.dma_status |= 1; }; IDEInterface.prototype.do_ata_read_sectors_dma = function() @@ -1782,13 +1781,13 @@ IDEInterface.prototype.do_ata_read_sectors_dma = function() this.report_read_start(); - var orig_prdt_start = this.device.prdt_addr; + var orig_prdt_start = this.channel.prdt_addr; this.read_buffer(start, byte_count, (data) => { //setTimeout(function() { dbg_log("do_ata_read_sectors_dma: Data arrived", LOG_DISK); - var prdt_start = this.device.prdt_addr; + var prdt_start = this.channel.prdt_addr; var offset = 0; dbg_assert(orig_prdt_start === prdt_start); @@ -1817,7 +1816,7 @@ IDEInterface.prototype.do_ata_read_sectors_dma = function() this.ata_advance(this.current_command, count); this.status = 0x50; - this.device.dma_status &= ~1; + this.channel.dma_status &= ~1; this.current_command = -1; this.report_read_end(byte_count); @@ -1881,7 +1880,7 @@ IDEInterface.prototype.ata_write_sectors_dma = function(cmd) } this.status = 0x58; - this.device.dma_status |= 1; + this.channel.dma_status |= 1; }; IDEInterface.prototype.do_ata_write_sectors_dma = function() @@ -1895,7 +1894,7 @@ IDEInterface.prototype.do_ata_write_sectors_dma = function() var byte_count = count * this.sector_size; var start = lba * this.sector_size; - var prdt_start = this.device.prdt_addr; + var prdt_start = this.channel.prdt_addr; var offset = 0; dbg_log("prdt addr: " + h(prdt_start, 8), LOG_DISK); @@ -1938,7 +1937,7 @@ IDEInterface.prototype.do_ata_write_sectors_dma = function() this.ata_advance(this.current_command, count); this.status = 0x50; this.push_irq(); - this.device.dma_status &= ~1; + this.channel.dma_status &= ~1; this.current_command = -1; }); From 2564e3095047fcc4a081bd9aaa5044fcfaee84a9 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sun, 11 May 2025 11:00:32 +0200 Subject: [PATCH 078/301] implemented ATA command GET_MEDIA_STATUS --- src/ide.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/ide.js b/src/ide.js index 15a0d178..b42f7fc5 100644 --- a/src/ide.js +++ b/src/ide.js @@ -568,6 +568,9 @@ function IDEInterface(channel, cpu, buffer, is_cd, channel_nr, interface_nr, bus /** @type {boolean} */ this.drive_connected = is_cd || buffer; + /** @type {boolean} */ + this.media_changed = false; + /** @type {number} */ this.sector_size = is_cd ? CDROM_SECTOR_SIZE : HD_SECTOR_SIZE; @@ -646,8 +649,8 @@ function IDEInterface(channel, cpu, buffer, is_cd, channel_nr, interface_nr, bus this.in_progress_io_ids = new Set(); this.cancelled_io_ids = new Set(); - // set optional buffer for both ATA and ATAPI this.set_cdrom(buffer); + this.media_changed = false; Object.seal(this); } @@ -656,6 +659,7 @@ IDEInterface.prototype.eject = function() { if(this.is_atapi && this.buffer) { + this.media_changed = true; this.buffer = null; this.status = 0x59; this.error = 0x60; @@ -670,6 +674,7 @@ IDEInterface.prototype.set_cdrom = function(buffer) return; } + this.media_changed = true; this.buffer = buffer; if(this.is_atapi) { @@ -922,9 +927,19 @@ IDEInterface.prototype.ata_command = function(cmd) break; case 0xDA: - dbg_log("Unimplemented: get media status", LOG_DISK); - this.status = 0x41; - this.error = 4; + dbg_log("ATA get media status", LOG_DISK); + this.status = 0x50; + this.error = 0; + if(this.is_atapi) { + if(!this.buffer) { + this.error |= 0x02; // NM: No Media + } + if(this.media_changed) { + this.error |= 0x20; // MC: Media Change + this.media_changed = false; + } + this.error |= 0x40; // WP: Write Protect + } this.push_irq(); break; From b9e6445baa6739ed748af4e7e720d1cd5bfe88b8 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sun, 11 May 2025 16:19:58 +0200 Subject: [PATCH 079/301] improved dbg_log() formatting Merged and improved on the work from JoeOsborn in respect to formatting in dbg_log() output. Used register names from the standard for better readability of the output, and added a few missing logs. --- src/ide.js | 293 +++++++++++++++++++++++++++-------------------------- 1 file changed, 151 insertions(+), 142 deletions(-) diff --git a/src/ide.js b/src/ide.js index b42f7fc5..33426548 100644 --- a/src/ide.js +++ b/src/ide.js @@ -132,6 +132,7 @@ function IDEChannel(adapter, adapter_config, channel_nr, channel_config) this.ata_port = channel_config.ata_port; this.ata_port_high = channel_config.ata_port_high; this.irq = channel_config.irq; + this.name = "ide" + channel_nr; const create_interface = interface_nr => { const config = adapter_config && adapter_config[channel_nr] ? @@ -146,7 +147,8 @@ function IDEChannel(adapter, adapter_config, channel_nr, channel_config) this.interfaces = [this.master, this.slave]; this.current_interface = this.master; - this.name = "ide" + channel_nr; + this.master.init_interface(); + this.slave.init_interface(); /** @type {number} */ this.device_control = 2; @@ -178,57 +180,55 @@ function IDEChannel(adapter, adapter_config, channel_nr, channel_config) return this.current_interface.read_data(4); }); - // read Error register cpu.io.register_read(this.ata_port | 1, this, function() { - dbg_log("Read error: " + h(this.current_interface.error & 0xFF) + - " slave=" + (this.current_interface === this.slave), LOG_DISK); + dbg_log(this.current_interface.name + ": read Error register: " + + h(this.current_interface.error & 0xFF) + " slave=" + + (this.current_interface === this.slave), LOG_DISK); return this.current_interface.error & 0xFF; }); - // read Sector Count register cpu.io.register_read(this.ata_port | 2, this, function() { - dbg_log("Read bytecount: " + h(this.current_interface.bytecount & 0xFF), LOG_DISK); + dbg_log(this.current_interface.name + ": read Sector Count register: " + + h(this.current_interface.bytecount & 0xFF), LOG_DISK); return this.current_interface.bytecount & 0xFF; }); - // read LBA Low register cpu.io.register_read(this.ata_port | 3, this, function() { - dbg_log("Read sector: " + h(this.current_interface.sector & 0xFF), LOG_DISK); + dbg_log(this.current_interface.name + ": read LBA Low register: " + + h(this.current_interface.sector & 0xFF), LOG_DISK); return this.current_interface.sector & 0xFF; }); - // read LBA Mid register cpu.io.register_read(this.ata_port | 4, this, function() { - dbg_log("Read 1F4: " + h(this.current_interface.cylinder_low & 0xFF), LOG_DISK); + dbg_log(this.current_interface.name + ": read LBA Mid register: " + + h(this.current_interface.cylinder_low & 0xFF), LOG_DISK); return this.current_interface.cylinder_low & 0xFF; }); - // read LBA High register cpu.io.register_read(this.ata_port | 5, this, function() { - dbg_log("Read 1F5: " + h(this.current_interface.cylinder_high & 0xFF), LOG_DISK); + dbg_log(this.current_interface.name + ": read LBA High register: " + + h(this.current_interface.cylinder_high & 0xFF), LOG_DISK); return this.current_interface.cylinder_high & 0xFF; }); - // read Device register cpu.io.register_read(this.ata_port | 6, this, function() { - dbg_log("Read 1F6", LOG_DISK); + dbg_log(this.current_interface.name + ": read Device register", LOG_DISK); return this.current_interface.drive_head & 0xFF; }); - // read Status register cpu.io.register_read(this.ata_port | 7, this, function() { - dbg_log("lower irq", LOG_DISK); + dbg_log(this.current_interface.name + ": read Status register", LOG_DISK); + dbg_log(this.current_interface.name + ": lower IRQ " + this.irq, LOG_DISK); this.cpu.device_lower_irq(this.irq); return this.read_status(); }); - // write Data register cpu.io.register_write(this.ata_port | 0, this, function(data) { this.current_interface.write_data_port8(data); @@ -240,10 +240,9 @@ function IDEChannel(adapter, adapter_config, channel_nr, channel_config) this.current_interface.write_data_port32(data); }); - // write Features register cpu.io.register_write(this.ata_port | 1, this, function(data) { - dbg_log("1F1/lba_count: " + h(data), LOG_DISK); + dbg_log(this.current_interface.name + ": write Features register: " + h(data), LOG_DISK); /*** * this.master.lba_count = (this.master.lba_count << 8 | data) & 0xFFFF; * this.slave.lba_count = (this.slave.lba_count << 8 | data) & 0xFFFF; @@ -251,10 +250,9 @@ function IDEChannel(adapter, adapter_config, channel_nr, channel_config) this.current_interface.lba_count = (this.current_interface.lba_count << 8 | data) & 0xFFFF; }); - // write Sector Count register cpu.io.register_write(this.ata_port | 2, this, function(data) { - dbg_log("1F2/bytecount: " + h(data), LOG_DISK); + dbg_log(this.current_interface.name + ": write Sector Count register: " + h(data), LOG_DISK); /*** * this.master.bytecount = (this.master.bytecount << 8 | data) & 0xFFFF; * if(slave_buffer) @@ -265,10 +263,9 @@ function IDEChannel(adapter, adapter_config, channel_nr, channel_config) this.current_interface.bytecount = (this.current_interface.bytecount << 8 | data) & 0xFFFF; }); - // write LBA Low register cpu.io.register_write(this.ata_port | 3, this, function(data) { - dbg_log("1F3/sector: " + h(data), LOG_DISK); + dbg_log(this.current_interface.name + ": write LBA Low register: " + h(data), LOG_DISK); /*** * this.master.sector = (this.master.sector << 8 | data) & 0xFFFF; * if(slave_buffer) @@ -279,10 +276,9 @@ function IDEChannel(adapter, adapter_config, channel_nr, channel_config) this.current_interface.sector = (this.current_interface.sector << 8 | data) & 0xFFFF; }); - // write LBA Mid register cpu.io.register_write(this.ata_port | 4, this, function(data) { - dbg_log("1F4/sector low: " + h(data), LOG_DISK); + dbg_log(this.current_interface.name + ": write LBA Mid register: " + h(data), LOG_DISK); /*** * this.master.cylinder_low = (this.master.cylinder_low << 8 | data) & 0xFFFF; * if(slave_buffer) @@ -293,10 +289,9 @@ function IDEChannel(adapter, adapter_config, channel_nr, channel_config) this.current_interface.cylinder_low = (this.current_interface.cylinder_low << 8 | data) & 0xFFFF; }); - // write LBA High register cpu.io.register_write(this.ata_port | 5, this, function(data) { - dbg_log("1F5/sector high: " + h(data), LOG_DISK); + dbg_log(this.current_interface.name + ": write LBA High register: " + h(data), LOG_DISK); /*** * this.master.cylinder_high = (this.master.cylinder_high << 8 | data) & 0xFFFF; * if(slave_buffer) @@ -307,22 +302,24 @@ function IDEChannel(adapter, adapter_config, channel_nr, channel_config) this.current_interface.cylinder_high = (this.current_interface.cylinder_high << 8 | data) & 0xFFFF; }); - // write Device register cpu.io.register_write(this.ata_port | 6, this, function(data) { var slave = data & 0x10; - var mode = data & 0xE0; + // var mode = data & 0xE0; // unused - dbg_log("1F6/drive: " + h(data, 2), LOG_DISK); - - if(slave) + dbg_log(this.current_interface.name + ": write Device register: " + h(data, 2), LOG_DISK); + if((slave && this.current_interface === this.master) || (!slave && this.current_interface === this.slave)) { - dbg_log("Slave", LOG_DISK); - this.current_interface = this.slave; - } - else - { - this.current_interface = this.master; + if(slave) + { + dbg_log(this.current_interface.name + ": select slave device", LOG_DISK); + this.current_interface = this.slave; + } + else + { + dbg_log(this.current_interface.name + ": select master device", LOG_DISK); + this.current_interface = this.master; + } } /*** * this.master.drive_head = data; @@ -335,10 +332,10 @@ function IDEChannel(adapter, adapter_config, channel_nr, channel_config) this.current_interface.head = data & 0xF; }); - // write Command register cpu.io.register_write(this.ata_port | 7, this, function(data) { - dbg_log("lower irq", LOG_DISK); + dbg_log(this.current_interface.name + ": write Command register", LOG_DISK); + dbg_log(this.current_interface.name + ": lower IRQ " + this.irq, LOG_DISK); this.cpu.device_lower_irq(this.irq); // clear error and DF bits this.current_interface.status &= ~(1 | (1 << 5)); @@ -378,19 +375,19 @@ function IDEChannel(adapter, adapter_config, channel_nr, channel_config) IDEChannel.prototype.read_status = function() { const ret = this.current_interface.drive_connected ? this.current_interface.status : 0; - dbg_log("dev "+this.name+" ATA read status: " + h(ret, 2), LOG_DISK); + dbg_log(this.current_interface.name + ": ATA read status: " + h(ret, 2), LOG_DISK); return ret; }; IDEChannel.prototype.write_control = function(data) { - dbg_log("set device control: " + h(data, 2) + " interrupts " + - ((data & 2) ? "disabled" : "enabled"), LOG_DISK); + dbg_log(this.current_interface.name + ": write Device control register: " + + h(data, 2) + " interrupts " + ((data & 2) ? "disabled" : "enabled"), LOG_DISK); if(data & 0x04) { - dbg_log("Reset via control port", LOG_DISK); - + dbg_log(this.current_interface.name + ": reset via control port", LOG_DISK); + dbg_log(this.current_interface.name + ": lower IRQ " + this.irq, LOG_DISK); this.cpu.device_lower_irq(this.irq); this.master.device_reset(); @@ -402,25 +399,25 @@ IDEChannel.prototype.write_control = function(data) IDEChannel.prototype.dma_read_addr = function() { - dbg_log("dma get address: " + h(this.prdt_addr, 8), LOG_DISK); + dbg_log(this.current_interface.name + ": DMA get address: " + h(this.prdt_addr, 8), LOG_DISK); return this.prdt_addr; }; IDEChannel.prototype.dma_set_addr = function(data) { - dbg_log("dma set address: " + h(data, 8), LOG_DISK); + dbg_log(this.current_interface.name + ": DMA set address: " + h(data, 8), LOG_DISK); this.prdt_addr = data; }; IDEChannel.prototype.dma_read_status = function() { - dbg_log("DMA read status: " + h(this.dma_status), LOG_DISK); + dbg_log(this.current_interface.name + ": DMA read status: " + h(this.dma_status), LOG_DISK); return this.dma_status; }; IDEChannel.prototype.dma_write_status = function(value) { - dbg_log("DMA set status: " + h(value), LOG_DISK); + dbg_log(this.current_interface.name + ": DMA write status: " + h(value), LOG_DISK); this.dma_status &= ~(value & 6); }; @@ -431,13 +428,13 @@ IDEChannel.prototype.dma_read_command = function() IDEChannel.prototype.dma_read_command8 = function() { - dbg_log("DMA read command: " + h(this.dma_command), LOG_DISK); + dbg_log(this.current_interface.name + ": DMA read command: " + h(this.dma_command), LOG_DISK); return this.dma_command; }; IDEChannel.prototype.dma_write_command = function(value) { - dbg_log("DMA write command: " + h(value), LOG_DISK); + dbg_log(this.current_interface.name + ": DMA write command: " + h(value), LOG_DISK); this.dma_write_command8(value & 0xFF); this.dma_write_status(value >> 16 & 0xFF); @@ -445,7 +442,7 @@ IDEChannel.prototype.dma_write_command = function(value) IDEChannel.prototype.dma_write_command8 = function(value) { - dbg_log("DMA write command8: " + h(value), LOG_DISK); + dbg_log(this.current_interface.name + ": DMA write command8: " + h(value), LOG_DISK); const old_command = this.dma_command; this.dma_command = value & 0x09; @@ -480,9 +477,9 @@ IDEChannel.prototype.dma_write_command8 = function(value) break; default: - dbg_log("Spurious dma command write, current command: " + + dbg_log(this.current_interface.name + ": spurious DMA command write, current command: " + h(this.current_interface.current_command), LOG_DISK); - dbg_log("dev "+this.name+" DMA clear status 1 bit, set status 2 bit", LOG_DISK); + dbg_log(this.current_interface.name + ": DMA clear status 1 bit, set status 2 bit", LOG_DISK); this.dma_status &= ~1; this.dma_status |= 2; this.push_irq(); @@ -494,7 +491,7 @@ IDEChannel.prototype.push_irq = function() { if((this.device_control & 2) === 0) { - dbg_log("push irq", LOG_DISK); + dbg_log(this.current_interface.name + ": push IRQ " + this.irq, LOG_DISK); this.dma_status |= 4; this.cpu.device_raise_irq(this.irq); } @@ -536,14 +533,13 @@ IDEChannel.prototype.set_state = function(state) this.dma_command = state[12]; }; - /** * @constructor */ function IDEInterface(channel, cpu, buffer, is_cd, channel_nr, interface_nr, bus) { this.channel = channel; - this.name = channel.name + ":" + interface_nr; + this.name = channel.name + "." + interface_nr; /** @const @type {BusConnector} */ this.bus = bus; @@ -649,12 +645,17 @@ function IDEInterface(channel, cpu, buffer, is_cd, channel_nr, interface_nr, bus this.in_progress_io_ids = new Set(); this.cancelled_io_ids = new Set(); - this.set_cdrom(buffer); - this.media_changed = false; - - Object.seal(this); + // caller must call this.init_interface() to complete object initialization + this.inital_buffer = buffer; } +IDEInterface.prototype.init_interface = function() +{ + this.set_disk_buffer(this.inital_buffer); + delete this.inital_buffer; + Object.seal(this); +}; + IDEInterface.prototype.eject = function() { if(this.is_atapi && this.buffer) @@ -668,13 +669,21 @@ IDEInterface.prototype.eject = function() }; IDEInterface.prototype.set_cdrom = function(buffer) +{ + if(this.is_atapi && buffer) + { + this.set_disk_buffer(buffer); + this.media_changed = true; + } +}; + +IDEInterface.prototype.set_disk_buffer = function(buffer) { if(!buffer) { return; } - this.media_changed = true; this.buffer = buffer; if(this.is_atapi) { @@ -685,7 +694,7 @@ IDEInterface.prototype.set_cdrom = function(buffer) if(this.sector_count !== (this.sector_count | 0)) { - dbg_log("Warning: Disk size not aligned with sector size", LOG_DISK); + dbg_log(this.name + ": Warning: disk size not aligned with sector size", LOG_DISK); this.sector_count = Math.ceil(this.sector_count); } @@ -707,7 +716,7 @@ IDEInterface.prototype.set_cdrom = function(buffer) if(this.cylinder_count !== (this.cylinder_count | 0)) { - dbg_log("Warning: Rounding up cylinder count. Choose different head number", LOG_DISK); + dbg_log(this.name + ": Warning: rounding up cylinder count, choose different head number", LOG_DISK); this.cylinder_count = Math.floor(this.cylinder_count); } @@ -766,11 +775,11 @@ IDEInterface.prototype.push_irq = function() IDEInterface.prototype.ata_command = function(cmd) { - dbg_log("ATA Command: " + h(cmd) + " slave=" + (this.drive_head >> 4 & 1), LOG_DISK); + dbg_log(this.name + ": ATA Command: " + h(cmd) + " slave=" + (this.drive_head >> 4 & 1), LOG_DISK); if(!this.drive_connected && cmd !== 0x90) { - dbg_log("ignored: No slave drive connected", LOG_DISK); + dbg_log(this.name + ": ATA command ignored: No slave drive connected", LOG_DISK); return; } @@ -780,7 +789,7 @@ IDEInterface.prototype.ata_command = function(cmd) switch(cmd) { case 0x08: - dbg_log("ATA device reset", LOG_DISK); + dbg_log(this.name + ": ATA device reset", LOG_DISK); this.data_pointer = 0; this.data_end = 0; this.data_length = 0; @@ -789,14 +798,14 @@ IDEInterface.prototype.ata_command = function(cmd) break; case 0x10: - // calibrate drive + dbg_log(this.name + ": ATA calibrate drive", LOG_DISK); // ATA/ATAPI-6: obsolete this.status = 0x50; this.cylinder_low = 0; this.push_irq(); break; case 0xF8: - // read native max address + dbg_log(this.name + ": ATA read native max address", LOG_DISK); this.status = 0x50; var last_sector = this.sector_count - 1; this.sector = last_sector & 0xFF; @@ -807,7 +816,7 @@ IDEInterface.prototype.ata_command = function(cmd) break; case 0x27: - // read native max address ext + dbg_log(this.name + ": ATA read native max address ext", LOG_DISK); this.status = 0x50; var last_sector = this.sector_count - 1; this.sector = last_sector & 0xFF; @@ -840,7 +849,7 @@ IDEInterface.prototype.ata_command = function(cmd) break; case 0x90: - // execute device diagnostic + dbg_log(this.name + ": ATA execute device diagnostic", LOG_DISK); // assign diagnostics code (used to be 0x101?) to error register if(this.interface_nr === 0) { // master drive is currently selected @@ -864,7 +873,7 @@ IDEInterface.prototype.ata_command = function(cmd) break; case 0x91: - // initialize device parameters + // initialize device parameters (not mentioned in ATA/ATAPI-6) this.status = 0x50; this.push_irq(); break; @@ -882,8 +891,7 @@ IDEInterface.prototype.ata_command = function(cmd) break; case 0xA1: - dbg_log("ATA identify packet device", LOG_DISK); - + dbg_log(this.name + ": ATA identify packet device", LOG_DISK); if(this.is_atapi) { this.create_identify_packet(); @@ -902,9 +910,9 @@ IDEInterface.prototype.ata_command = function(cmd) break; case 0xC6: - // set multiple mode + dbg_log(this.name + ": ATA set multiple mode", LOG_DISK); // Logical sectors per DRQ Block in word 1 - dbg_log("Logical sectors per DRQ Block: " + h(this.bytecount & 0xFF), LOG_DISK); + dbg_log(this.name + ": logical sectors per DRQ Block: " + h(this.bytecount & 0xFF), LOG_DISK); this.sectors_per_drq = this.bytecount & 0xFF; this.status = 0x50; this.push_irq(); @@ -921,13 +929,13 @@ IDEInterface.prototype.ata_command = function(cmd) break; case 0x40: - dbg_log("read verify sectors", LOG_DISK); + dbg_log(this.name + ": ATA read verify sector(s)", LOG_DISK); this.status = 0x50; this.push_irq(); break; case 0xDA: - dbg_log("ATA get media status", LOG_DISK); + dbg_log(this.name + ": ATA get media status", LOG_DISK); this.status = 0x50; this.error = 0; if(this.is_atapi) { @@ -944,25 +952,25 @@ IDEInterface.prototype.ata_command = function(cmd) break; case 0xE0: - dbg_log("ATA standby immediate", LOG_DISK); + dbg_log(this.name + ": ATA standby immediate", LOG_DISK); this.status = 0x50; this.push_irq(); break; case 0xE1: - dbg_log("ATA idle immediate", LOG_DISK); + dbg_log(this.name + ": ATA idle immediate", LOG_DISK); this.status = 0x50; this.push_irq(); break; case 0xE7: - dbg_log("ATA flush cache", LOG_DISK); + dbg_log(this.name + ": ATA flush cache", LOG_DISK); this.status = 0x50; this.push_irq(); break; case 0xEC: - dbg_log("ATA identify device", LOG_DISK); + dbg_log(this.name + ": ATA identify device", LOG_DISK); if(this.is_atapi) { @@ -979,37 +987,37 @@ IDEInterface.prototype.ata_command = function(cmd) break; case 0xEA: - dbg_log("flush cache ext", LOG_DISK); + dbg_log(this.name + ": ATA flush cache ext", LOG_DISK); this.status = 0x50; this.push_irq(); break; case 0xEF: - dbg_log("set features: " + h(this.bytecount & 0xFF), LOG_DISK); + dbg_log(this.name + ": ATA set features: " + h(this.bytecount & 0xFF), LOG_DISK); this.status = 0x50; this.push_irq(); break; case 0xDE: - // obsolete + dbg_log(this.name + ": ATA media lock", LOG_DISK); this.status = 0x50; this.push_irq(); break; case 0xF5: - dbg_log("security freeze lock", LOG_DISK); + dbg_log(this.name + ": ATA security freeze lock", LOG_DISK); this.status = 0x50; this.push_irq(); break; case 0xF9: - dbg_log("Unimplemented: set max address", LOG_DISK); + dbg_log(this.name + ": ATA set max address (unimplemented)", LOG_DISK); this.status = 0x41; this.error = 4; break; default: - dbg_assert(false, "New ATA cmd on 1F7: " + h(cmd), LOG_DISK); + dbg_assert(false, this.name + ": unhandled ATA command: " + h(cmd), LOG_DISK); this.status = 0x41; // abort bit set @@ -1019,7 +1027,7 @@ IDEInterface.prototype.ata_command = function(cmd) IDEInterface.prototype.atapi_handle = function() { - dbg_log("ATAPI Command: " + h(this.data[0]) + + dbg_log(this.name + ": ATAPI Command: " + h(this.data[0]) + " slave=" + (this.drive_head >> 4 & 1), LOG_DISK); this.data_pointer = 0; @@ -1030,7 +1038,7 @@ IDEInterface.prototype.atapi_handle = function() this.current_atapi_command === 0x43 || this.current_atapi_command === 0x51)) { - dbg_log("dev "+this.channel.name+" CD read-related action: no buffer", LOG_DISK); + dbg_log(this.name + ": CD read-related action: no buffer", LOG_DISK); this.status = 0x51; this.error = 0x21; this.data_allocate(0); @@ -1043,7 +1051,7 @@ IDEInterface.prototype.atapi_handle = function() switch(this.current_atapi_command) { case 0x00: - dbg_log("test unit ready", LOG_DISK); + dbg_log(this.name + ": test unit ready", LOG_DISK); // test unit ready this.data_allocate(0); this.data_end = this.data_length; @@ -1066,7 +1074,7 @@ IDEInterface.prototype.atapi_handle = function() var length = this.data[4]; this.status = 0x58; - dbg_log("inquiry: " + h(this.data[1], 2) + " length=" + length, LOG_DISK); + dbg_log(this.name + ": inquiry: " + h(this.data[1], 2) + " length=" + length, LOG_DISK); // http://www.t10.org/ftp/x3t9.2/document.87/87-106r0.txt //this.data_allocate(36); @@ -1139,7 +1147,7 @@ IDEInterface.prototype.atapi_handle = function() var length = this.data[8]; this.data_allocate(Math.min(8, length)); this.data_end = this.data_length; - dbg_log("read q subcode: length=" + length, LOG_DISK); + dbg_log(this.name + ": read q subcode: length=" + length, LOG_DISK); this.status = 0x58; break; @@ -1150,7 +1158,7 @@ IDEInterface.prototype.atapi_handle = function() this.data_allocate(length); this.data_end = this.data_length; - dbg_log("read toc: " + h(format, 2) + + dbg_log(this.name + ": read toc: " + h(format, 2) + " length=" + length + " " + (this.data[1] & 2) + " " + h(this.data[6]), LOG_DISK); @@ -1191,7 +1199,7 @@ IDEInterface.prototype.atapi_handle = function() } else { - dbg_assert(false, "Unimplemented format: " + format); + dbg_assert(false, this.name + ": Unimplemented format: " + format); } this.status = 0x58; @@ -1220,7 +1228,7 @@ IDEInterface.prototype.atapi_handle = function() break; case 0x52: - dbg_log("Unimplemented ATAPI command: " + h(this.data[0]), LOG_DISK); + dbg_log(this.name + ": unimplemented ATAPI command: " + h(this.data[0]), LOG_DISK); this.status = 0x51; this.data_length = 0; this.error = 5 << 4; @@ -1230,7 +1238,7 @@ IDEInterface.prototype.atapi_handle = function() // mode sense var length = this.data[8] | this.data[7] << 8; var page_code = this.data[2]; - dbg_log("mode sense: " + h(page_code) + " length=" + length, LOG_DISK); + dbg_log(this.name + ": mode sense: " + h(page_code) + " length=" + length, LOG_DISK); if(page_code === 0x2A) { this.data_allocate(Math.min(30, length)); @@ -1251,12 +1259,12 @@ IDEInterface.prototype.atapi_handle = function() this.status = 0x51; this.data_length = 0; this.error = 5 << 4; - dbg_log("Unimplemented ATAPI command: " + h(this.data[0]), LOG_DISK); + dbg_log(this.name + ": unimplemented ATAPI command: " + h(this.data[0]), LOG_DISK); break; case 0xBE: // Hiren's boot CD - dbg_log("Unimplemented ATAPI command: " + h(this.data[0]), LOG_DISK); + dbg_log(this.name + ": unimplemented ATAPI command: " + h(this.data[0]), LOG_DISK); this.data_allocate(0); this.data_end = this.data_length; this.status = 0x50; @@ -1266,7 +1274,7 @@ IDEInterface.prototype.atapi_handle = function() this.status = 0x51; this.data_length = 0; this.error = 5 << 4; - dbg_log("Unimplemented ATAPI command: " + h(this.data[0]), LOG_DISK); + dbg_log(this.name + ": unimplemented ATAPI command: " + h(this.data[0]), LOG_DISK); dbg_assert(false); } @@ -1312,14 +1320,14 @@ IDEInterface.prototype.atapi_read = function(cmd) var byte_count = count * this.sector_size; var start = lba * this.sector_size; - dbg_log("CD read lba=" + h(lba) + + dbg_log(this.name + ": CD read lba=" + h(lba) + " lbacount=" + h(count) + " bytecount=" + h(byte_count) + " flags=" + h(flags), LOG_DISK); this.data_length = 0; var req_length = this.cylinder_high << 8 & 0xFF00 | this.cylinder_low & 0xFF; - dbg_log(h(this.cylinder_high, 2) + " " + h(this.cylinder_low, 2), LOG_DISK); + dbg_log(this.name + ": " + h(this.cylinder_high, 2) + " " + h(this.cylinder_low, 2), LOG_DISK); this.cylinder_low = this.cylinder_high = 0; // oak technology driver (windows 3.0) if(req_length === 0xFFFF) @@ -1332,14 +1340,14 @@ IDEInterface.prototype.atapi_read = function(cmd) if(!this.buffer) { - dbg_assert(false, "dev "+this.channel.name+" CD read: no buffer", LOG_DISK); + dbg_assert(false, this.name + ": CD read: no buffer", LOG_DISK); this.status = 0xFF; this.error = 0x41; this.push_irq(); } else if(start >= this.buffer.byteLength) { - dbg_assert(false, "dev "+this.channel.name+" CD read: Outside of disk end=" + h(start + byte_count) + + dbg_assert(false, this.name + ": CD read: Outside of disk end=" + h(start + byte_count) + " size=" + h(this.buffer.byteLength), LOG_DISK); this.status = 0xFF; @@ -1361,7 +1369,7 @@ IDEInterface.prototype.atapi_read = function(cmd) this.read_buffer(start, byte_count, (data) => { //setTimeout(() => { - dbg_log("cd read: data arrived", LOG_DISK); + dbg_log(this.name + ": CD read: data arrived", LOG_DISK); this.data_set(data); this.status = 0x58; this.bytecount = this.bytecount & ~7 | 2; @@ -1393,14 +1401,14 @@ IDEInterface.prototype.atapi_read_dma = function(cmd) var byte_count = count * this.sector_size; var start = lba * this.sector_size; - dbg_log("CD read DMA lba=" + h(lba) + + dbg_log(this.name + ": CD read DMA lba=" + h(lba) + " lbacount=" + h(count) + " bytecount=" + h(byte_count) + " flags=" + h(flags), LOG_DISK); if(start >= this.buffer.byteLength) { - dbg_assert(false, "CD read: Outside of disk end=" + h(start + byte_count) + + dbg_assert(false, this.name + ": CD read: Outside of disk end=" + h(start + byte_count) + " size=" + h(this.buffer.byteLength), LOG_DISK); this.status = 0xFF; @@ -1413,7 +1421,7 @@ IDEInterface.prototype.atapi_read_dma = function(cmd) this.read_buffer(start, byte_count, (data) => { - dbg_log("atapi_read_dma: Data arrived"); + dbg_log(this.name + ": atapi_read_dma: Data arrived"); this.report_read_end(byte_count); this.status = 0x58; this.bytecount = this.bytecount & ~7 | 2; @@ -1428,17 +1436,17 @@ IDEInterface.prototype.do_atapi_dma = function() { if((this.channel.dma_status & 1) === 0) { - dbg_log("do_atapi_dma: Status not set", LOG_DISK); + dbg_log(this.name + ": do_atapi_dma: Status not set", LOG_DISK); return; } if((this.status & 0x8) === 0) { - dbg_log("do_atapi_dma: DRQ not set", LOG_DISK); + dbg_log(this.name + ": do_atapi_dma: DRQ not set", LOG_DISK); return; } - dbg_log("atapi dma transfer len=" + this.data_length, LOG_DISK); + dbg_log(this.name + ": ATAPI DMA transfer len=" + this.data_length, LOG_DISK); var prdt_start = this.channel.prdt_addr; var offset = 0; @@ -1455,7 +1463,7 @@ IDEInterface.prototype.do_atapi_dma = function() count = 0x10000; } - dbg_log("dma read dest=" + h(addr) + " count=" + h(count) + " datalen=" + h(this.data_length), LOG_DISK); + dbg_log(this.name + ": DMA read dest=" + h(addr) + " count=" + h(count) + " datalen=" + h(this.data_length), LOG_DISK); this.cpu.write_blob(data.subarray(offset, Math.min(offset + count, this.data_length)), addr); offset += count; @@ -1463,7 +1471,7 @@ IDEInterface.prototype.do_atapi_dma = function() if(offset >= this.data_length && !end) { - dbg_log("leave early end=" + (+end) + + dbg_log(this.name + ": leave early end=" + (+end) + " offset=" + h(offset) + " data_length=" + h(this.data_length) + " cmd=" + h(this.current_command), LOG_DISK); @@ -1472,7 +1480,7 @@ IDEInterface.prototype.do_atapi_dma = function() } while(!end); - dbg_log("end offset=" + offset, LOG_DISK); + dbg_log(this.name + ": end offset=" + offset, LOG_DISK); this.status = 0x50; this.channel.dma_status &= ~1; @@ -1505,7 +1513,7 @@ IDEInterface.prototype.read_data = function(length) var align = (this.data_end & 0xFFF) === 0 ? 0xFFF : 0xFF; if((this.data_pointer & align) === 0) { - dbg_log("Read 1F0: " + h(this.data[this.data_pointer], 2) + + dbg_log(this.name + ": read 1F0: " + h(this.data[this.data_pointer], 2) + " cur=" + h(this.data_pointer) + " cnt=" + h(this.data_length), LOG_DISK); } @@ -1519,7 +1527,7 @@ IDEInterface.prototype.read_data = function(length) } else { - dbg_log("Read 1F0: empty", LOG_DISK); + dbg_log(this.name + ": read 1F0: empty", LOG_DISK); this.data_pointer += length; return 0; @@ -1528,8 +1536,9 @@ IDEInterface.prototype.read_data = function(length) IDEInterface.prototype.read_end = function() { - dbg_log("read_end cmd=" + h(this.current_command) + " data_pointer=" + h(this.data_pointer) + - " end=" + h(this.data_end) + " length=" + h(this.data_length), LOG_DISK); + dbg_log(this.name + ": read_end cmd=" + h(this.current_command) + + " data_pointer=" + h(this.data_pointer) + " end=" + h(this.data_end) + + " length=" + h(this.data_length), LOG_DISK); if(this.current_command === 0xA0) { @@ -1556,7 +1565,7 @@ IDEInterface.prototype.read_end = function() { this.data_end += byte_count; } - dbg_log("data_end=" + h(this.data_end), LOG_DISK); + dbg_log(this.name + ": data_end=" + h(this.data_end), LOG_DISK); } } else @@ -1593,16 +1602,16 @@ IDEInterface.prototype.write_data_port = function(data, length) if(this.data_pointer >= this.data_end) { - dbg_log("Redundant write to data port: " + h(data) + " count=" + h(this.data_end) + - " cur=" + h(this.data_pointer), LOG_DISK); + dbg_log(this.name + ": redundant write to data port: " + h(data) + " count=" + + h(this.data_end) + " cur=" + h(this.data_pointer), LOG_DISK); } else { var align = (this.data_end & 0xFFF) === 0 ? 0xFFF : 0xFF; if((this.data_pointer + length & align) === 0 || this.data_end < 20) { - dbg_log("Data port: " + h(data >>> 0) + " count=" + h(this.data_end) + - " cur=" + h(this.data_pointer), LOG_DISK); + dbg_log(this.name + ": data port: " + h(data >>> 0) + " count=" + + h(this.data_end) + " cur=" + h(this.data_pointer), LOG_DISK); } if(length === 1) @@ -1651,7 +1660,7 @@ IDEInterface.prototype.write_end = function() } else { - dbg_log("write_end data_pointer=" + h(this.data_pointer) + + dbg_log(this.name + ": write_end data_pointer=" + h(this.data_pointer) + " data_length=" + h(this.data_length), LOG_DISK); if(this.data_pointer >= this.data_length) @@ -1676,7 +1685,7 @@ IDEInterface.prototype.write_end = function() IDEInterface.prototype.ata_advance = function(cmd, sectors) { - dbg_log("Advance sectors=" + sectors + " old_bytecount=" + this.bytecount, LOG_DISK); + dbg_log(this.name + ": advance sectors=" + sectors + " old_bytecount=" + this.bytecount, LOG_DISK); this.bytecount -= sectors; if(cmd === 0x24 || cmd === 0x29 || cmd === 0x34 || cmd === 0x39 || @@ -1720,7 +1729,7 @@ IDEInterface.prototype.ata_read_sectors = function(cmd) var byte_count = count * this.sector_size; var start = lba * this.sector_size; - dbg_log("ATA read cmd=" + h(cmd) + + dbg_log(this.name + ": ATA read cmd=" + h(cmd) + " mode=" + (this.is_lba ? "lba" : "chs") + " lba=" + h(lba) + " lbacount=" + h(count) + @@ -1728,7 +1737,7 @@ IDEInterface.prototype.ata_read_sectors = function(cmd) if(start + byte_count > this.buffer.byteLength) { - dbg_assert(false, "ATA read: Outside of disk", LOG_DISK); + dbg_assert(false, this.name + ": ATA read: Outside of disk", LOG_DISK); this.status = 0xFF; this.push_irq(); @@ -1741,7 +1750,7 @@ IDEInterface.prototype.ata_read_sectors = function(cmd) this.read_buffer(start, byte_count, (data) => { //setTimeout(() => { - dbg_log("ata_read: Data arrived", LOG_DISK); + dbg_log(this.name + ": ata_read: Data arrived", LOG_DISK); this.data_set(data); this.status = 0x58; @@ -1764,13 +1773,13 @@ IDEInterface.prototype.ata_read_sectors_dma = function(cmd) var byte_count = count * this.sector_size; var start = lba * this.sector_size; - dbg_log("ATA DMA read lba=" + h(lba) + + dbg_log(this.name + ": ATA DMA read lba=" + h(lba) + " lbacount=" + h(count) + " bytecount=" + h(byte_count), LOG_DISK); if(start + byte_count > this.buffer.byteLength) { - dbg_assert(false, "ATA read: Outside of disk", LOG_DISK); + dbg_assert(false, this.name + ": ATA read: Outside of disk", LOG_DISK); this.status = 0xFF; this.push_irq(); @@ -1801,7 +1810,7 @@ IDEInterface.prototype.do_ata_read_sectors_dma = function() this.read_buffer(start, byte_count, (data) => { //setTimeout(function() { - dbg_log("do_ata_read_sectors_dma: Data arrived", LOG_DISK); + dbg_log(this.name + ": do_ata_read_sectors_dma: Data arrived", LOG_DISK); var prdt_start = this.channel.prdt_addr; var offset = 0; @@ -1815,10 +1824,10 @@ IDEInterface.prototype.do_ata_read_sectors_dma = function() if(!prd_count) { prd_count = 0x10000; - dbg_log("dma: prd count was 0", LOG_DISK); + dbg_log(this.name + ": DMA: prd count was 0", LOG_DISK); } - dbg_log("dma read transfer dest=" + h(prd_addr) + + dbg_log(this.name + ": DMA read transfer dest=" + h(prd_addr) + " prd_count=" + h(prd_count), LOG_DISK); this.cpu.write_blob(data.subarray(offset, offset + prd_count), prd_addr); @@ -1851,14 +1860,14 @@ IDEInterface.prototype.ata_write_sectors = function(cmd) var byte_count = count * this.sector_size; var start = lba * this.sector_size; - dbg_log("ATA write lba=" + h(lba) + + dbg_log(this.name + ": ATA write lba=" + h(lba) + " mode=" + (this.is_lba ? "lba" : "chs") + " lbacount=" + h(count) + " bytecount=" + h(byte_count), LOG_DISK); if(start + byte_count > this.buffer.byteLength) { - dbg_assert(false, "ATA write: Outside of disk", LOG_DISK); + dbg_assert(false, this.name + ": ATA write: Outside of disk", LOG_DISK); this.status = 0xFF; this.push_irq(); @@ -1881,13 +1890,13 @@ IDEInterface.prototype.ata_write_sectors_dma = function(cmd) var byte_count = count * this.sector_size; var start = lba * this.sector_size; - dbg_log("ATA DMA write lba=" + h(lba) + + dbg_log(this.name + ": ATA DMA write lba=" + h(lba) + " lbacount=" + h(count) + " bytecount=" + h(byte_count), LOG_DISK); if(start + byte_count > this.buffer.byteLength) { - dbg_assert(false, "ATA DMA write: Outside of disk", LOG_DISK); + dbg_assert(false, this.name + ": ATA DMA write: Outside of disk", LOG_DISK); this.status = 0xFF; this.push_irq(); @@ -1912,7 +1921,7 @@ IDEInterface.prototype.do_ata_write_sectors_dma = function() var prdt_start = this.channel.prdt_addr; var offset = 0; - dbg_log("prdt addr: " + h(prdt_start, 8), LOG_DISK); + dbg_log(this.name + ": prdt addr: " + h(prdt_start, 8), LOG_DISK); const buffer = new Uint8Array(byte_count); @@ -1924,10 +1933,10 @@ IDEInterface.prototype.do_ata_write_sectors_dma = function() if(!prd_count) { prd_count = 0x10000; - dbg_log("dma: prd count was 0", LOG_DISK); + dbg_log(this.name + ": DMA: prd count was 0", LOG_DISK); } - dbg_log("dma write transfer dest=" + h(prd_addr) + " prd_count=" + h(prd_count), LOG_DISK); + dbg_log(this.name + ": DMA write transfer dest=" + h(prd_addr) + " prd_count=" + h(prd_count), LOG_DISK); var slice = this.cpu.mem8.subarray(prd_addr, prd_addr + prd_count); dbg_assert(slice.length === prd_count); @@ -1948,7 +1957,7 @@ IDEInterface.prototype.do_ata_write_sectors_dma = function() this.buffer.set(start, buffer, () => { - dbg_log("dma write completed", LOG_DISK); + dbg_log(this.name + ": DMA write completed", LOG_DISK); this.ata_advance(this.current_command, count); this.status = 0x50; this.push_irq(); @@ -1965,7 +1974,7 @@ IDEInterface.prototype.get_chs = function() var h = this.head; var s = this.sector & 0xFF; - dbg_log("get_chs: c=" + c + " h=" + h + " s=" + s, LOG_DISK); + dbg_log(this.name + ": get_chs: c=" + c + " h=" + h + " s=" + s, LOG_DISK); return (c * this.head_count + h) * this.sectors_per_track + s - 1; }; From 4131aa709fa5d564b78b0bca888b4cfc6e99a4c8 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Mon, 12 May 2025 20:17:08 +0200 Subject: [PATCH 080/301] renamed register members to better align with the standard Renamed members that carry register values to better align with the actual wording in the ATA/ATAPI-6 standard. Other changes in this commit include: - added a bunch of named constants for ATA registers and their bits - improved PCI configuration space setup - renamed class IDEPCIAdapter to IDEController --- src/cpu.js | 4 +- src/ide.js | 776 +++++++++++++++++++++++++++-------------------------- 2 files changed, 395 insertions(+), 385 deletions(-) diff --git a/src/cpu.js b/src/cpu.js index 0375f03b..00a2992a 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -29,7 +29,7 @@ import { PS2 } from "./ps2.js"; import { read_elf } from "./elf.js"; import { FloppyController } from "./floppy.js"; -import { IDEPCIAdapter } from "./ide.js"; +import { IDEController } from "./ide.js"; import { VirtioNet } from "./virtio_net.js"; import { VGAScreen } from "./vga.js"; import { VirtioBalloon } from "./virtio_balloon.js"; @@ -1120,7 +1120,7 @@ CPU.prototype.init = function(settings, device_bus) }; } - this.devices.ide = new IDEPCIAdapter(this, device_bus, ide_config); + this.devices.ide = new IDEController(this, device_bus, ide_config); if(settings.hda) { this.devices.hda = this.devices.ide.channels[0]; diff --git a/src/ide.js b/src/ide.js index 33426548..9b61d9ef 100644 --- a/src/ide.js +++ b/src/ide.js @@ -7,20 +7,67 @@ import { CMOS_BIOS_DISKTRANSFLAG, CMOS_DISK_DATA, CMOS_DISK_DRIVE1_CYL, CMOS_DIS import { CPU } from "./cpu.js"; import { BusConnector } from "./bus.js"; +// References +// [ATA-6] +// AT Attachment with Packet Interface - 6 (ATA/ATAPI-6) (Rev. 3a, 14 Dec. 2001) +// https://technion-csl.github.io/ose/readings/hardware/ATA-d1410r3a.pdf + const CDROM_SECTOR_SIZE = 2048; const HD_SECTOR_SIZE = 512; +// Per-channel PIO register offsets, legend: +// (*1*) Control block register (BAR1/3), else: Command block register (BAR0/2) +// +// Read-only registers: +const ATA_REG_ERROR = 0x01; // Error register, see [ATA-6] 7.9 +const ATA_REG_STATUS = 0x07; // Status register, see [ATA-6] 7.15 +const ATA_REG_ALT_STATUS = 0x02; // (*1*) Alternate Status register, see [ATA-6] 7.3 +// Read-/writable registers: +const ATA_REG_DATA = 0x00; // Data register, see [ATA-6] 7.6 +const ATA_REG_SECTOR = 0x02; // Sector Count register, see [ATA-6] 7.14 +const ATA_REG_LBA_LOW = 0x03; // LBA Low register, see [ATA-6] 7.12 +const ATA_REG_LBA_MID = 0x04; // LBA Mid register, see [ATA-6] 7.13 +const ATA_REG_LBA_HIGH = 0x05; // LBA High register, see [ATA-6] 7.11 +const ATA_REG_DEVICE = 0x06; // Device register, see [ATA-6] 7.7 +// Write-only registers: +const ATA_REG_FEATURES = 0x01; // Features register, see [ATA-6] 7.10 +const ATA_REG_COMMAND = 0x07; // Command register, see [ATA-6] 7.4 +const ATA_REG_CONTROL = 0x02; // (*1*) Device Control register, see [ATA-6] 7.8 + +// Error register bits: +// - All remaining bits (except bit 3) are command dependent. +const ATA_ER_ABRT = 0x04; // Command aborted + +// Status register bits: +// - Bits 1 and 2 are obsolete, 4 and 5 are command dependent. +// - Bit 1 used to be called "Index" (IDX), bit 2 "Corrected data" (CORR), +// bit 4 "Drive seek complete" (DSC), and bit 5 "Drive write fault" (DF). +const ATA_SR_ERR = 0x01; // Error +const ATA_SR_DRQ = 0x08; // Data request ready +const ATA_SR_DRDY = 0x40; // Drive ready +const ATA_SR_BSY = 0x80; // Busy + +// Device register bits: +// - Bits 5 and 7 are obsolete, 0-3 and 6 are command dependent. +const ATA_DR_DEV = 0x10; // Device select, slave device if set, else master device + +// Device Control register bits: +// - Bits 0 and 3-6 are reserved. +const ATA_CR_NIEN = 0x02; // Interrupt disable (Not Interrupt Enable) +const ATA_CR_SRST = 0x04; // Software reset +const ATA_CR_HOB = 0x80; // 48-bit Address feature set + /** * @constructor * @param {CPU} cpu * @param {BusConnector} bus * - * adapter_config: [ [, ], [, ] ] + * ide_config: [ [, ], [, ] ] * * Each of the four arguments (, , ...) is - * either undefined or an interface_config object of the form: + * either undefined or an ide_config object of the form: * - * interface_config := { buffer: Uint8Array, is_cdrom: bool } + * ide_config := { buffer: Uint8Array, is_cdrom: bool } * * If is_cdrom is defined and true: * - If buffer is defined: create an ATAPI CD-ROM device using buffer as inserted disk @@ -31,112 +78,111 @@ const HD_SECTOR_SIZE = 512; * * A slave drive can only exist if a master drive also exists. * */ -export function IDEPCIAdapter(cpu, bus, adapter_config) +export function IDEController(cpu, bus, ide_config) { - // Except for bus_master_port, all ports and IRQs are predefined and - // cannot be changed in Compatibility mode. - // The 16 registers of the Bus Master port are split in two, registers - // 0..7 are used for the primary channel, and 8..15 for the secondary. - const bus_master_port = 0xB400; - const pri = { ata_port: 0x1f0, ata_port_high: 0x3f4, irq: 14, bus_master_port: bus_master_port }; - const sec = { ata_port: 0x170, ata_port_high: 0x374, irq: 15, bus_master_port: bus_master_port | 0x8 }; - this.cpu = cpu; this.bus = bus; this.primary = undefined; this.secondary = undefined; this.channels = [undefined, undefined]; - const has_primary = adapter_config && adapter_config[0][0]; - const has_secondary = adapter_config && adapter_config[1][0]; - if(!has_primary && !has_secondary) { - Object.seal(this); - return; - } - if(has_primary) { - this.primary = new IDEChannel(this, adapter_config, 0, pri); - } - if(has_secondary) { - this.secondary = new IDEChannel(this, adapter_config, 1, sec); - } - this.channels = [this.primary, this.secondary]; - - const vendor_id = 0x8086; // Intel Corporation - const device_id = 0x7010; // 82371SB PIIX3 IDE [Natoma/Triton II] - const class_code = 0x01; // Mass Storage Controller - const subclass = 0x01; // IDE Controller - const prog_if = 0x80; // ISA Compatibility mode-only controller, supports bus mastering - const interrupt_line = 0x00; // IRQs 14 and 15 are predefined in Compatibility mode and this field is ignored - - this.pci_id = 0x1F << 3; - this.pci_space = [ - vendor_id & 0xFF, vendor_id >> 8, device_id & 0xFF, device_id >> 8, 0x05, 0x00, 0xA0, 0x02, - 0x00, prog_if, subclass, class_code, 0x00, 0x00, 0x00, 0x00, - pri.ata_port & 0xFF | 1, pri.ata_port >> 8, 0x00, 0x00, - pri.ata_port_high & 0xFF | 1, pri.ata_port_high >> 8, 0x00, 0x00, - sec.ata_port & 0xFF | 1, sec.ata_port >> 8, 0x00, 0x00, - sec.ata_port_high & 0xFF | 1, sec.ata_port_high >> 8, 0x00, 0x00, - bus_master_port & 0xFF | 1, bus_master_port >> 8, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x43, 0x10, 0xD4, 0x82, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, interrupt_line, 0x01, 0x00, 0x00, - // 0x40 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 0x80 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ]; - this.pci_bars = [ - { - size: 8, // BAR0: Command block registers of primary ATA Channel - }, - { - size: 4, // BAR1: Control registers of primary ATA Channel - }, - { - size: 8, // BAR2: Command block registers of secondary ATA Channel - }, - { - size: 4, // BAR3: Control registers of secondary ATA Channel - }, - { - size: 16, // BAR4: ATA Bus Master I/O registers (primary ATA Channel: 0..7, secondary: 8..15) + const has_primary = ide_config && ide_config[0][0]; + const has_secondary = ide_config && ide_config[1][0]; + if(has_primary || has_secondary) { + const bus_master_base = 0xB400; + if(has_primary) { + this.primary = new IDEChannel(this, ide_config, 0, { + command_base: 0x1f0, control_base: 0x3f4, bus_master_base: bus_master_base, irq: 14 + }); + this.channels[0] = this.primary; + } + if(has_secondary) { + this.secondary = new IDEChannel(this, ide_config, 1, { + command_base: 0x170, control_base: 0x374, bus_master_base: bus_master_base | 0x8, irq: 15 + }); + this.channels[1] = this.secondary; } - ]; - cpu.devices.pci.register_device(this); + const vendor_id = 0x8086; // Intel Corporation + const device_id = 0x7010; // 82371SB PIIX3 IDE [Natoma/Triton II] + const class_code = 0x01; // Mass Storage Controller + const subclass = 0x01; // IDE Controller + const prog_if = 0x80; // ISA Compatibility mode-only controller, supports bus mastering + const interrupt_line = 0x00; // IRQs 14 and 15 are predefined in Compatibility mode and this field is ignored + const command_base0 = has_primary ? this.primary.command_base : 0; + const control_base0 = has_primary ? this.primary.control_base : 0; + const command_base1 = has_secondary ? this.secondary.command_base : 0; + const control_base1 = has_secondary ? this.secondary.control_base : 0; + + this.pci_id = 0x1F << 3; + this.pci_space = [ + vendor_id & 0xFF, vendor_id >> 8, device_id & 0xFF, device_id >> 8, 0x05, 0x00, 0xA0, 0x02, + 0x00, prog_if, subclass, class_code, 0x00, 0x00, 0x00, 0x00, + command_base0 & 0xFF | 1, command_base0 >> 8, 0x00, 0x00, + control_base0 & 0xFF | 1, control_base0 >> 8, 0x00, 0x00, + command_base1 & 0xFF | 1, command_base1 >> 8, 0x00, 0x00, + control_base1 & 0xFF | 1, control_base1 >> 8, 0x00, 0x00, + bus_master_base & 0xFF | 1, bus_master_base >> 8, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x43, 0x10, 0xD4, 0x82, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, interrupt_line, 0x01, 0x00, 0x00, + // 0x40 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // 0x80 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + this.pci_bars = [ + { + size: 8, // BAR0: Command block registers of primary ATA Channel + }, + { + size: 4, // BAR1: Control registers of primary ATA Channel + }, + { + size: 8, // BAR2: Command block registers of secondary ATA Channel + }, + { + size: 4, // BAR3: Control registers of secondary ATA Channel + }, + { + size: 16, // BAR4: ATA Bus Master I/O registers (primary ATA Channel: 0..7, secondary: 8..15) + } + ]; + cpu.devices.pci.register_device(this); + } Object.seal(this); } /** * @constructor - * @param {IDEPCIAdapter} adapter + * @param {IDEController} controller * @param {number} channel_nr * */ -function IDEChannel(adapter, adapter_config, channel_nr, channel_config) +function IDEChannel(controller, controller_config, channel_nr, channel_config) { - this.adapter = adapter; - this.cpu = adapter.cpu; - this.bus = adapter.bus; - this.ata_port = channel_config.ata_port; - this.ata_port_high = channel_config.ata_port_high; + this.controller = controller; + this.cpu = controller.cpu; + this.bus = controller.bus; + this.command_base = channel_config.command_base; + this.control_base = channel_config.control_base; this.irq = channel_config.irq; this.name = "ide" + channel_nr; const create_interface = interface_nr => { - const config = adapter_config && adapter_config[channel_nr] ? - adapter_config[channel_nr][interface_nr] : undefined; + const config = controller_config && controller_config[channel_nr] ? + controller_config[channel_nr][interface_nr] : undefined; const buffer = config ? config.buffer : undefined; const is_cdrom = config ? !!config.is_cdrom : false; return new IDEInterface(this, this.cpu, buffer, is_cdrom, channel_nr, interface_nr, this.bus); @@ -151,7 +197,7 @@ function IDEChannel(adapter, adapter_config, channel_nr, channel_config) this.slave.init_interface(); /** @type {number} */ - this.device_control = 2; + this.device_control_reg = 2; /** @type {number} */ this.prdt_addr = 0; @@ -162,14 +208,14 @@ function IDEChannel(adapter, adapter_config, channel_nr, channel_config) /** @type {number} */ this.dma_command = 0; - const cpu = adapter.cpu; + const cpu = controller.cpu; // - // Command Block Registers: ata_port + 0...7 (BAR0: 1F0h, BAR2: 170h) + // Command Block Registers: command_base + 0...7 (BAR0: 1F0h, BAR2: 170h) // // read Data register - cpu.io.register_read(this.ata_port | 0, this, function() + cpu.io.register_read(this.command_base | ATA_REG_DATA, this, function() { return this.current_interface.read_data(1); }, function() @@ -180,56 +226,56 @@ function IDEChannel(adapter, adapter_config, channel_nr, channel_config) return this.current_interface.read_data(4); }); - cpu.io.register_read(this.ata_port | 1, this, function() + cpu.io.register_read(this.command_base | ATA_REG_ERROR, this, function() { dbg_log(this.current_interface.name + ": read Error register: " + - h(this.current_interface.error & 0xFF) + " slave=" + + h(this.current_interface.error_reg & 0xFF) + " slave=" + (this.current_interface === this.slave), LOG_DISK); - return this.current_interface.error & 0xFF; + return this.current_interface.error_reg & 0xFF; }); - cpu.io.register_read(this.ata_port | 2, this, function() + cpu.io.register_read(this.command_base | ATA_REG_SECTOR, this, function() { dbg_log(this.current_interface.name + ": read Sector Count register: " + - h(this.current_interface.bytecount & 0xFF), LOG_DISK); - return this.current_interface.bytecount & 0xFF; + h(this.current_interface.sector_count_reg & 0xFF), LOG_DISK); + return this.current_interface.sector_count_reg & 0xFF; }); - cpu.io.register_read(this.ata_port | 3, this, function() + cpu.io.register_read(this.command_base | ATA_REG_LBA_LOW, this, function() { dbg_log(this.current_interface.name + ": read LBA Low register: " + - h(this.current_interface.sector & 0xFF), LOG_DISK); - return this.current_interface.sector & 0xFF; + h(this.current_interface.lba_low_reg & 0xFF), LOG_DISK); + return this.current_interface.lba_low_reg & 0xFF; }); - cpu.io.register_read(this.ata_port | 4, this, function() + cpu.io.register_read(this.command_base | ATA_REG_LBA_MID, this, function() { dbg_log(this.current_interface.name + ": read LBA Mid register: " + - h(this.current_interface.cylinder_low & 0xFF), LOG_DISK); - return this.current_interface.cylinder_low & 0xFF; + h(this.current_interface.lba_mid_reg & 0xFF), LOG_DISK); + return this.current_interface.lba_mid_reg & 0xFF; }); - cpu.io.register_read(this.ata_port | 5, this, function() + cpu.io.register_read(this.command_base | ATA_REG_LBA_HIGH, this, function() { dbg_log(this.current_interface.name + ": read LBA High register: " + - h(this.current_interface.cylinder_high & 0xFF), LOG_DISK); - return this.current_interface.cylinder_high & 0xFF; + h(this.current_interface.lba_high_reg & 0xFF), LOG_DISK); + return this.current_interface.lba_high_reg & 0xFF; }); - cpu.io.register_read(this.ata_port | 6, this, function() + cpu.io.register_read(this.command_base | ATA_REG_DEVICE, this, function() { dbg_log(this.current_interface.name + ": read Device register", LOG_DISK); - return this.current_interface.drive_head & 0xFF; + return this.current_interface.device_reg & 0xFF; }); - cpu.io.register_read(this.ata_port | 7, this, function() { + cpu.io.register_read(this.command_base | ATA_REG_STATUS, this, function() { dbg_log(this.current_interface.name + ": read Status register", LOG_DISK); dbg_log(this.current_interface.name + ": lower IRQ " + this.irq, LOG_DISK); this.cpu.device_lower_irq(this.irq); return this.read_status(); }); - cpu.io.register_write(this.ata_port | 0, this, function(data) + cpu.io.register_write(this.command_base | ATA_REG_DATA, this, function(data) { this.current_interface.write_data_port8(data); }, function(data) @@ -240,69 +286,37 @@ function IDEChannel(adapter, adapter_config, channel_nr, channel_config) this.current_interface.write_data_port32(data); }); - cpu.io.register_write(this.ata_port | 1, this, function(data) + cpu.io.register_write(this.command_base | ATA_REG_FEATURES, this, function(data) { dbg_log(this.current_interface.name + ": write Features register: " + h(data), LOG_DISK); -/*** - * this.master.lba_count = (this.master.lba_count << 8 | data) & 0xFFFF; - * this.slave.lba_count = (this.slave.lba_count << 8 | data) & 0xFFFF; - ***/ - this.current_interface.lba_count = (this.current_interface.lba_count << 8 | data) & 0xFFFF; + this.current_interface.features_reg = (this.current_interface.features_reg << 8 | data) & 0xFFFF; }); - cpu.io.register_write(this.ata_port | 2, this, function(data) + cpu.io.register_write(this.command_base | ATA_REG_SECTOR, this, function(data) { dbg_log(this.current_interface.name + ": write Sector Count register: " + h(data), LOG_DISK); -/*** - * this.master.bytecount = (this.master.bytecount << 8 | data) & 0xFFFF; - * if(slave_buffer) - * { - * this.slave.bytecount = (this.slave.bytecount << 8 | data) & 0xFFFF; - * } - ***/ - this.current_interface.bytecount = (this.current_interface.bytecount << 8 | data) & 0xFFFF; + this.current_interface.sector_count_reg = (this.current_interface.sector_count_reg << 8 | data) & 0xFFFF; }); - cpu.io.register_write(this.ata_port | 3, this, function(data) + cpu.io.register_write(this.command_base | ATA_REG_LBA_LOW, this, function(data) { dbg_log(this.current_interface.name + ": write LBA Low register: " + h(data), LOG_DISK); -/*** - * this.master.sector = (this.master.sector << 8 | data) & 0xFFFF; - * if(slave_buffer) - * { - * this.slave.sector = (this.slave.sector << 8 | data) & 0xFFFF; - * } - ***/ - this.current_interface.sector = (this.current_interface.sector << 8 | data) & 0xFFFF; + this.current_interface.lba_low_reg = (this.current_interface.lba_low_reg << 8 | data) & 0xFFFF; }); - cpu.io.register_write(this.ata_port | 4, this, function(data) + cpu.io.register_write(this.command_base | ATA_REG_LBA_MID, this, function(data) { dbg_log(this.current_interface.name + ": write LBA Mid register: " + h(data), LOG_DISK); -/*** - * this.master.cylinder_low = (this.master.cylinder_low << 8 | data) & 0xFFFF; - * if(slave_buffer) - * { - * this.slave.cylinder_low = (this.slave.cylinder_low << 8 | data) & 0xFFFF; - * } - ***/ - this.current_interface.cylinder_low = (this.current_interface.cylinder_low << 8 | data) & 0xFFFF; + this.current_interface.lba_mid_reg = (this.current_interface.lba_mid_reg << 8 | data) & 0xFFFF; }); - cpu.io.register_write(this.ata_port | 5, this, function(data) + cpu.io.register_write(this.command_base | ATA_REG_LBA_HIGH, this, function(data) { dbg_log(this.current_interface.name + ": write LBA High register: " + h(data), LOG_DISK); -/*** - * this.master.cylinder_high = (this.master.cylinder_high << 8 | data) & 0xFFFF; - * if(slave_buffer) - * { - * this.slave.cylinder_high = (this.slave.cylinder_high << 8 | data) & 0xFFFF; - * } - ***/ - this.current_interface.cylinder_high = (this.current_interface.cylinder_high << 8 | data) & 0xFFFF; + this.current_interface.lba_high_reg = (this.current_interface.lba_high_reg << 8 | data) & 0xFFFF; }); - cpu.io.register_write(this.ata_port | 6, this, function(data) + cpu.io.register_write(this.command_base | ATA_REG_DEVICE, this, function(data) { var slave = data & 0x10; // var mode = data & 0xE0; // unused @@ -321,67 +335,62 @@ function IDEChannel(adapter, adapter_config, channel_nr, channel_config) this.current_interface = this.master; } } -/*** - * this.master.drive_head = data; - * this.slave.drive_head = data; - * this.master.is_lba = this.slave.is_lba = data >> 6 & 1; - * this.master.head = this.slave.head = data & 0xF; - ***/ - this.current_interface.drive_head = data; + + this.current_interface.device_reg = data; this.current_interface.is_lba = data >> 6 & 1; this.current_interface.head = data & 0xF; }); - cpu.io.register_write(this.ata_port | 7, this, function(data) + cpu.io.register_write(this.command_base | ATA_REG_COMMAND, this, function(data) { dbg_log(this.current_interface.name + ": write Command register", LOG_DISK); dbg_log(this.current_interface.name + ": lower IRQ " + this.irq, LOG_DISK); this.cpu.device_lower_irq(this.irq); // clear error and DF bits - this.current_interface.status &= ~(1 | (1 << 5)); + this.current_interface.status_reg &= ~(1 | (1 << 5)); this.current_interface.ata_command(data); }); // - // Control Block Register: ata_port_high + 0...3 (BAR1: 3F4h, BAR3: 374h) + // Control Block Register: control_base + 0...3 (BAR1: 3F4h, BAR3: 374h) // // read Alternate Status register - cpu.io.register_read(this.ata_port_high | 2, this, this.read_status); + cpu.io.register_read(this.control_base | ATA_REG_ALT_STATUS, this, this.read_status); // write Device Control register - cpu.io.register_write(this.ata_port_high | 2, this, this.write_control); + cpu.io.register_write(this.control_base | ATA_REG_CONTROL, this, this.write_control); // - // Bus Master Registers: bus_master_port + 0...16 (BAR4, lower 8 for primary and upper 8 for secondary channel) + // Bus Master Registers: bus_master_base + 0...15 (BAR4: B400h, lower 8 for primary and upper 8 for secondary channel) // - const bus_master_port = channel_config.bus_master_port; + const bus_master_base = channel_config.bus_master_base; - cpu.io.register_read(bus_master_port, this, + cpu.io.register_read(bus_master_base, this, this.dma_read_command8, undefined, this.dma_read_command); - cpu.io.register_write(bus_master_port, this, + cpu.io.register_write(bus_master_base, this, this.dma_write_command8, undefined, this.dma_write_command); - cpu.io.register_read(bus_master_port | 2, this, this.dma_read_status); - cpu.io.register_write(bus_master_port | 2, this, this.dma_write_status); + cpu.io.register_read(bus_master_base | 2, this, this.dma_read_status); + cpu.io.register_write(bus_master_base | 2, this, this.dma_write_status); - cpu.io.register_read(bus_master_port | 4, this, undefined, undefined, this.dma_read_addr); - cpu.io.register_write(bus_master_port | 4, this, undefined, undefined, this.dma_set_addr); + cpu.io.register_read(bus_master_base | 4, this, undefined, undefined, this.dma_read_addr); + cpu.io.register_write(bus_master_base | 4, this, undefined, undefined, this.dma_set_addr); DEBUG && Object.seal(this); } IDEChannel.prototype.read_status = function() { - const ret = this.current_interface.drive_connected ? this.current_interface.status : 0; + const ret = this.current_interface.drive_connected ? this.current_interface.status_reg : 0; dbg_log(this.current_interface.name + ": ATA read status: " + h(ret, 2), LOG_DISK); return ret; }; IDEChannel.prototype.write_control = function(data) { - dbg_log(this.current_interface.name + ": write Device control register: " + + dbg_log(this.current_interface.name + ": write Device Control register: " + h(data, 2) + " interrupts " + ((data & 2) ? "disabled" : "enabled"), LOG_DISK); if(data & 0x04) @@ -394,7 +403,7 @@ IDEChannel.prototype.write_control = function(data) this.slave.device_reset(); } - this.device_control = data; + this.device_control_reg = data; }; IDEChannel.prototype.dma_read_addr = function() @@ -489,7 +498,7 @@ IDEChannel.prototype.dma_write_command8 = function(value) IDEChannel.prototype.push_irq = function() { - if((this.device_control & 2) === 0) + if((this.device_control_reg & 2) === 0) { dbg_log(this.current_interface.name + ": push IRQ " + this.irq, LOG_DISK); this.dma_status |= 4; @@ -502,13 +511,13 @@ IDEChannel.prototype.get_state = function() var state = []; state[0] = this.master; state[1] = this.slave; - state[2] = this.ata_port; + state[2] = this.command_base; state[3] = this.irq; // state[4] = this.pci_id; - state[5] = this.ata_port_high; - // state[6] = this.bus_master_port; + state[5] = this.control_base; + // state[6] = this.bus_master_base; state[7] = this.name; - state[8] = this.device_control; + state[8] = this.device_control_reg; state[9] = this.prdt_addr; state[10] = this.dma_status; state[11] = this.current_interface === this.master; @@ -520,13 +529,13 @@ IDEChannel.prototype.set_state = function(state) { this.master.set_state(state[0]); this.slave.set_state(state[1]); - this.ata_port = state[2]; + this.command_base = state[2]; this.irq = state[3]; // this.pci_id = state[4]; - this.ata_port_high = state[5]; - // this.bus_master_port = state[6]; + this.control_base = state[5]; + // this.bus_master_base = state[6]; this.name = state[7]; - this.device_control = state[8]; + this.device_control_reg = state[8]; this.prdt_addr = state[9]; this.dma_status = state[10]; this.current_interface = state[11] ? this.master : this.slave; @@ -535,6 +544,12 @@ IDEChannel.prototype.set_state = function(state) /** * @constructor + * @param {IDEChannel} channel + * @param {CPU} cpu + * @param {boolean} is_cd + * @param {number} channel_nr + * @param {number} interface_nr + * @param {BusConnector} bus */ function IDEInterface(channel, cpu, buffer, is_cd, channel_nr, interface_nr, bus) { @@ -589,34 +604,34 @@ function IDEInterface(channel, cpu, buffer, is_cd, channel_nr, interface_nr, bus this.is_lba = 0; /** @type {number} */ - this.bytecount = 0; + this.sector_count_reg = 0; /** @type {number} */ - this.sector = 0; + this.lba_low_reg = 0; /** @type {number} */ - this.lba_count = 0; + this.features_reg = 0; /** @type {number} */ - this.cylinder_low = 0; + this.lba_mid_reg = 0; /** @type {number} */ - this.cylinder_high = 0; + this.lba_high_reg = 0; /** @type {number} */ this.head = 0; /** @type {number} */ - this.drive_head = 0; + this.device_reg = 0; /** @type {number} */ - this.status = 0x50; + this.status_reg = 0x50; /** @type {number} */ this.sectors_per_drq = 0x80; /** @type {number} */ - this.error = 0; + this.error_reg = 0; /** @type {number} */ this.data_pointer = 0; @@ -662,8 +677,8 @@ IDEInterface.prototype.eject = function() { this.media_changed = true; this.buffer = null; - this.status = 0x59; - this.error = 0x60; + this.status_reg = 0x59; + this.error_reg = 0x60; this.push_irq(); } }; @@ -687,8 +702,8 @@ IDEInterface.prototype.set_disk_buffer = function(buffer) this.buffer = buffer; if(this.is_atapi) { - this.status = 0x59; - this.error = 0x60; + this.status_reg = 0x59; + this.error_reg = 0x60; } this.sector_count = this.buffer.byteLength / this.sector_size; @@ -744,26 +759,25 @@ IDEInterface.prototype.set_disk_buffer = function(buffer) IDEInterface.prototype.device_reset = function() { + // for details, see [ATA-6] 9.11 "DEVICE RESET command protocol" if(this.is_atapi) { - this.status = 0; - this.bytecount = 1; - this.error = 1; - this.sector = 1; // lba_low - this.cylinder_low = 0x14; // lba_mid - this.cylinder_high = 0xEB; // lba_high + this.sector_count_reg = 1; + this.lba_low_reg = 1; + this.lba_mid_reg = 0x14; + this.lba_high_reg = 0xEB; + this.device_reg &= ATA_DR_DEV; } else { - this.status = 0x50 | 1; - this.bytecount = 1; - this.error = 1; - this.sector = 1; // lba_low - - // 0, 0 needed by bochs bios - this.cylinder_low = 0; // lba_mid - this.cylinder_high = 0; // lba_high + this.sector_count_reg = 1; + this.lba_low_reg = 1; + this.lba_mid_reg = 0; + this.lba_high_reg = 0; + this.device_reg = 0; } + this.error_reg &= ~0x80; // clear bit 7 only (explicitly, why?) + this.status_reg &= ~(ATA_SR_ERR|ATA_SR_BSY); this.cancel_io_operations(); }; @@ -775,7 +789,7 @@ IDEInterface.prototype.push_irq = function() IDEInterface.prototype.ata_command = function(cmd) { - dbg_log(this.name + ": ATA Command: " + h(cmd) + " slave=" + (this.drive_head >> 4 & 1), LOG_DISK); + dbg_log(this.name + ": ATA Command: " + h(cmd) + " slave=" + (this.device_reg >> 4 & 1), LOG_DISK); if(!this.drive_connected && cmd !== 0x90) { @@ -784,11 +798,11 @@ IDEInterface.prototype.ata_command = function(cmd) } this.current_command = cmd; - this.error = 0; + this.error_reg = 0; switch(cmd) { - case 0x08: + case 0x08: // DEVICE RESET, see [ATA-6] 8.10 and 9.11 dbg_log(this.name + ": ATA device reset", LOG_DISK); this.data_pointer = 0; this.data_end = 0; @@ -797,32 +811,32 @@ IDEInterface.prototype.ata_command = function(cmd) this.push_irq(); break; - case 0x10: - dbg_log(this.name + ": ATA calibrate drive", LOG_DISK); // ATA/ATAPI-6: obsolete - this.status = 0x50; - this.cylinder_low = 0; + case 0x10: // see [ATA-6] Table E.2: obsolete! + dbg_log(this.name + ": ATA calibrate drive", LOG_DISK); + this.status_reg = 0x50; + this.lba_mid_reg = 0; this.push_irq(); break; case 0xF8: dbg_log(this.name + ": ATA read native max address", LOG_DISK); - this.status = 0x50; + this.status_reg = 0x50; var last_sector = this.sector_count - 1; - this.sector = last_sector & 0xFF; - this.cylinder_low = last_sector >> 8 & 0xFF; - this.cylinder_high = last_sector >> 16 & 0xFF; - this.drive_head = this.drive_head & 0xF0 | last_sector >> 24 & 0x0F; + this.lba_low_reg = last_sector & 0xFF; + this.lba_mid_reg = last_sector >> 8 & 0xFF; + this.lba_high_reg = last_sector >> 16 & 0xFF; + this.device_reg = this.device_reg & 0xF0 | last_sector >> 24 & 0x0F; this.push_irq(); break; case 0x27: dbg_log(this.name + ": ATA read native max address ext", LOG_DISK); - this.status = 0x50; + this.status_reg = 0x50; var last_sector = this.sector_count - 1; - this.sector = last_sector & 0xFF; - this.cylinder_low = last_sector >> 8 & 0xFF; - this.cylinder_high = last_sector >> 16 & 0xFF; - this.sector |= last_sector >> 24 << 8 & 0xFF00; + this.lba_low_reg = last_sector & 0xFF; + this.lba_mid_reg = last_sector >> 8 & 0xFF; + this.lba_high_reg = last_sector >> 16 & 0xFF; + this.lba_low_reg |= last_sector >> 24 << 8 & 0xFF00; this.push_irq(); break; @@ -848,33 +862,33 @@ IDEInterface.prototype.ata_command = function(cmd) this.ata_write_sectors(cmd); break; - case 0x90: + case 0x90: // EXECUTE DEVICE DIAGNOSTIC, see [ATA-6] 8.11 dbg_log(this.name + ": ATA execute device diagnostic", LOG_DISK); - // assign diagnostics code (used to be 0x101?) to error register + // assign diagnostic code (used to be 0x101?) to error register if(this.interface_nr === 0) { // master drive is currently selected if(this.channel.slave.drive_connected) { - this.error = 0x01; // Master drive passed, slave passed or not present + this.error_reg = 0x01; // Master drive passed, slave passed or not present } else { - this.error = 0x81; // Master drive passed, slave failed + this.error_reg = 0x81; // Master drive passed, slave failed } } else { if(this.channel.slave.drive_connected) { - this.error = 0x01; // Slave drive passed + this.error_reg = 0x01; // Slave drive passed } else { - this.error = 0x00; // Slave drive failed + this.error_reg = 0x00; // Slave drive failed } } - this.status = 0x50; + this.status_reg = ATA_SR_DRDY; this.push_irq(); break; case 0x91: - // initialize device parameters (not mentioned in ATA/ATAPI-6) - this.status = 0x50; + // initialize device parameters (not mentioned in ATA-6) + this.status_reg = 0x50; this.push_irq(); break; @@ -882,10 +896,10 @@ IDEInterface.prototype.ata_command = function(cmd) // ATA packet if(this.is_atapi) { - this.status = 0x58; + this.status_reg = 0x58; this.data_allocate(12); this.data_end = 12; - this.bytecount = 1; + this.sector_count_reg = 1; this.push_irq(); } break; @@ -895,16 +909,16 @@ IDEInterface.prototype.ata_command = function(cmd) if(this.is_atapi) { this.create_identify_packet(); - this.status = 0x58; + this.status_reg = 0x58; - this.cylinder_low = 0x14; - this.cylinder_high = 0xEB; + this.lba_mid_reg = 0x14; + this.lba_high_reg = 0xEB; this.push_irq(); } else { - this.status = 0x41; + this.status_reg = 0x41; this.push_irq(); } break; @@ -912,9 +926,9 @@ IDEInterface.prototype.ata_command = function(cmd) case 0xC6: dbg_log(this.name + ": ATA set multiple mode", LOG_DISK); // Logical sectors per DRQ Block in word 1 - dbg_log(this.name + ": logical sectors per DRQ Block: " + h(this.bytecount & 0xFF), LOG_DISK); - this.sectors_per_drq = this.bytecount & 0xFF; - this.status = 0x50; + dbg_log(this.name + ": logical sectors per DRQ Block: " + h(this.sector_count_reg & 0xFF), LOG_DISK); + this.sectors_per_drq = this.sector_count_reg & 0xFF; + this.status_reg = 0x50; this.push_irq(); break; @@ -930,42 +944,42 @@ IDEInterface.prototype.ata_command = function(cmd) case 0x40: dbg_log(this.name + ": ATA read verify sector(s)", LOG_DISK); - this.status = 0x50; + this.status_reg = 0x50; this.push_irq(); break; case 0xDA: dbg_log(this.name + ": ATA get media status", LOG_DISK); - this.status = 0x50; - this.error = 0; + this.status_reg = 0x50; + this.error_reg = 0; if(this.is_atapi) { if(!this.buffer) { - this.error |= 0x02; // NM: No Media + this.error_reg |= 0x02; // NM: No Media } if(this.media_changed) { - this.error |= 0x20; // MC: Media Change + this.error_reg |= 0x20; // MC: Media Change this.media_changed = false; } - this.error |= 0x40; // WP: Write Protect + this.error_reg |= 0x40; // WP: Write Protect } this.push_irq(); break; case 0xE0: dbg_log(this.name + ": ATA standby immediate", LOG_DISK); - this.status = 0x50; + this.status_reg = 0x50; this.push_irq(); break; case 0xE1: dbg_log(this.name + ": ATA idle immediate", LOG_DISK); - this.status = 0x50; + this.status_reg = 0x50; this.push_irq(); break; case 0xE7: dbg_log(this.name + ": ATA flush cache", LOG_DISK); - this.status = 0x50; + this.status_reg = 0x50; this.push_irq(); break; @@ -974,61 +988,61 @@ IDEInterface.prototype.ata_command = function(cmd) if(this.is_atapi) { - this.status = 0x41; - this.error = 4; + this.status_reg = 0x41; + this.error_reg = 4; this.push_irq(); return; } this.create_identify_packet(); - this.status = 0x58; + this.status_reg = 0x58; this.push_irq(); break; case 0xEA: dbg_log(this.name + ": ATA flush cache ext", LOG_DISK); - this.status = 0x50; + this.status_reg = 0x50; this.push_irq(); break; case 0xEF: - dbg_log(this.name + ": ATA set features: " + h(this.bytecount & 0xFF), LOG_DISK); - this.status = 0x50; + dbg_log(this.name + ": ATA set features: " + h(this.sector_count_reg & 0xFF), LOG_DISK); + this.status_reg = 0x50; this.push_irq(); break; case 0xDE: dbg_log(this.name + ": ATA media lock", LOG_DISK); - this.status = 0x50; + this.status_reg = 0x50; this.push_irq(); break; case 0xF5: dbg_log(this.name + ": ATA security freeze lock", LOG_DISK); - this.status = 0x50; + this.status_reg = 0x50; this.push_irq(); break; case 0xF9: dbg_log(this.name + ": ATA set max address (unimplemented)", LOG_DISK); - this.status = 0x41; - this.error = 4; + this.status_reg = 0x41; + this.error_reg = 4; break; default: dbg_assert(false, this.name + ": unhandled ATA command: " + h(cmd), LOG_DISK); - this.status = 0x41; + this.status_reg = 0x41; // abort bit set - this.error = 4; + this.error_reg = 4; } }; IDEInterface.prototype.atapi_handle = function() { dbg_log(this.name + ": ATAPI Command: " + h(this.data[0]) + - " slave=" + (this.drive_head >> 4 & 1), LOG_DISK); + " slave=" + (this.device_reg >> 4 & 1), LOG_DISK); this.data_pointer = 0; this.current_atapi_command = this.data[0]; @@ -1039,11 +1053,11 @@ IDEInterface.prototype.atapi_handle = function() this.current_atapi_command === 0x51)) { dbg_log(this.name + ": CD read-related action: no buffer", LOG_DISK); - this.status = 0x51; - this.error = 0x21; + this.status_reg = 0x51; + this.error_reg = 0x21; this.data_allocate(0); this.data_end = this.data_length; - this.bytecount = this.bytecount & ~7 | 2 | 1; + this.sector_count_reg = this.sector_count_reg & ~7 | 2 | 1; this.push_irq(); return; } @@ -1055,24 +1069,24 @@ IDEInterface.prototype.atapi_handle = function() // test unit ready this.data_allocate(0); this.data_end = this.data_length; - this.status = 0x50; + this.status_reg = 0x50; break; case 0x03: // request sense this.data_allocate(this.data[4]); this.data_end = this.data_length; - this.status = 0x58; + this.status_reg = 0x58; this.data[0] = 0x80 | 0x70; - this.data[2] = this.error >> 4; + this.data[2] = this.error_reg >> 4; this.data[7] = 8; break; case 0x12: // inquiry var length = this.data[4]; - this.status = 0x58; + this.status_reg = 0x58; dbg_log(this.name + ": inquiry: " + h(this.data[1], 2) + " length=" + length, LOG_DISK); @@ -1104,14 +1118,14 @@ IDEInterface.prototype.atapi_handle = function() // mode sense (6) this.data_allocate(this.data[4]); this.data_end = this.data_length; - this.status = 0x58; + this.status_reg = 0x58; break; case 0x1E: // prevent/allow medium removal this.data_allocate(0); this.data_end = this.data_length; - this.status = 0x50; + this.status_reg = 0x50; break; case 0x25: @@ -1128,12 +1142,12 @@ IDEInterface.prototype.atapi_handle = function() this.sector_size & 0xFF, ])); this.data_end = this.data_length; - this.status = 0x58; + this.status_reg = 0x58; break; case 0x28: // read - if(this.lba_count & 1) + if(this.features_reg & 1) { this.atapi_read_dma(this.data); } @@ -1148,7 +1162,7 @@ IDEInterface.prototype.atapi_handle = function() this.data_allocate(Math.min(8, length)); this.data_end = this.data_length; dbg_log(this.name + ": read q subcode: length=" + length, LOG_DISK); - this.status = 0x58; + this.status_reg = 0x58; break; case 0x43: @@ -1202,7 +1216,7 @@ IDEInterface.prototype.atapi_handle = function() dbg_assert(false, this.name + ": Unimplemented format: " + format); } - this.status = 0x58; + this.status_reg = 0x58; break; case 0x46: @@ -1217,21 +1231,21 @@ IDEInterface.prototype.atapi_handle = function() this.data[3] = length - 4 & 0xFF; this.data[6] = 0x08; this.data[10] = 3; - this.status = 0x58; + this.status_reg = 0x58; break; case 0x51: // read disk information this.data_allocate(0); this.data_end = this.data_length; - this.status = 0x50; + this.status_reg = 0x50; break; case 0x52: dbg_log(this.name + ": unimplemented ATAPI command: " + h(this.data[0]), LOG_DISK); - this.status = 0x51; + this.status_reg = 0x51; this.data_length = 0; - this.error = 5 << 4; + this.error_reg = 5 << 4; break; case 0x5A: @@ -1244,7 +1258,7 @@ IDEInterface.prototype.atapi_handle = function() this.data_allocate(Math.min(30, length)); } this.data_end = this.data_length; - this.status = 0x58; + this.status_reg = 0x58; break; case 0xBD: @@ -1252,13 +1266,13 @@ IDEInterface.prototype.atapi_handle = function() this.data_allocate(this.data[9] | this.data[8] << 8); this.data_end = this.data_length; this.data[5] = 1; - this.status = 0x58; + this.status_reg = 0x58; break; case 0x4A: - this.status = 0x51; + this.status_reg = 0x51; this.data_length = 0; - this.error = 5 << 4; + this.error_reg = 5 << 4; dbg_log(this.name + ": unimplemented ATAPI command: " + h(this.data[0]), LOG_DISK); break; @@ -1267,34 +1281,34 @@ IDEInterface.prototype.atapi_handle = function() dbg_log(this.name + ": unimplemented ATAPI command: " + h(this.data[0]), LOG_DISK); this.data_allocate(0); this.data_end = this.data_length; - this.status = 0x50; + this.status_reg = 0x50; break; default: - this.status = 0x51; + this.status_reg = 0x51; this.data_length = 0; - this.error = 5 << 4; + this.error_reg = 5 << 4; dbg_log(this.name + ": unimplemented ATAPI command: " + h(this.data[0]), LOG_DISK); dbg_assert(false); } - this.bytecount = this.bytecount & ~7 | 2; + this.sector_count_reg = this.sector_count_reg & ~7 | 2; - if((this.status & 0x80) === 0) + if((this.status_reg & 0x80) === 0) { this.push_irq(); } - if((this.status & 0x80) === 0 && this.data_length === 0) + if((this.status_reg & 0x80) === 0 && this.data_length === 0) { - this.bytecount |= 1; - this.status &= ~8; + this.sector_count_reg |= 1; + this.status_reg &= ~8; } }; IDEInterface.prototype.do_write = function() { - this.status = 0x50; + this.status_reg = 0x50; dbg_assert(this.data_length <= this.data.length); var data = this.data.subarray(0, this.data_length); @@ -1326,9 +1340,9 @@ IDEInterface.prototype.atapi_read = function(cmd) " flags=" + h(flags), LOG_DISK); this.data_length = 0; - var req_length = this.cylinder_high << 8 & 0xFF00 | this.cylinder_low & 0xFF; - dbg_log(this.name + ": " + h(this.cylinder_high, 2) + " " + h(this.cylinder_low, 2), LOG_DISK); - this.cylinder_low = this.cylinder_high = 0; // oak technology driver (windows 3.0) + var req_length = this.lba_high_reg << 8 & 0xFF00 | this.lba_mid_reg & 0xFF; + dbg_log(this.name + ": " + h(this.lba_high_reg, 2) + " " + h(this.lba_mid_reg, 2), LOG_DISK); + this.lba_mid_reg = this.lba_high_reg = 0; // oak technology driver (windows 3.0) if(req_length === 0xFFFF) req_length--; @@ -1341,8 +1355,8 @@ IDEInterface.prototype.atapi_read = function(cmd) if(!this.buffer) { dbg_assert(false, this.name + ": CD read: no buffer", LOG_DISK); - this.status = 0xFF; - this.error = 0x41; + this.status_reg = 0xFF; + this.error_reg = 0x41; this.push_irq(); } else if(start >= this.buffer.byteLength) @@ -1350,12 +1364,12 @@ IDEInterface.prototype.atapi_read = function(cmd) dbg_assert(false, this.name + ": CD read: Outside of disk end=" + h(start + byte_count) + " size=" + h(this.buffer.byteLength), LOG_DISK); - this.status = 0xFF; + this.status_reg = 0xFF; this.push_irq(); } else if(byte_count === 0) { - this.status = 0x50; + this.status_reg = 0x50; this.data_pointer = 0; //this.push_irq(); @@ -1363,7 +1377,7 @@ IDEInterface.prototype.atapi_read = function(cmd) else { byte_count = Math.min(byte_count, this.buffer.byteLength - start); - this.status = 0x50 | 0x80; + this.status_reg = 0x50 | 0x80; this.report_read_start(); this.read_buffer(start, byte_count, (data) => @@ -1371,8 +1385,8 @@ IDEInterface.prototype.atapi_read = function(cmd) //setTimeout(() => { dbg_log(this.name + ": CD read: data arrived", LOG_DISK); this.data_set(data); - this.status = 0x58; - this.bytecount = this.bytecount & ~7 | 2; + this.status_reg = 0x58; + this.sector_count_reg = this.sector_count_reg & ~7 | 2; this.push_irq(); @@ -1383,8 +1397,8 @@ IDEInterface.prototype.atapi_read = function(cmd) { this.data_end = this.data_length; } - this.cylinder_low = this.data_end & 0xFF; - this.cylinder_high = this.data_end >> 8 & 0xFF; + this.lba_mid_reg = this.data_end & 0xFF; + this.lba_high_reg = this.data_end >> 8 & 0xFF; this.report_read_end(byte_count); //}, 10); @@ -1411,20 +1425,20 @@ IDEInterface.prototype.atapi_read_dma = function(cmd) dbg_assert(false, this.name + ": CD read: Outside of disk end=" + h(start + byte_count) + " size=" + h(this.buffer.byteLength), LOG_DISK); - this.status = 0xFF; + this.status_reg = 0xFF; this.push_irq(); } else { - this.status = 0x50 | 0x80; + this.status_reg = 0x50 | 0x80; this.report_read_start(); this.read_buffer(start, byte_count, (data) => { dbg_log(this.name + ": atapi_read_dma: Data arrived"); this.report_read_end(byte_count); - this.status = 0x58; - this.bytecount = this.bytecount & ~7 | 2; + this.status_reg = 0x58; + this.sector_count_reg = this.sector_count_reg & ~7 | 2; this.data_set(data); this.do_atapi_dma(); @@ -1440,7 +1454,7 @@ IDEInterface.prototype.do_atapi_dma = function() return; } - if((this.status & 0x8) === 0) + if((this.status_reg & 0x8) === 0) { dbg_log(this.name + ": do_atapi_dma: DRQ not set", LOG_DISK); return; @@ -1482,9 +1496,9 @@ IDEInterface.prototype.do_atapi_dma = function() dbg_log(this.name + ": end offset=" + offset, LOG_DISK); - this.status = 0x50; + this.status_reg = 0x50; this.channel.dma_status &= ~1; - this.bytecount = this.bytecount & ~7 | 3; + this.sector_count_reg = this.sector_count_reg & ~7 | 3; this.push_irq(); }; @@ -1544,21 +1558,21 @@ IDEInterface.prototype.read_end = function() { if(this.data_end === this.data_length) { - this.status = 0x50; - this.bytecount = this.bytecount & ~7 | 3; + this.status_reg = 0x50; + this.sector_count_reg = this.sector_count_reg & ~7 | 3; this.push_irq(); } else { - this.status = 0x58; - this.bytecount = this.bytecount & ~7 | 2; + this.status_reg = 0x58; + this.sector_count_reg = this.sector_count_reg & ~7 | 2; this.push_irq(); - var byte_count = this.cylinder_high << 8 & 0xFF00 | this.cylinder_low & 0xFF; + var byte_count = this.lba_high_reg << 8 & 0xFF00 | this.lba_mid_reg & 0xFF; if(this.data_end + byte_count > this.data_length) { - this.cylinder_low = (this.data_length - this.data_end) & 0xFF; - this.cylinder_high = (this.data_length - this.data_end) >> 8 & 0xFF; + this.lba_mid_reg = (this.data_length - this.data_end) & 0xFF; + this.lba_high_reg = (this.data_length - this.data_end) >> 8 & 0xFF; this.data_end = this.data_length; } else @@ -1570,10 +1584,10 @@ IDEInterface.prototype.read_end = function() } else { - this.error = 0; + this.error_reg = 0; if(this.data_pointer >= this.data_length) { - this.status = 0x50; + this.status_reg = 0x50; } else { @@ -1590,7 +1604,7 @@ IDEInterface.prototype.read_end = function() } this.ata_advance(this.current_command, sector_count); this.data_end += 512 * sector_count; - this.status = 0x58; + this.status_reg = 0x58; this.push_irq(); } } @@ -1676,7 +1690,7 @@ IDEInterface.prototype.write_end = function() // XXX: Should advance here, but do_write does all the advancing //this.ata_advance(this.current_command, 1); - this.status = 0x58; + this.status_reg = 0x58; this.data_end += 512; this.push_irq(); } @@ -1685,23 +1699,23 @@ IDEInterface.prototype.write_end = function() IDEInterface.prototype.ata_advance = function(cmd, sectors) { - dbg_log(this.name + ": advance sectors=" + sectors + " old_bytecount=" + this.bytecount, LOG_DISK); - this.bytecount -= sectors; + dbg_log(this.name + ": advance sectors=" + sectors + " old_sector_count_reg=" + this.sector_count_reg, LOG_DISK); + this.sector_count_reg -= sectors; if(cmd === 0x24 || cmd === 0x29 || cmd === 0x34 || cmd === 0x39 || cmd === 0x25 || cmd === 0x35) { var new_sector = sectors + this.get_lba48(); - this.sector = new_sector & 0xFF | new_sector >> 16 & 0xFF00; - this.cylinder_low = new_sector >> 8 & 0xFF; - this.cylinder_high = new_sector >> 16 & 0xFF; + this.lba_low_reg = new_sector & 0xFF | new_sector >> 16 & 0xFF00; + this.lba_mid_reg = new_sector >> 8 & 0xFF; + this.lba_high_reg = new_sector >> 16 & 0xFF; } else if(this.is_lba) { var new_sector = sectors + this.get_lba28(); - this.sector = new_sector & 0xFF; - this.cylinder_low = new_sector >> 8 & 0xFF; - this.cylinder_high = new_sector >> 16 & 0xFF; + this.lba_low_reg = new_sector & 0xFF; + this.lba_mid_reg = new_sector >> 8 & 0xFF; + this.lba_high_reg = new_sector >> 16 & 0xFF; this.head = this.head & ~0xF | new_sector & 0xF; } else // chs @@ -1709,10 +1723,10 @@ IDEInterface.prototype.ata_advance = function(cmd, sectors) var new_sector = sectors + this.get_chs(); var c = new_sector / (this.head_count * this.sectors_per_track) | 0; - this.cylinder_low = c & 0xFF; - this.cylinder_high = c >> 8 & 0xFF; + this.lba_mid_reg = c & 0xFF; + this.lba_high_reg = c >> 8 & 0xFF; this.head = (new_sector / this.sectors_per_track | 0) % this.head_count & 0xF; - this.sector = (new_sector % this.sectors_per_track + 1) & 0xFF; + this.lba_low_reg = (new_sector % this.sectors_per_track + 1) & 0xFF; dbg_assert(new_sector === this.get_chs()); } @@ -1739,12 +1753,12 @@ IDEInterface.prototype.ata_read_sectors = function(cmd) { dbg_assert(false, this.name + ": ATA read: Outside of disk", LOG_DISK); - this.status = 0xFF; + this.status_reg = 0xFF; this.push_irq(); } else { - this.status = 0x80 | 0x40; + this.status_reg = 0x80 | 0x40; this.report_read_start(); this.read_buffer(start, byte_count, (data) => @@ -1753,7 +1767,7 @@ IDEInterface.prototype.ata_read_sectors = function(cmd) dbg_log(this.name + ": ata_read: Data arrived", LOG_DISK); this.data_set(data); - this.status = 0x58; + this.status_reg = 0x58; this.data_end = is_single ? 512 : Math.min(byte_count, this.sectors_per_drq * 512); this.ata_advance(cmd, is_single ? 1 : Math.min(count, this.sectors_per_track)); @@ -1781,12 +1795,12 @@ IDEInterface.prototype.ata_read_sectors_dma = function(cmd) { dbg_assert(false, this.name + ": ATA read: Outside of disk", LOG_DISK); - this.status = 0xFF; + this.status_reg = 0xFF; this.push_irq(); return; } - this.status = 0x58; + this.status_reg = 0x58; this.channel.dma_status |= 1; }; @@ -1839,7 +1853,7 @@ IDEInterface.prototype.do_ata_read_sectors_dma = function() dbg_assert(offset === byte_count); this.ata_advance(this.current_command, count); - this.status = 0x50; + this.status_reg = 0x50; this.channel.dma_status &= ~1; this.current_command = -1; @@ -1869,12 +1883,12 @@ IDEInterface.prototype.ata_write_sectors = function(cmd) { dbg_assert(false, this.name + ": ATA write: Outside of disk", LOG_DISK); - this.status = 0xFF; + this.status_reg = 0xFF; this.push_irq(); } else { - this.status = 0x58; + this.status_reg = 0x58; this.data_allocate_noclear(byte_count); this.data_end = is_single ? 512 : Math.min(byte_count, this.sectors_per_drq * 512); this.write_dest = start; @@ -1898,12 +1912,12 @@ IDEInterface.prototype.ata_write_sectors_dma = function(cmd) { dbg_assert(false, this.name + ": ATA DMA write: Outside of disk", LOG_DISK); - this.status = 0xFF; + this.status_reg = 0xFF; this.push_irq(); return; } - this.status = 0x58; + this.status_reg = 0x58; this.channel.dma_status |= 1; }; @@ -1959,7 +1973,7 @@ IDEInterface.prototype.do_ata_write_sectors_dma = function() { dbg_log(this.name + ": DMA write completed", LOG_DISK); this.ata_advance(this.current_command, count); - this.status = 0x50; + this.status_reg = 0x50; this.push_irq(); this.channel.dma_status &= ~1; this.current_command = -1; @@ -1970,9 +1984,9 @@ IDEInterface.prototype.do_ata_write_sectors_dma = function() IDEInterface.prototype.get_chs = function() { - var c = this.cylinder_low & 0xFF | this.cylinder_high << 8 & 0xFF00; + var c = this.lba_mid_reg & 0xFF | this.lba_high_reg << 8 & 0xFF00; var h = this.head; - var s = this.sector & 0xFF; + var s = this.lba_low_reg & 0xFF; dbg_log(this.name + ": get_chs: c=" + c + " h=" + h + " s=" + s, LOG_DISK); @@ -1981,19 +1995,19 @@ IDEInterface.prototype.get_chs = function() IDEInterface.prototype.get_lba28 = function() { - return this.sector & 0xFF | - this.cylinder_low << 8 & 0xFF00 | - this.cylinder_high << 16 & 0xFF0000 | + return this.lba_low_reg & 0xFF | + this.lba_mid_reg << 8 & 0xFF00 | + this.lba_high_reg << 16 & 0xFF0000 | (this.head & 0xF) << 24; }; IDEInterface.prototype.get_lba48 = function() { // Note: Bits over 32 missing - return (this.sector & 0xFF | - this.cylinder_low << 8 & 0xFF00 | - this.cylinder_high << 16 & 0xFF0000 | - (this.sector >> 8) << 24 & 0xFF000000) >>> 0; + return (this.lba_low_reg & 0xFF | + this.lba_mid_reg << 8 & 0xFF00 | + this.lba_high_reg << 16 & 0xFF0000 | + (this.lba_low_reg >> 8) << 24 & 0xFF000000) >>> 0; }; IDEInterface.prototype.get_lba = function(is_lba48) @@ -2016,13 +2030,13 @@ IDEInterface.prototype.get_count = function(is_lba48) { if(is_lba48) { - var count = this.bytecount; + var count = this.sector_count_reg; if(count === 0) count = 0x10000; return count; } else { - var count = this.bytecount & 0xFF; + var count = this.sector_count_reg & 0xFF; if(count === 0) count = 0x100; return count; } @@ -2126,11 +2140,7 @@ IDEInterface.prototype.create_identify_packet = function() IDEInterface.prototype.data_allocate = function(len) { this.data_allocate_noclear(len); - - for(var i = 0; i < (len + 3 >> 2); i++) - { - this.data32[i] = 0; - } + this.data32.fill(0, 0, len + 3 >> 2); }; IDEInterface.prototype.data_allocate_noclear = function(len) @@ -2201,30 +2211,30 @@ IDEInterface.prototype.cancel_io_operations = function() IDEInterface.prototype.get_state = function() { var state = []; - state[0] = this.bytecount; + state[0] = this.sector_count_reg; state[1] = this.cylinder_count; - state[2] = this.cylinder_high; - state[3] = this.cylinder_low; + state[2] = this.lba_high_reg; + state[3] = this.lba_mid_reg; state[4] = this.data_pointer; state[5] = 0; state[6] = 0; state[7] = 0; state[8] = 0; - state[9] = this.drive_head; - state[10] = this.error; + state[9] = this.device_reg; + state[10] = this.error_reg; state[11] = this.head; state[12] = this.head_count; state[13] = this.is_atapi; state[14] = this.is_lba; - state[15] = this.lba_count; + state[15] = this.features_reg; state[16] = this.data; state[17] = this.data_length; - state[18] = this.sector; + state[18] = this.lba_low_reg; state[19] = this.sector_count; state[20] = this.sector_size; state[21] = this.sectors_per_drq; state[22] = this.sectors_per_track; - state[23] = this.status; + state[23] = this.status_reg; state[24] = this.write_dest; state[25] = this.current_command; state[26] = this.data_end; @@ -2235,27 +2245,27 @@ IDEInterface.prototype.get_state = function() IDEInterface.prototype.set_state = function(state) { - this.bytecount = state[0]; + this.sector_count_reg = state[0]; this.cylinder_count = state[1]; - this.cylinder_high = state[2]; - this.cylinder_low = state[3]; + this.lba_high_reg = state[2]; + this.lba_mid_reg = state[3]; this.data_pointer = state[4]; - this.drive_head = state[9]; - this.error = state[10]; + this.device_reg = state[9]; + this.error_reg = state[10]; this.head = state[11]; this.head_count = state[12]; this.is_atapi = state[13]; this.is_lba = state[14]; - this.lba_count = state[15]; + this.features_reg = state[15]; this.data = state[16]; this.data_length = state[17]; - this.sector = state[18]; + this.lba_low_reg = state[18]; this.sector_count = state[19]; this.sector_size = state[20]; this.sectors_per_drq = state[21]; this.sectors_per_track = state[22]; - this.status = state[23]; + this.status_reg = state[23]; this.write_dest = state[24]; this.current_command = state[25]; From 81483e41faecac0d8049a29ef905c18caed32a94 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Tue, 13 May 2025 21:21:06 +0200 Subject: [PATCH 081/301] first round of ATA command overhaul --- src/ide.js | 158 ++++++++++++++++++++++++++++------------------------- 1 file changed, 85 insertions(+), 73 deletions(-) diff --git a/src/ide.js b/src/ide.js index 9b61d9ef..bab65a8d 100644 --- a/src/ide.js +++ b/src/ide.js @@ -802,7 +802,7 @@ IDEInterface.prototype.ata_command = function(cmd) switch(cmd) { - case 0x08: // DEVICE RESET, see [ATA-6] 8.10 and 9.11 + case 0x08: // ATA: DEVICE RESET, see [ATA-6] 8.10 and 9.11 dbg_log(this.name + ": ATA device reset", LOG_DISK); this.data_pointer = 0; this.data_end = 0; @@ -811,32 +811,40 @@ IDEInterface.prototype.ata_command = function(cmd) this.push_irq(); break; - case 0x10: // see [ATA-6] Table E.2: obsolete! + case 0x10: // command obsolete, see [ATA-6] Table E.2 dbg_log(this.name + ": ATA calibrate drive", LOG_DISK); - this.status_reg = 0x50; this.lba_mid_reg = 0; + this.status_reg = 0x50; // unknown, likely ATA_SR_DRDY this.push_irq(); break; - case 0xF8: + case 0xF8: // ATA: READ NATIVE MAX ADDRESS, see [ATA-6] 8.32 dbg_log(this.name + ": ATA read native max address", LOG_DISK); - this.status_reg = 0x50; var last_sector = this.sector_count - 1; this.lba_low_reg = last_sector & 0xFF; this.lba_mid_reg = last_sector >> 8 & 0xFF; this.lba_high_reg = last_sector >> 16 & 0xFF; - this.device_reg = this.device_reg & 0xF0 | last_sector >> 24 & 0x0F; + this.device_reg = this.device_reg & 0x10 | last_sector >> 24 & 0x0F; + this.status_reg = ATA_SR_DRDY; this.push_irq(); break; - case 0x27: + case 0x27: // ATA: READ NATIVE MAX ADDRESS EXT, see [ATA-6] 8.33 dbg_log(this.name + ": ATA read native max address ext", LOG_DISK); - this.status_reg = 0x50; var last_sector = this.sector_count - 1; - this.lba_low_reg = last_sector & 0xFF; - this.lba_mid_reg = last_sector >> 8 & 0xFF; - this.lba_high_reg = last_sector >> 16 & 0xFF; - this.lba_low_reg |= last_sector >> 24 << 8 & 0xFF00; + if(this.channel.device_control_reg & ATA_CR_HOB === 0) + { + this.lba_low_reg = last_sector & 0xFF; + this.lba_mid_reg = last_sector >> 8 & 0xFF; + this.lba_high_reg = last_sector >> 16 & 0xFF; + } + else + { + this.lba_low_reg = last_sector >> 24 & 0xFF; + this.lba_mid_reg = last_sector >> 32 & 0xFF; + this.lba_high_reg = last_sector >> 40 & 0xFF; + } + this.status_reg = ATA_SR_DRDY; this.push_irq(); break; @@ -862,23 +870,29 @@ IDEInterface.prototype.ata_command = function(cmd) this.ata_write_sectors(cmd); break; - case 0x90: // EXECUTE DEVICE DIAGNOSTIC, see [ATA-6] 8.11 + case 0x90: // ATA: EXECUTE DEVICE DIAGNOSTIC, see [ATA-6] 8.11 dbg_log(this.name + ": ATA execute device diagnostic", LOG_DISK); + // TODO: this code is completely wrong (master device replies if slave does not exist) // assign diagnostic code (used to be 0x101?) to error register - if(this.interface_nr === 0) { - // master drive is currently selected - if(this.channel.slave.drive_connected) { + if(this.interface_nr === 0) + { + if(this.channel.slave.drive_connected) + { this.error_reg = 0x01; // Master drive passed, slave passed or not present } - else { + else + { this.error_reg = 0x81; // Master drive passed, slave failed } } - else { - if(this.channel.slave.drive_connected) { + else + { + if(this.channel.slave.drive_connected) + { this.error_reg = 0x01; // Slave drive passed } - else { + else + { this.error_reg = 0x00; // Slave drive failed } } @@ -886,49 +900,44 @@ IDEInterface.prototype.ata_command = function(cmd) this.push_irq(); break; - case 0x91: - // initialize device parameters (not mentioned in ATA-6) - this.status_reg = 0x50; + case 0x91: // initialize device parameters (not mentioned in [ATA-6]) + this.status_reg = 0x50; // unknown, likely ATA_SR_DRDY this.push_irq(); break; - case 0xA0: - // ATA packet + case 0xA0: // ATA: PACKET, see [ATA-6] 8.23 if(this.is_atapi) { - this.status_reg = 0x58; this.data_allocate(12); this.data_end = 12; - this.sector_count_reg = 1; + this.sector_count_reg = 0x01; // 0x01: indicates transfer of a command packet (C/D) + this.status_reg = ATA_SR_DRDY|ATA_SR_DRQ|0x10; // 0x10: another command is ready to be serviced (SERV) this.push_irq(); } break; - case 0xA1: + case 0xA1: // ATA: IDENTIFY PACKET DEVICE, see [ATA-6] 8.16 dbg_log(this.name + ": ATA identify packet device", LOG_DISK); if(this.is_atapi) { this.create_identify_packet(); - this.status_reg = 0x58; - - this.lba_mid_reg = 0x14; - this.lba_high_reg = 0xEB; - + this.status_reg = ATA_SR_DRDY|ATA_SR_DRQ; this.push_irq(); } else { - this.status_reg = 0x41; + this.error_reg = ATA_ER_ABRT; + this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; this.push_irq(); } break; - case 0xC6: + case 0xC6: // ATA: SET MULTIPLE MODE, see [ATA-6] 8.49 dbg_log(this.name + ": ATA set multiple mode", LOG_DISK); // Logical sectors per DRQ Block in word 1 dbg_log(this.name + ": logical sectors per DRQ Block: " + h(this.sector_count_reg & 0xFF), LOG_DISK); this.sectors_per_drq = this.sector_count_reg & 0xFF; - this.status_reg = 0x50; + this.status_reg = ATA_SR_DRDY; this.push_irq(); break; @@ -942,100 +951,103 @@ IDEInterface.prototype.ata_command = function(cmd) this.ata_write_sectors_dma(cmd); break; - case 0x40: + case 0x40: // ATA: READ VERIFY SECTOR(S), see [ATA-6] 8.36 dbg_log(this.name + ": ATA read verify sector(s)", LOG_DISK); - this.status_reg = 0x50; + // TODO: check that lba_low/mid/high and sector_count regs are within the bounds of the disk's size + this.status_reg = ATA_SR_DRDY; this.push_irq(); break; - case 0xDA: + case 0xDA: // ATA: GET MEDIA STATUS, see [ATA-6] 8.14 dbg_log(this.name + ": ATA get media status", LOG_DISK); - this.status_reg = 0x50; this.error_reg = 0; - if(this.is_atapi) { - if(!this.buffer) { + if(this.is_atapi) + { + if(!this.buffer) + { this.error_reg |= 0x02; // NM: No Media } - if(this.media_changed) { + if(this.media_changed) + { this.error_reg |= 0x20; // MC: Media Change this.media_changed = false; } this.error_reg |= 0x40; // WP: Write Protect } + this.status_reg = ATA_SR_DRDY; this.push_irq(); break; - case 0xE0: + case 0xE0: // ATA: STANDBY IMMEDIATE, see [ATA-6] 8.53 dbg_log(this.name + ": ATA standby immediate", LOG_DISK); - this.status_reg = 0x50; + this.status_reg = ATA_SR_DRDY; this.push_irq(); break; - case 0xE1: + case 0xE1: // ATA: IDLE IMMEDIATE, see [ATA-6] 8.18 dbg_log(this.name + ": ATA idle immediate", LOG_DISK); - this.status_reg = 0x50; + this.status_reg = ATA_SR_DRDY; this.push_irq(); break; - case 0xE7: + case 0xE7: // ATA: FLUSH CACHE, see [ATA-6] 8.12 dbg_log(this.name + ": ATA flush cache", LOG_DISK); - this.status_reg = 0x50; + this.status_reg = ATA_SR_DRDY; this.push_irq(); break; - case 0xEC: + case 0xEC: // ATA: IDENTIFY DEVICE, see [ATA-6] 8.15 dbg_log(this.name + ": ATA identify device", LOG_DISK); - if(this.is_atapi) { - this.status_reg = 0x41; - this.error_reg = 4; - this.push_irq(); - return; + this.error_reg = ATA_ER_ABRT; + this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; + } + else + { + this.create_identify_packet(); + this.status_reg = ATA_SR_DRDY|ATA_SR_DRQ; } - - this.create_identify_packet(); - this.status_reg = 0x58; - this.push_irq(); break; - case 0xEA: + case 0xEA: // ATA: FLUSH CACHE EXT, see [ATA-6] 8.13 dbg_log(this.name + ": ATA flush cache ext", LOG_DISK); - this.status_reg = 0x50; + this.status_reg = ATA_SR_DRDY; this.push_irq(); break; - case 0xEF: + case 0xEF: // ATA: SET FEATURES, see [ATA-6] 8.46 dbg_log(this.name + ": ATA set features: " + h(this.sector_count_reg & 0xFF), LOG_DISK); + // TODO: this one is important, accept/refuse requested device features this.status_reg = 0x50; this.push_irq(); break; - case 0xDE: + case 0xDE: // ATA: MEDIA LOCK, see [ATA-6] 8.20 dbg_log(this.name + ": ATA media lock", LOG_DISK); - this.status_reg = 0x50; + this.status_reg = ATA_SR_DRDY; this.push_irq(); break; - case 0xF5: + case 0xF5: // ATA: SECURITY FREEZE LOCK, see [ATA-6] 8.41 dbg_log(this.name + ": ATA security freeze lock", LOG_DISK); - this.status_reg = 0x50; + this.status_reg = ATA_SR_DRDY; this.push_irq(); break; - case 0xF9: + case 0xF9: // ATA: SET MAX, see [ATA-6] 8.47 dbg_log(this.name + ": ATA set max address (unimplemented)", LOG_DISK); - this.status_reg = 0x41; - this.error_reg = 4; + this.error_reg = ATA_ER_ABRT; + this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; + this.push_irq(); break; default: dbg_assert(false, this.name + ": unhandled ATA command: " + h(cmd), LOG_DISK); - - this.status_reg = 0x41; - // abort bit set - this.error_reg = 4; + this.error_reg = ATA_ER_ABRT; + this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; + this.push_irq(); } }; From 588dcaa876d1f809619312de5ac2207a2d01c718 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Wed, 14 May 2025 20:01:30 +0200 Subject: [PATCH 082/301] replaced more magic numbers with named constants --- src/ide.js | 244 ++++++++++++++++++++++++++++------------------------- 1 file changed, 131 insertions(+), 113 deletions(-) diff --git a/src/ide.js b/src/ide.js index bab65a8d..4c6c6beb 100644 --- a/src/ide.js +++ b/src/ide.js @@ -7,6 +7,11 @@ import { CMOS_BIOS_DISKTRANSFLAG, CMOS_DISK_DATA, CMOS_DISK_DRIVE1_CYL, CMOS_DIS import { CPU } from "./cpu.js"; import { BusConnector } from "./bus.js"; +// ATA/ATAPI-6 IDE Controller with support for: +// - up to 4 IDE devices (2 IDE channels with 2 IDE interfaces per channel) +// - ATA hard-disk devices +// - ATAPI CD-ROM devices +// // References // [ATA-6] // AT Attachment with Packet Interface - 6 (ATA/ATAPI-6) (Rev. 3a, 14 Dec. 2001) @@ -53,10 +58,44 @@ const ATA_DR_DEV = 0x10; // Device select, slave device if set, else master d // Device Control register bits: // - Bits 0 and 3-6 are reserved. -const ATA_CR_NIEN = 0x02; // Interrupt disable (Not Interrupt Enable) +const ATA_CR_nIEN = 0x02; // Interrupt disable (not Interrupt Enable) const ATA_CR_SRST = 0x04; // Software reset const ATA_CR_HOB = 0x80; // 48-bit Address feature set +// ATA commands +const ATA_CMD_DEVICE_RESET = 0x08; // see [ATA-6] 8.10 and 9.11 +const ATA_CMD_10h = 0x10; // command obsolete, see [ATA-6] Table E.2 +const ATA_CMD_READ_NATIVE_MAX_ADDRESS = 0xF8; // see [ATA-6] 8.32 +const ATA_CMD_READ_NATIVE_MAX_ADDRESS_EXT = 0x27; // see [ATA-6] 8.33 +const ATA_CMD_READ_SECTORS = 0x20; // see [ATA-6] 8.34 +const ATA_CMD_READ_SECTORS_EXT = 0x24; // see [ATA-6] 8.35 +const ATA_CMD_READ_MULTIPLE = 0x29; // see [ATA-6] 8.30 +const ATA_CMD_READ_MULTIPLE_EXT = 0xC4; // see [ATA-6] 8.31 +const ATA_CMD_WRITE_SECTORS = 0x30; // see [ATA-6] 8.62 +const ATA_CMD_WRITE_SECTORS_EXT = 0x34; // see [ATA-6] 8.63 +const ATA_CMD_WRITE_MULTIPLE = 0x39; // see [ATA-6] 8.60 +const ATA_CMD_WRITE_MULTIPLE_EXT = 0xC5; // see [ATA-6] 8.61 +const ATA_CMD_EXECUTE_DEVICE_DIAGNOSTIC = 0x90; // see [ATA-6] 8.11 +const ATA_CMD_INITIALIZE_DEVICE_PARAMETERS = 0x91; // not mentioned in [ATA-6] +const ATA_CMD_PACKET = 0xA0; // see [ATA-6] 8.23 +const ATA_CMD_IDENTIFY_PACKET_DEVICE = 0xA1; // see [ATA-6] 8.16 +const ATA_CMD_SET_MULTIPLE_MODE = 0xC6; // see [ATA-6] 8.49 +const ATA_CMD_READ_DMA = 0xC8; // see [ATA-6] 8.26 +const ATA_CMD_READ_DMA_EXT = 0x25; // see [ATA-6] 8.25 +const ATA_CMD_WRITE_DMA = 0xCA; // see [ATA-6] 8.55 +const ATA_CMD_WRITE_DMA_EXT = 0x35; // see [ATA-6] 8.56 +const ATA_CMD_READ_VERIFY_SECTORS = 0x40; // see [ATA-6] 8.36 +const ATA_CMD_GET_MEDIA_STATUS = 0xDA; // see [ATA-6] 8.14 +const ATA_CMD_STANDBY_IMMEDIATE = 0xE0; // see [ATA-6] 8.53 +const ATA_CMD_IDLE_IMMEDIATE = 0xE1; // see [ATA-6] 8.18 +const ATA_CMD_FLUSH_CACHE = 0xE7; // see [ATA-6] 8.12 +const ATA_CMD_FLUSH_CACHE_EXT = 0xEA; // see [ATA-6] 8.13 +const ATA_CMD_IDENTIFY_DEVICE = 0xEC; // see [ATA-6] 8.15 +const ATA_CMD_SET_FEATURES = 0xEF; // see [ATA-6] 8.46 +const ATA_CMD_MEDIA_LOCK = 0xDE; // see [ATA-6] 8.20 +const ATA_CMD_SECURITY_FREEZE_LOCK = 0xF5; // see [ATA-6] 8.41 +const ATA_CMD_SET_MAX = 0xF9; // see [ATA-6] 8.47 + /** * @constructor * @param {CPU} cpu @@ -144,24 +183,15 @@ export function IDEController(cpu, bus, ide_config) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; this.pci_bars = [ - { - size: 8, // BAR0: Command block registers of primary ATA Channel - }, - { - size: 4, // BAR1: Control registers of primary ATA Channel - }, - { - size: 8, // BAR2: Command block registers of secondary ATA Channel - }, - { - size: 4, // BAR3: Control registers of secondary ATA Channel - }, - { - size: 16, // BAR4: ATA Bus Master I/O registers (primary ATA Channel: 0..7, secondary: 8..15) - } + has_primary ? { size: 8 } : undefined, // BAR0: Command block register address of primary channel + has_primary ? { size: 4 } : undefined, // BAR1: Control block register address of primary channel + has_secondary ? { size: 8 } : undefined, // BAR2: Command block register address of secondary channel + has_secondary ? { size: 4 } : undefined, // BAR3: Control block register address of secondary channel + { size: 16 } // BAR4: Bus Master I/O register address of both channels (8+8) ]; cpu.devices.pci.register_device(this); } + Object.seal(this); } @@ -197,7 +227,7 @@ function IDEChannel(controller, controller_config, channel_nr, channel_config) this.slave.init_interface(); /** @type {number} */ - this.device_control_reg = 2; + this.device_control_reg = ATA_CR_nIEN; /** @type {number} */ this.prdt_addr = 0; @@ -318,9 +348,7 @@ function IDEChannel(controller, controller_config, channel_nr, channel_config) cpu.io.register_write(this.command_base | ATA_REG_DEVICE, this, function(data) { - var slave = data & 0x10; - // var mode = data & 0xE0; // unused - + const slave = data & 0x10; dbg_log(this.current_interface.name + ": write Device register: " + h(data, 2), LOG_DISK); if((slave && this.current_interface === this.master) || (!slave && this.current_interface === this.slave)) { @@ -335,7 +363,6 @@ function IDEChannel(controller, controller_config, channel_nr, channel_config) this.current_interface = this.master; } } - this.current_interface.device_reg = data; this.current_interface.is_lba = data >> 6 & 1; this.current_interface.head = data & 0xF; @@ -345,10 +372,9 @@ function IDEChannel(controller, controller_config, channel_nr, channel_config) { dbg_log(this.current_interface.name + ": write Command register", LOG_DISK); dbg_log(this.current_interface.name + ": lower IRQ " + this.irq, LOG_DISK); - this.cpu.device_lower_irq(this.irq); - // clear error and DF bits - this.current_interface.status_reg &= ~(1 | (1 << 5)); + this.current_interface.status_reg &= ~(ATA_SR_ERR | 0x20); // 0x20: command dependent, used to be Drive write fault (DF) this.current_interface.ata_command(data); + this.cpu.device_lower_irq(this.irq); }); // @@ -391,18 +417,15 @@ IDEChannel.prototype.read_status = function() IDEChannel.prototype.write_control = function(data) { dbg_log(this.current_interface.name + ": write Device Control register: " + - h(data, 2) + " interrupts " + ((data & 2) ? "disabled" : "enabled"), LOG_DISK); - - if(data & 0x04) + h(data, 2) + " interrupts " + ((data & ATA_CR_nIEN) ? "disabled" : "enabled"), LOG_DISK); + if(data & ATA_CR_SRST) { - dbg_log(this.current_interface.name + ": reset via control port", LOG_DISK); + dbg_log(this.current_interface.name + ": soft reset via control port", LOG_DISK); dbg_log(this.current_interface.name + ": lower IRQ " + this.irq, LOG_DISK); this.cpu.device_lower_irq(this.irq); - this.master.device_reset(); this.slave.device_reset(); } - this.device_control_reg = data; }; @@ -471,24 +494,21 @@ IDEChannel.prototype.dma_write_command8 = function(value) switch(this.current_interface.current_command) { - case 0x25: - case 0xC8: + case ATA_CMD_READ_DMA: + case ATA_CMD_READ_DMA_EXT: this.current_interface.do_ata_read_sectors_dma(); break; - - case 0xCA: - case 0x35: + case ATA_CMD_WRITE_DMA: + case ATA_CMD_WRITE_DMA_EXT: this.current_interface.do_ata_write_sectors_dma(); break; - - case 0xA0: + case ATA_CMD_PACKET: this.current_interface.do_atapi_dma(); break; - default: dbg_log(this.current_interface.name + ": spurious DMA command write, current command: " + h(this.current_interface.current_command), LOG_DISK); - dbg_log(this.current_interface.name + ": DMA clear status 1 bit, set status 2 bit", LOG_DISK); + dbg_log(this.current_interface.name + ": DMA clear status bit 1h, set status bit 2h", LOG_DISK); this.dma_status &= ~1; this.dma_status |= 2; this.push_irq(); @@ -498,7 +518,7 @@ IDEChannel.prototype.dma_write_command8 = function(value) IDEChannel.prototype.push_irq = function() { - if((this.device_control_reg & 2) === 0) + if((this.device_control_reg & ATA_CR_nIEN) === 0) { dbg_log(this.current_interface.name + ": push IRQ " + this.irq, LOG_DISK); this.dma_status |= 4; @@ -625,7 +645,7 @@ function IDEInterface(channel, cpu, buffer, is_cd, channel_nr, interface_nr, bus this.device_reg = 0; /** @type {number} */ - this.status_reg = 0x50; + this.status_reg = ATA_SR_DRDY; /** @type {number} */ this.sectors_per_drq = 0x80; @@ -677,8 +697,8 @@ IDEInterface.prototype.eject = function() { this.media_changed = true; this.buffer = null; - this.status_reg = 0x59; - this.error_reg = 0x60; + this.status_reg = 0x59; // TODO + this.error_reg = 0x60; // TODO this.push_irq(); } }; @@ -702,8 +722,8 @@ IDEInterface.prototype.set_disk_buffer = function(buffer) this.buffer = buffer; if(this.is_atapi) { - this.status_reg = 0x59; - this.error_reg = 0x60; + this.status_reg = 0x59; // TODO + this.error_reg = 0x60; // TODO } this.sector_count = this.buffer.byteLength / this.sector_size; @@ -776,9 +796,8 @@ IDEInterface.prototype.device_reset = function() this.lba_high_reg = 0; this.device_reg = 0; } - this.error_reg &= ~0x80; // clear bit 7 only (explicitly, why?) + this.error_reg &= ~0x80; // clear bit 7 only (explicitly) this.status_reg &= ~(ATA_SR_ERR|ATA_SR_BSY); - this.cancel_io_operations(); }; @@ -789,9 +808,9 @@ IDEInterface.prototype.push_irq = function() IDEInterface.prototype.ata_command = function(cmd) { - dbg_log(this.name + ": ATA Command: " + h(cmd) + " slave=" + (this.device_reg >> 4 & 1), LOG_DISK); + dbg_log(this.name + ": ATA Command: " + h(cmd), LOG_DISK); - if(!this.drive_connected && cmd !== 0x90) + if(!this.drive_connected && cmd !== ATA_CMD_EXECUTE_DEVICE_DIAGNOSTIC) { dbg_log(this.name + ": ATA command ignored: No slave drive connected", LOG_DISK); return; @@ -802,7 +821,7 @@ IDEInterface.prototype.ata_command = function(cmd) switch(cmd) { - case 0x08: // ATA: DEVICE RESET, see [ATA-6] 8.10 and 9.11 + case ATA_CMD_DEVICE_RESET: dbg_log(this.name + ": ATA device reset", LOG_DISK); this.data_pointer = 0; this.data_end = 0; @@ -811,25 +830,25 @@ IDEInterface.prototype.ata_command = function(cmd) this.push_irq(); break; - case 0x10: // command obsolete, see [ATA-6] Table E.2 + case ATA_CMD_10h: dbg_log(this.name + ": ATA calibrate drive", LOG_DISK); this.lba_mid_reg = 0; this.status_reg = 0x50; // unknown, likely ATA_SR_DRDY this.push_irq(); break; - case 0xF8: // ATA: READ NATIVE MAX ADDRESS, see [ATA-6] 8.32 + case ATA_CMD_READ_NATIVE_MAX_ADDRESS: dbg_log(this.name + ": ATA read native max address", LOG_DISK); var last_sector = this.sector_count - 1; this.lba_low_reg = last_sector & 0xFF; this.lba_mid_reg = last_sector >> 8 & 0xFF; this.lba_high_reg = last_sector >> 16 & 0xFF; - this.device_reg = this.device_reg & 0x10 | last_sector >> 24 & 0x0F; + this.device_reg = this.device_reg & ATA_DR_DEV | last_sector >> 24 & 0x0F; this.status_reg = ATA_SR_DRDY; this.push_irq(); break; - case 0x27: // ATA: READ NATIVE MAX ADDRESS EXT, see [ATA-6] 8.33 + case ATA_CMD_READ_NATIVE_MAX_ADDRESS_EXT: dbg_log(this.name + ": ATA read native max address ext", LOG_DISK); var last_sector = this.sector_count - 1; if(this.channel.device_control_reg & ATA_CR_HOB === 0) @@ -848,29 +867,21 @@ IDEInterface.prototype.ata_command = function(cmd) this.push_irq(); break; - case 0x20: - case 0x24: - case 0x29: - case 0xC4: - // 0x20 read sectors - // 0x24 read sectors ext - // 0xC4 read multiple - // 0x29 read multiple ext + case ATA_CMD_READ_SECTORS: + case ATA_CMD_READ_SECTORS_EXT: + case ATA_CMD_READ_MULTIPLE: + case ATA_CMD_READ_MULTIPLE_EXT: this.ata_read_sectors(cmd); break; - case 0x30: - case 0x34: - case 0x39: - case 0xC5: - // 0x30 write sectors - // 0x34 write sectors ext - // 0xC5 write multiple - // 0x39 write multiple ext + case ATA_CMD_WRITE_SECTORS: + case ATA_CMD_WRITE_SECTORS_EXT: + case ATA_CMD_WRITE_MULTIPLE: + case ATA_CMD_WRITE_MULTIPLE_EXT: this.ata_write_sectors(cmd); break; - case 0x90: // ATA: EXECUTE DEVICE DIAGNOSTIC, see [ATA-6] 8.11 + case ATA_CMD_EXECUTE_DEVICE_DIAGNOSTIC: dbg_log(this.name + ": ATA execute device diagnostic", LOG_DISK); // TODO: this code is completely wrong (master device replies if slave does not exist) // assign diagnostic code (used to be 0x101?) to error register @@ -900,12 +911,12 @@ IDEInterface.prototype.ata_command = function(cmd) this.push_irq(); break; - case 0x91: // initialize device parameters (not mentioned in [ATA-6]) + case ATA_CMD_INITIALIZE_DEVICE_PARAMETERS: this.status_reg = 0x50; // unknown, likely ATA_SR_DRDY this.push_irq(); break; - case 0xA0: // ATA: PACKET, see [ATA-6] 8.23 + case ATA_CMD_PACKET: if(this.is_atapi) { this.data_allocate(12); @@ -916,23 +927,22 @@ IDEInterface.prototype.ata_command = function(cmd) } break; - case 0xA1: // ATA: IDENTIFY PACKET DEVICE, see [ATA-6] 8.16 + case ATA_CMD_IDENTIFY_PACKET_DEVICE: dbg_log(this.name + ": ATA identify packet device", LOG_DISK); if(this.is_atapi) { this.create_identify_packet(); this.status_reg = ATA_SR_DRDY|ATA_SR_DRQ; - this.push_irq(); } else { this.error_reg = ATA_ER_ABRT; this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; - this.push_irq(); } + this.push_irq(); break; - case 0xC6: // ATA: SET MULTIPLE MODE, see [ATA-6] 8.49 + case ATA_CMD_SET_MULTIPLE_MODE: dbg_log(this.name + ": ATA set multiple mode", LOG_DISK); // Logical sectors per DRQ Block in word 1 dbg_log(this.name + ": logical sectors per DRQ Block: " + h(this.sector_count_reg & 0xFF), LOG_DISK); @@ -941,24 +951,24 @@ IDEInterface.prototype.ata_command = function(cmd) this.push_irq(); break; - case 0x25: // read dma ext - case 0xC8: // read dma + case ATA_CMD_READ_DMA: + case ATA_CMD_READ_DMA_EXT: this.ata_read_sectors_dma(cmd); break; - case 0x35: // write dma ext - case 0xCA: // write dma + case ATA_CMD_WRITE_DMA: + case ATA_CMD_WRITE_DMA_EXT: this.ata_write_sectors_dma(cmd); break; - case 0x40: // ATA: READ VERIFY SECTOR(S), see [ATA-6] 8.36 + case ATA_CMD_READ_VERIFY_SECTORS: dbg_log(this.name + ": ATA read verify sector(s)", LOG_DISK); // TODO: check that lba_low/mid/high and sector_count regs are within the bounds of the disk's size this.status_reg = ATA_SR_DRDY; this.push_irq(); break; - case 0xDA: // ATA: GET MEDIA STATUS, see [ATA-6] 8.14 + case ATA_CMD_GET_MEDIA_STATUS: dbg_log(this.name + ": ATA get media status", LOG_DISK); this.error_reg = 0; if(this.is_atapi) @@ -978,25 +988,31 @@ IDEInterface.prototype.ata_command = function(cmd) this.push_irq(); break; - case 0xE0: // ATA: STANDBY IMMEDIATE, see [ATA-6] 8.53 + case ATA_CMD_STANDBY_IMMEDIATE: dbg_log(this.name + ": ATA standby immediate", LOG_DISK); this.status_reg = ATA_SR_DRDY; this.push_irq(); break; - case 0xE1: // ATA: IDLE IMMEDIATE, see [ATA-6] 8.18 + case ATA_CMD_IDLE_IMMEDIATE: dbg_log(this.name + ": ATA idle immediate", LOG_DISK); this.status_reg = ATA_SR_DRDY; this.push_irq(); break; - case 0xE7: // ATA: FLUSH CACHE, see [ATA-6] 8.12 + case ATA_CMD_FLUSH_CACHE: dbg_log(this.name + ": ATA flush cache", LOG_DISK); this.status_reg = ATA_SR_DRDY; this.push_irq(); break; - case 0xEC: // ATA: IDENTIFY DEVICE, see [ATA-6] 8.15 + case ATA_CMD_FLUSH_CACHE_EXT: + dbg_log(this.name + ": ATA flush cache ext", LOG_DISK); + this.status_reg = ATA_SR_DRDY; + this.push_irq(); + break; + + case ATA_CMD_IDENTIFY_DEVICE: dbg_log(this.name + ": ATA identify device", LOG_DISK); if(this.is_atapi) { @@ -1011,32 +1027,26 @@ IDEInterface.prototype.ata_command = function(cmd) this.push_irq(); break; - case 0xEA: // ATA: FLUSH CACHE EXT, see [ATA-6] 8.13 - dbg_log(this.name + ": ATA flush cache ext", LOG_DISK); - this.status_reg = ATA_SR_DRDY; - this.push_irq(); - break; - - case 0xEF: // ATA: SET FEATURES, see [ATA-6] 8.46 + case ATA_CMD_SET_FEATURES: dbg_log(this.name + ": ATA set features: " + h(this.sector_count_reg & 0xFF), LOG_DISK); // TODO: this one is important, accept/refuse requested device features - this.status_reg = 0x50; + this.status_reg = 0x50; // TODO this.push_irq(); break; - case 0xDE: // ATA: MEDIA LOCK, see [ATA-6] 8.20 + case ATA_CMD_MEDIA_LOCK: dbg_log(this.name + ": ATA media lock", LOG_DISK); this.status_reg = ATA_SR_DRDY; this.push_irq(); break; - case 0xF5: // ATA: SECURITY FREEZE LOCK, see [ATA-6] 8.41 + case ATA_CMD_SECURITY_FREEZE_LOCK: dbg_log(this.name + ": ATA security freeze lock", LOG_DISK); this.status_reg = ATA_SR_DRDY; this.push_irq(); break; - case 0xF9: // ATA: SET MAX, see [ATA-6] 8.47 + case ATA_CMD_SET_MAX: dbg_log(this.name + ": ATA set max address (unimplemented)", LOG_DISK); this.error_reg = ATA_ER_ABRT; this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; @@ -1053,8 +1063,7 @@ IDEInterface.prototype.ata_command = function(cmd) IDEInterface.prototype.atapi_handle = function() { - dbg_log(this.name + ": ATAPI Command: " + h(this.data[0]) + - " slave=" + (this.device_reg >> 4 & 1), LOG_DISK); + dbg_log(this.name + ": ATAPI Command: " + h(this.data[0]), LOG_DISK); this.data_pointer = 0; this.current_atapi_command = this.data[0]; @@ -1566,7 +1575,7 @@ IDEInterface.prototype.read_end = function() " data_pointer=" + h(this.data_pointer) + " end=" + h(this.data_end) + " length=" + h(this.data_length), LOG_DISK); - if(this.current_command === 0xA0) + if(this.current_command === ATA_CMD_PACKET) { if(this.data_end === this.data_length) { @@ -1603,7 +1612,7 @@ IDEInterface.prototype.read_end = function() } else { - if(this.current_command === 0xC4 || this.current_command === 0x29) + if(this.current_command === ATA_CMD_READ_MULTIPLE || this.current_command === ATA_CMD_READ_MULTIPLE_EXT) { var sector_count = Math.min(this.sectors_per_drq, (this.data_length - this.data_end) / 512); @@ -1611,7 +1620,7 @@ IDEInterface.prototype.read_end = function() } else { - dbg_assert(this.current_command === 0x20 || this.current_command === 0x24); + dbg_assert(this.current_command === ATA_CMD_READ_SECTORS || this.current_command === ATA_CMD_READ_SECTORS_EXT); var sector_count = 1; } this.ata_advance(this.current_command, sector_count); @@ -1714,8 +1723,12 @@ IDEInterface.prototype.ata_advance = function(cmd, sectors) dbg_log(this.name + ": advance sectors=" + sectors + " old_sector_count_reg=" + this.sector_count_reg, LOG_DISK); this.sector_count_reg -= sectors; - if(cmd === 0x24 || cmd === 0x29 || cmd === 0x34 || cmd === 0x39 || - cmd === 0x25 || cmd === 0x35) + if(cmd === ATA_CMD_READ_SECTORS_EXT || + cmd === ATA_CMD_READ_MULTIPLE || + cmd === ATA_CMD_READ_DMA_EXT || + cmd === ATA_CMD_WRITE_SECTORS_EXT || + cmd === ATA_CMD_WRITE_MULTIPLE || + cmd === ATA_CMD_WRITE_DMA_EXT) { var new_sector = sectors + this.get_lba48(); this.lba_low_reg = new_sector & 0xFF | new_sector >> 16 & 0xFF00; @@ -1746,11 +1759,11 @@ IDEInterface.prototype.ata_advance = function(cmd, sectors) IDEInterface.prototype.ata_read_sectors = function(cmd) { - var is_lba48 = cmd === 0x24 || cmd === 0x29; + var is_lba48 = cmd === ATA_CMD_READ_SECTORS_EXT || cmd === ATA_CMD_READ_MULTIPLE; var count = this.get_count(is_lba48); var lba = this.get_lba(is_lba48); - var is_single = cmd === 0x20 || cmd === 0x24; + var is_single = cmd === ATA_CMD_READ_SECTORS || cmd === ATA_CMD_READ_SECTORS_EXT; var byte_count = count * this.sector_size; var start = lba * this.sector_size; @@ -1792,7 +1805,7 @@ IDEInterface.prototype.ata_read_sectors = function(cmd) IDEInterface.prototype.ata_read_sectors_dma = function(cmd) { - var is_lba48 = cmd === 0x25; + var is_lba48 = cmd === ATA_CMD_READ_DMA_EXT; var count = this.get_count(is_lba48); var lba = this.get_lba(is_lba48); @@ -1820,7 +1833,7 @@ IDEInterface.prototype.do_ata_read_sectors_dma = function() { var cmd = this.current_command; - var is_lba48 = cmd === 0x25; + var is_lba48 = cmd === ATA_CMD_READ_DMA_EXT; var count = this.get_count(is_lba48); var lba = this.get_lba(is_lba48); @@ -1877,11 +1890,11 @@ IDEInterface.prototype.do_ata_read_sectors_dma = function() IDEInterface.prototype.ata_write_sectors = function(cmd) { - var is_lba48 = cmd === 0x34 || cmd === 0x39; + var is_lba48 = cmd === ATA_CMD_WRITE_SECTORS_EXT || cmd === ATA_CMD_WRITE_MULTIPLE; var count = this.get_count(is_lba48); var lba = this.get_lba(is_lba48); - var is_single = cmd === 0x30 || cmd === 0x34; + var is_single = cmd === ATA_CMD_WRITE_SECTORS || cmd === ATA_CMD_WRITE_SECTORS_EXT; var byte_count = count * this.sector_size; var start = lba * this.sector_size; @@ -1909,7 +1922,7 @@ IDEInterface.prototype.ata_write_sectors = function(cmd) IDEInterface.prototype.ata_write_sectors_dma = function(cmd) { - var is_lba48 = cmd === 0x35; + var is_lba48 = cmd === ATA_CMD_WRITE_DMA_EXT; var count = this.get_count(is_lba48); var lba = this.get_lba(is_lba48); @@ -1937,7 +1950,7 @@ IDEInterface.prototype.do_ata_write_sectors_dma = function() { var cmd = this.current_command; - var is_lba48 = cmd === 0x35; + var is_lba48 = cmd === ATA_CMD_WRITE_DMA_EXT; var count = this.get_count(is_lba48); var lba = this.get_lba(is_lba48); @@ -2252,6 +2265,8 @@ IDEInterface.prototype.get_state = function() state[26] = this.data_end; state[27] = this.current_atapi_command; state[28] = this.buffer; + state[29] = this.drive_connected; + state[30] = this.media_changed; return state; }; @@ -2288,4 +2303,7 @@ IDEInterface.prototype.set_state = function(state) this.data32 = new Int32Array(this.data.buffer); this.buffer && this.buffer.set_state(state[28]); + + this.drive_connected = state[29] === undefined ? this.is_atapi || this.buffer : state[29]; + this.media_changed = state[30] === undefined ? false : state[30]; }; From f253f8b67e8001ed0b5c1c946a8c7ff151add0f1 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Wed, 14 May 2025 20:27:36 +0200 Subject: [PATCH 083/301] replaced more magic numbers --- src/ide.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ide.js b/src/ide.js index 4c6c6beb..c5fe91bc 100644 --- a/src/ide.js +++ b/src/ide.js @@ -101,12 +101,12 @@ const ATA_CMD_SET_MAX = 0xF9; // see [ATA-6] 8.47 * @param {CPU} cpu * @param {BusConnector} bus * - * ide_config: [ [, ], [, ] ] + * ide_config: [ [primary-master, primary-slave], [secondary-master, secondary-slave] ] * - * Each of the four arguments (, , ...) is - * either undefined or an ide_config object of the form: + * Each of the four arguments (primary-master, primary-slave, ...) is either + * undefined or an object of the form: * - * ide_config := { buffer: Uint8Array, is_cdrom: bool } + * { buffer: Uint8Array, is_cdrom: bool } * * If is_cdrom is defined and true: * - If buffer is defined: create an ATAPI CD-ROM device using buffer as inserted disk @@ -1689,7 +1689,7 @@ IDEInterface.prototype.write_data_port32 = function(data) IDEInterface.prototype.write_end = function() { - if(this.current_command === 0xA0) + if(this.current_command === ATA_CMD_PACKET) { this.atapi_handle(); } @@ -1704,9 +1704,9 @@ IDEInterface.prototype.write_end = function() } else { - dbg_assert(this.current_command === 0x30 || - this.current_command === 0x34 || - this.current_command === 0xC5, + dbg_assert(this.current_command === ATA_CMD_WRITE_SECTORS || + this.current_command === ATA_CMD_WRITE_SECTORS_EXT || + this.current_command === ATA_CMD_WRITE_MULTIPLE_EXT, "Unexpected command: " + h(this.current_command)); // XXX: Should advance here, but do_write does all the advancing @@ -2135,7 +2135,7 @@ IDEInterface.prototype.create_identify_packet = function() 0, 0, // 63, dma supported mode, dma selected mode - this.current_command === 0xA0 ? 0 : 7, this.current_command === 0xA0 ? 0 : 4, + this.current_command === ATA_CMD_PACKET ? 0 : 7, this.current_command === ATA_CMD_PACKET ? 0 : 4, //0, 0, // no DMA 0, 0, From 64df9aef35ca01cce3ccf39807d8e1de449835f6 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Thu, 15 May 2025 17:24:46 +0200 Subject: [PATCH 084/301] added named constants for SCSI commands --- src/ide.js | 112 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 63 insertions(+), 49 deletions(-) diff --git a/src/ide.js b/src/ide.js index c5fe91bc..113065d7 100644 --- a/src/ide.js +++ b/src/ide.js @@ -13,9 +13,12 @@ import { BusConnector } from "./bus.js"; // - ATAPI CD-ROM devices // // References -// [ATA-6] -// AT Attachment with Packet Interface - 6 (ATA/ATAPI-6) (Rev. 3a, 14 Dec. 2001) +// - [ATA-6] +// AT Attachment with Packet Interface - 6 (ATA/ATAPI-6) (Rev. 3a; Dec. 14, 2001) // https://technion-csl.github.io/ose/readings/hardware/ATA-d1410r3a.pdf +// - [MMC-3] +// SCSI Multimedia Commands - 3 (MMC-3) (Rev. 10g; Nov. 12, 2001) +// https://ia902808.us.archive.org/33/items/mmc3r10g/mmc3r10g.pdf const CDROM_SECTOR_SIZE = 2048; const HD_SECTOR_SIZE = 512; @@ -96,6 +99,25 @@ const ATA_CMD_MEDIA_LOCK = 0xDE; // see [ATA-6] 8.20 const ATA_CMD_SECURITY_FREEZE_LOCK = 0xF5; // see [ATA-6] 8.41 const ATA_CMD_SET_MAX = 0xF9; // see [ATA-6] 8.47 +// ATAPI MMC commands (not all commands have their own chapter): +const MMC_CMD_TEST_UNIT_READY = 0x00; // see [MMC-3] +const MMC_CMD_REQUEST_SENSE = 0x03; // see [MMC-3] +const MMC_CMD_INQUIRY = 0x12; // see [MMC-3] +const MMC_CMD_MODE_SENSE_6 = 0x1A; // see [MMC-3] +const MMC_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1E; // see [MMC-3] +const MMC_CMD_READ_CAPACITY = 0x25; // see [MMC-3] 5.16 +const MMC_CMD_READ = 0x28; // see [MMC-3] 5.14 +const MMC_CMD_READ_SUBCHANNEL = 0x42; // see [MMC-3] 5.22 +const MMC_CMD_READ_TOC_PMA_ATIP = 0x43; // see [MMC-3] 5.23 +const MMC_CMD_GET_CONFIGURATION = 0x46; // see [MMC-3] 5.5 +const MMC_CMD_READ_DISK_INFORMATION = 0x51; // see [MMC-3] 5.19 +const MMC_CMD_READ_TRACK_INFORMATION = 0x52; // see [MMC-3] 5.24 +const MMC_CMD_MODE_SENSE_10 = 0x5A; // see [MMC-3] +const MMC_CMD_MECHANISM_STATUS = 0xBD; // see [MMC-3] 5.9 +const MMC_CMD_GET_EVENT_STATUS_NOTIFICATION = 0x4A; // see [MMC-3] 5.6 +const MMC_CMD_READ_CD = 0xBE; // see [MMC-3] 5.17 +//const MMC_CMD_LOAD_UNLOAD_MEDIUM = 0xA6; // see [MMC-3] 5.8 + /** * @constructor * @param {CPU} cpu @@ -1067,11 +1089,11 @@ IDEInterface.prototype.atapi_handle = function() this.data_pointer = 0; this.current_atapi_command = this.data[0]; - if(!this.buffer && (this.current_atapi_command === 0x25 || - this.current_atapi_command === 0x28 || - this.current_atapi_command === 0x42 || - this.current_atapi_command === 0x43 || - this.current_atapi_command === 0x51)) + if(!this.buffer && (this.current_atapi_command === MMC_CMD_READ_CAPACITY || + this.current_atapi_command === MMC_CMD_READ || + this.current_atapi_command === MMC_CMD_READ_SUBCHANNEL || + this.current_atapi_command === MMC_CMD_READ_TOC_PMA_ATIP || + this.current_atapi_command === MMC_CMD_READ_DISK_INFORMATION)) { dbg_log(this.name + ": CD read-related action: no buffer", LOG_DISK); this.status_reg = 0x51; @@ -1085,31 +1107,28 @@ IDEInterface.prototype.atapi_handle = function() switch(this.current_atapi_command) { - case 0x00: - dbg_log(this.name + ": test unit ready", LOG_DISK); - // test unit ready + case MMC_CMD_TEST_UNIT_READY: + dbg_log(this.name + ": ATAPI test unit ready", LOG_DISK); this.data_allocate(0); this.data_end = this.data_length; this.status_reg = 0x50; break; - case 0x03: - // request sense + case MMC_CMD_REQUEST_SENSE: + dbg_log(this.name + ": ATAPI request sense", LOG_DISK); this.data_allocate(this.data[4]); this.data_end = this.data_length; this.status_reg = 0x58; - this.data[0] = 0x80 | 0x70; this.data[2] = this.error_reg >> 4; this.data[7] = 8; break; - case 0x12: - // inquiry + case MMC_CMD_INQUIRY: var length = this.data[4]; this.status_reg = 0x58; - dbg_log(this.name + ": inquiry: " + h(this.data[1], 2) + " length=" + length, LOG_DISK); + dbg_log(this.name + ": ATAPI inquiry: " + h(this.data[1], 2) + " length=" + length, LOG_DISK); // http://www.t10.org/ftp/x3t9.2/document.87/87-106r0.txt //this.data_allocate(36); @@ -1135,22 +1154,22 @@ IDEInterface.prototype.atapi_handle = function() this.data_end = this.data_length = Math.min(36, length); break; - case 0x1A: - // mode sense (6) + case MMC_CMD_MODE_SENSE_6: + dbg_log(this.name + ": ATAPI mode sense (6)", LOG_DISK); this.data_allocate(this.data[4]); this.data_end = this.data_length; this.status_reg = 0x58; break; - case 0x1E: - // prevent/allow medium removal + case MMC_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: + dbg_log(this.name + ": ATAPI prevent allow medium removal", LOG_DISK); this.data_allocate(0); this.data_end = this.data_length; this.status_reg = 0x50; break; - case 0x25: - // read capacity + case MMC_CMD_READ_CAPACITY: + dbg_log(this.name + ": ATAPI read capacity", LOG_DISK); var count = this.sector_count - 1; this.data_set(new Uint8Array([ count >> 24 & 0xFF, @@ -1166,8 +1185,7 @@ IDEInterface.prototype.atapi_handle = function() this.status_reg = 0x58; break; - case 0x28: - // read + case MMC_CMD_READ: if(this.features_reg & 1) { this.atapi_read_dma(this.data); @@ -1178,29 +1196,28 @@ IDEInterface.prototype.atapi_handle = function() } break; - case 0x42: + case MMC_CMD_READ_SUBCHANNEL: var length = this.data[8]; this.data_allocate(Math.min(8, length)); this.data_end = this.data_length; - dbg_log(this.name + ": read q subcode: length=" + length, LOG_DISK); + dbg_log(this.name + ": ATAPI read subchannel: length=" + length, LOG_DISK); this.status_reg = 0x58; break; - case 0x43: - // read toc + case MMC_CMD_READ_TOC_PMA_ATIP: var length = this.data[8] | this.data[7] << 8; var format = this.data[9] >> 6; this.data_allocate(length); this.data_end = this.data_length; - dbg_log(this.name + ": read toc: " + h(format, 2) + + dbg_log(this.name + ": ATAPI read toc/pma/atip: " + h(format, 2) + " length=" + length + " " + (this.data[1] & 2) + " " + h(this.data[6]), LOG_DISK); if(format === 0) { - var sector_count = this.sector_count; + const sector_count = this.sector_count; this.data.set(new Uint8Array([ 0, 18, // length 1, 1, // first and last session @@ -1234,16 +1251,15 @@ IDEInterface.prototype.atapi_handle = function() } else { - dbg_assert(false, this.name + ": Unimplemented format: " + format); + dbg_assert(false, this.name + ": unimplemented format: " + format); } this.status_reg = 0x58; break; - case 0x46: - // get configuration - var length = this.data[8] | this.data[7] << 8; - length = Math.min(length, 32); + case MMC_CMD_GET_CONFIGURATION: + var length = Math.min(this.data[8] | this.data[7] << 8, 32); + dbg_log(this.name + ": ATAPI get configuration: length=" + length, LOG_DISK); this.data_allocate(length); this.data_end = this.data_length; this.data[0] = length - 4 >> 24 & 0xFF; @@ -1255,25 +1271,24 @@ IDEInterface.prototype.atapi_handle = function() this.status_reg = 0x58; break; - case 0x51: - // read disk information + case MMC_CMD_READ_DISK_INFORMATION: + dbg_log(this.name + ": ATAPI read disk information", LOG_DISK); this.data_allocate(0); this.data_end = this.data_length; this.status_reg = 0x50; break; - case 0x52: - dbg_log(this.name + ": unimplemented ATAPI command: " + h(this.data[0]), LOG_DISK); + case MMC_CMD_READ_TRACK_INFORMATION: + dbg_log(this.name + ": ATAPI read track information (unimplemented)", LOG_DISK); this.status_reg = 0x51; this.data_length = 0; this.error_reg = 5 << 4; break; - case 0x5A: - // mode sense + case MMC_CMD_MODE_SENSE_10: var length = this.data[8] | this.data[7] << 8; var page_code = this.data[2]; - dbg_log(this.name + ": mode sense: " + h(page_code) + " length=" + length, LOG_DISK); + dbg_log(this.name + ": ATAPI mode sense (10): page_code=" + h(page_code) + " length=" + length, LOG_DISK); if(page_code === 0x2A) { this.data_allocate(Math.min(30, length)); @@ -1282,24 +1297,23 @@ IDEInterface.prototype.atapi_handle = function() this.status_reg = 0x58; break; - case 0xBD: - // mechanism status + case MMC_CMD_MECHANISM_STATUS: + dbg_log(this.name + ": ATAPI mechanism status", LOG_DISK); this.data_allocate(this.data[9] | this.data[8] << 8); this.data_end = this.data_length; this.data[5] = 1; this.status_reg = 0x58; break; - case 0x4A: + case MMC_CMD_GET_EVENT_STATUS_NOTIFICATION: + dbg_log(this.name + ": ATAPI get event status notification (unimplemented)", LOG_DISK); this.status_reg = 0x51; this.data_length = 0; this.error_reg = 5 << 4; - dbg_log(this.name + ": unimplemented ATAPI command: " + h(this.data[0]), LOG_DISK); break; - case 0xBE: - // Hiren's boot CD - dbg_log(this.name + ": unimplemented ATAPI command: " + h(this.data[0]), LOG_DISK); + case MMC_CMD_READ_CD: + dbg_log(this.name + ": ATAPI read cd (unimplemented)", LOG_DISK); this.data_allocate(0); this.data_end = this.data_length; this.status_reg = 0x50; From 03d0b402fbd7033aa5dc0292a9c06c79b45b4bed Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Thu, 15 May 2025 20:19:44 +0200 Subject: [PATCH 085/301] added references to SPC-3 (SCSI Primary Commands) --- src/ide.js | 196 +++++++++++++++++++++++++++-------------------------- 1 file changed, 99 insertions(+), 97 deletions(-) diff --git a/src/ide.js b/src/ide.js index 113065d7..149b41d4 100644 --- a/src/ide.js +++ b/src/ide.js @@ -16,6 +16,9 @@ import { BusConnector } from "./bus.js"; // - [ATA-6] // AT Attachment with Packet Interface - 6 (ATA/ATAPI-6) (Rev. 3a; Dec. 14, 2001) // https://technion-csl.github.io/ose/readings/hardware/ATA-d1410r3a.pdf +// - [SPC-3] +// SCSI Primary Commands - 3 (SPC-3) (Jul 20, 2008) +// https://www.t10.org/ftp/t10/document.08/08-309r0.pdf // - [MMC-3] // SCSI Multimedia Commands - 3 (MMC-3) (Rev. 10g; Nov. 12, 2001) // https://ia902808.us.archive.org/33/items/mmc3r10g/mmc3r10g.pdf @@ -43,80 +46,80 @@ const ATA_REG_COMMAND = 0x07; // Command register, see [ATA-6] 7.4 const ATA_REG_CONTROL = 0x02; // (*1*) Device Control register, see [ATA-6] 7.8 // Error register bits: -// - All remaining bits (except bit 3) are command dependent. -const ATA_ER_ABRT = 0x04; // Command aborted +// All bits except for bit 0x04 are command dependent. +const ATA_ER_ABRT = 0x04; // Command aborted // Status register bits: -// - Bits 1 and 2 are obsolete, 4 and 5 are command dependent. -// - Bit 1 used to be called "Index" (IDX), bit 2 "Corrected data" (CORR), -// bit 4 "Drive seek complete" (DSC), and bit 5 "Drive write fault" (DF). -const ATA_SR_ERR = 0x01; // Error -const ATA_SR_DRQ = 0x08; // Data request ready -const ATA_SR_DRDY = 0x40; // Drive ready -const ATA_SR_BSY = 0x80; // Busy +// Bits 0x02/0x04 are obsolete and 0x10/0x20 are command dependent. +// Bit 0x02 used to be called "Index" (IDX), bit 0x04 "Corrected data" (CORR), +// bit 0x10 "Drive seek complete" (DSC), and bit 0x20 "Drive write fault" (DF). +const ATA_SR_ERR = 0x01; // Error +const ATA_SR_DRQ = 0x08; // Data request ready +const ATA_SR_DRDY = 0x40; // Drive ready +const ATA_SR_BSY = 0x80; // Busy // Device register bits: -// - Bits 5 and 7 are obsolete, 0-3 and 6 are command dependent. -const ATA_DR_DEV = 0x10; // Device select, slave device if set, else master device +// Bits 0x20/0x80 are obsolete and 0x01/0x02/0x04/0x08/0x40 are command dependent. +const ATA_DR_DEV = 0x10; // Device select, slave device if set, else master device // Device Control register bits: -// - Bits 0 and 3-6 are reserved. -const ATA_CR_nIEN = 0x02; // Interrupt disable (not Interrupt Enable) -const ATA_CR_SRST = 0x04; // Software reset -const ATA_CR_HOB = 0x80; // 48-bit Address feature set +// Bits 0x08/0x10/0x20/0x40 are reserved and bit 0x01 is always zero. +const ATA_CR_nIEN = 0x02; // Interrupt disable (not Interrupt Enable) +const ATA_CR_SRST = 0x04; // Software reset +const ATA_CR_HOB = 0x80; // 48-bit Address feature set // ATA commands -const ATA_CMD_DEVICE_RESET = 0x08; // see [ATA-6] 8.10 and 9.11 -const ATA_CMD_10h = 0x10; // command obsolete, see [ATA-6] Table E.2 -const ATA_CMD_READ_NATIVE_MAX_ADDRESS = 0xF8; // see [ATA-6] 8.32 -const ATA_CMD_READ_NATIVE_MAX_ADDRESS_EXT = 0x27; // see [ATA-6] 8.33 -const ATA_CMD_READ_SECTORS = 0x20; // see [ATA-6] 8.34 -const ATA_CMD_READ_SECTORS_EXT = 0x24; // see [ATA-6] 8.35 -const ATA_CMD_READ_MULTIPLE = 0x29; // see [ATA-6] 8.30 -const ATA_CMD_READ_MULTIPLE_EXT = 0xC4; // see [ATA-6] 8.31 -const ATA_CMD_WRITE_SECTORS = 0x30; // see [ATA-6] 8.62 -const ATA_CMD_WRITE_SECTORS_EXT = 0x34; // see [ATA-6] 8.63 -const ATA_CMD_WRITE_MULTIPLE = 0x39; // see [ATA-6] 8.60 -const ATA_CMD_WRITE_MULTIPLE_EXT = 0xC5; // see [ATA-6] 8.61 -const ATA_CMD_EXECUTE_DEVICE_DIAGNOSTIC = 0x90; // see [ATA-6] 8.11 -const ATA_CMD_INITIALIZE_DEVICE_PARAMETERS = 0x91; // not mentioned in [ATA-6] -const ATA_CMD_PACKET = 0xA0; // see [ATA-6] 8.23 -const ATA_CMD_IDENTIFY_PACKET_DEVICE = 0xA1; // see [ATA-6] 8.16 -const ATA_CMD_SET_MULTIPLE_MODE = 0xC6; // see [ATA-6] 8.49 -const ATA_CMD_READ_DMA = 0xC8; // see [ATA-6] 8.26 -const ATA_CMD_READ_DMA_EXT = 0x25; // see [ATA-6] 8.25 -const ATA_CMD_WRITE_DMA = 0xCA; // see [ATA-6] 8.55 -const ATA_CMD_WRITE_DMA_EXT = 0x35; // see [ATA-6] 8.56 -const ATA_CMD_READ_VERIFY_SECTORS = 0x40; // see [ATA-6] 8.36 -const ATA_CMD_GET_MEDIA_STATUS = 0xDA; // see [ATA-6] 8.14 -const ATA_CMD_STANDBY_IMMEDIATE = 0xE0; // see [ATA-6] 8.53 -const ATA_CMD_IDLE_IMMEDIATE = 0xE1; // see [ATA-6] 8.18 -const ATA_CMD_FLUSH_CACHE = 0xE7; // see [ATA-6] 8.12 -const ATA_CMD_FLUSH_CACHE_EXT = 0xEA; // see [ATA-6] 8.13 -const ATA_CMD_IDENTIFY_DEVICE = 0xEC; // see [ATA-6] 8.15 -const ATA_CMD_SET_FEATURES = 0xEF; // see [ATA-6] 8.46 -const ATA_CMD_MEDIA_LOCK = 0xDE; // see [ATA-6] 8.20 -const ATA_CMD_SECURITY_FREEZE_LOCK = 0xF5; // see [ATA-6] 8.41 -const ATA_CMD_SET_MAX = 0xF9; // see [ATA-6] 8.47 +const ATA_CMD_DEVICE_RESET = 0x08; // see [ATA-6] 8.10 and 9.11 +const ATA_CMD_10h = 0x10; // command obsolete, see [ATA-6] Table E.2 +const ATA_CMD_READ_NATIVE_MAX_ADDRESS = 0xF8; // see [ATA-6] 8.32 +const ATA_CMD_READ_NATIVE_MAX_ADDRESS_EXT = 0x27; // see [ATA-6] 8.33 +const ATA_CMD_READ_SECTORS = 0x20; // see [ATA-6] 8.34 +const ATA_CMD_READ_SECTORS_EXT = 0x24; // see [ATA-6] 8.35 +const ATA_CMD_READ_MULTIPLE = 0x29; // see [ATA-6] 8.30 +const ATA_CMD_READ_MULTIPLE_EXT = 0xC4; // see [ATA-6] 8.31 +const ATA_CMD_WRITE_SECTORS = 0x30; // see [ATA-6] 8.62 +const ATA_CMD_WRITE_SECTORS_EXT = 0x34; // see [ATA-6] 8.63 +const ATA_CMD_WRITE_MULTIPLE = 0x39; // see [ATA-6] 8.60 +const ATA_CMD_WRITE_MULTIPLE_EXT = 0xC5; // see [ATA-6] 8.61 +const ATA_CMD_EXECUTE_DEVICE_DIAGNOSTIC = 0x90; // see [ATA-6] 8.11 +const ATA_CMD_INITIALIZE_DEVICE_PARAMETERS = 0x91; // not mentioned in [ATA-6] +const ATA_CMD_PACKET = 0xA0; // see [ATA-6] 8.23 +const ATA_CMD_IDENTIFY_PACKET_DEVICE = 0xA1; // see [ATA-6] 8.16 +const ATA_CMD_SET_MULTIPLE_MODE = 0xC6; // see [ATA-6] 8.49 +const ATA_CMD_READ_DMA = 0xC8; // see [ATA-6] 8.26 +const ATA_CMD_READ_DMA_EXT = 0x25; // see [ATA-6] 8.25 +const ATA_CMD_WRITE_DMA = 0xCA; // see [ATA-6] 8.55 +const ATA_CMD_WRITE_DMA_EXT = 0x35; // see [ATA-6] 8.56 +const ATA_CMD_READ_VERIFY_SECTORS = 0x40; // see [ATA-6] 8.36 +const ATA_CMD_GET_MEDIA_STATUS = 0xDA; // see [ATA-6] 8.14 +const ATA_CMD_STANDBY_IMMEDIATE = 0xE0; // see [ATA-6] 8.53 +const ATA_CMD_IDLE_IMMEDIATE = 0xE1; // see [ATA-6] 8.18 +const ATA_CMD_FLUSH_CACHE = 0xE7; // see [ATA-6] 8.12 +const ATA_CMD_FLUSH_CACHE_EXT = 0xEA; // see [ATA-6] 8.13 +const ATA_CMD_IDENTIFY_DEVICE = 0xEC; // see [ATA-6] 8.15 +const ATA_CMD_SET_FEATURES = 0xEF; // see [ATA-6] 8.46 +const ATA_CMD_MEDIA_LOCK = 0xDE; // see [ATA-6] 8.20 +const ATA_CMD_SECURITY_FREEZE_LOCK = 0xF5; // see [ATA-6] 8.41 +const ATA_CMD_SET_MAX = 0xF9; // see [ATA-6] 8.47 -// ATAPI MMC commands (not all commands have their own chapter): -const MMC_CMD_TEST_UNIT_READY = 0x00; // see [MMC-3] -const MMC_CMD_REQUEST_SENSE = 0x03; // see [MMC-3] -const MMC_CMD_INQUIRY = 0x12; // see [MMC-3] -const MMC_CMD_MODE_SENSE_6 = 0x1A; // see [MMC-3] -const MMC_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1E; // see [MMC-3] -const MMC_CMD_READ_CAPACITY = 0x25; // see [MMC-3] 5.16 -const MMC_CMD_READ = 0x28; // see [MMC-3] 5.14 -const MMC_CMD_READ_SUBCHANNEL = 0x42; // see [MMC-3] 5.22 -const MMC_CMD_READ_TOC_PMA_ATIP = 0x43; // see [MMC-3] 5.23 -const MMC_CMD_GET_CONFIGURATION = 0x46; // see [MMC-3] 5.5 -const MMC_CMD_READ_DISK_INFORMATION = 0x51; // see [MMC-3] 5.19 -const MMC_CMD_READ_TRACK_INFORMATION = 0x52; // see [MMC-3] 5.24 -const MMC_CMD_MODE_SENSE_10 = 0x5A; // see [MMC-3] -const MMC_CMD_MECHANISM_STATUS = 0xBD; // see [MMC-3] 5.9 -const MMC_CMD_GET_EVENT_STATUS_NOTIFICATION = 0x4A; // see [MMC-3] 5.6 -const MMC_CMD_READ_CD = 0xBE; // see [MMC-3] 5.17 -//const MMC_CMD_LOAD_UNLOAD_MEDIUM = 0xA6; // see [MMC-3] 5.8 +// ATAPI commands +const ATAPI_CMD_TEST_UNIT_READY = 0x00; // see [SPC-3] 6.33 +const ATAPI_CMD_REQUEST_SENSE = 0x03; // see [SPC-3] 6.27 +const ATAPI_CMD_INQUIRY = 0x12; // see [SPC-3] 6.4 +const ATAPI_CMD_MODE_SENSE_6 = 0x1A; // see [SPC-3] 6.9 +const ATAPI_CMD_MODE_SENSE_10 = 0x5A; // see [SPC-3] 6.10 +const ATAPI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1E; // see [SPC-3] 6.13 +const ATAPI_CMD_READ_CAPACITY = 0x25; // see [MMC-3] 5.16 +const ATAPI_CMD_READ = 0x28; // see [MMC-3] 5.14 +const ATAPI_CMD_READ_SUBCHANNEL = 0x42; // see [MMC-3] 5.22 +const ATAPI_CMD_READ_TOC_PMA_ATIP = 0x43; // see [MMC-3] 5.23 +const ATAPI_CMD_GET_CONFIGURATION = 0x46; // see [MMC-3] 5.5 +const ATAPI_CMD_READ_DISK_INFORMATION = 0x51; // see [MMC-3] 5.19 +const ATAPI_CMD_READ_TRACK_INFORMATION = 0x52; // see [MMC-3] 5.24 +const ATAPI_CMD_MECHANISM_STATUS = 0xBD; // see [MMC-3] 5.9 +const ATAPI_CMD_GET_EVENT_STATUS_NOTIFICATION = 0x4A; // see [MMC-3] 5.6 +const ATAPI_CMD_READ_CD = 0xBE; // see [MMC-3] 5.17 +//const ATAPI_CMD_LOAD_UNLOAD_MEDIUM = 0xA6; // see [MMC-3] 5.8, TODO: not implemented, might be useful /** * @constructor @@ -152,13 +155,13 @@ export function IDEController(cpu, bus, ide_config) if(has_primary || has_secondary) { const bus_master_base = 0xB400; if(has_primary) { - this.primary = new IDEChannel(this, ide_config, 0, { + this.primary = new IDEChannel(this, 0, ide_config[0], { command_base: 0x1f0, control_base: 0x3f4, bus_master_base: bus_master_base, irq: 14 }); this.channels[0] = this.primary; } if(has_secondary) { - this.secondary = new IDEChannel(this, ide_config, 1, { + this.secondary = new IDEChannel(this, 1, ide_config[1], { command_base: 0x170, control_base: 0x374, bus_master_base: bus_master_base | 0x8, irq: 15 }); this.channels[1] = this.secondary; @@ -222,19 +225,18 @@ export function IDEController(cpu, bus, ide_config) * @param {IDEController} controller * @param {number} channel_nr * */ -function IDEChannel(controller, controller_config, channel_nr, channel_config) +function IDEChannel(controller, channel_nr, channel_config, hw_settings) { this.controller = controller; this.cpu = controller.cpu; this.bus = controller.bus; - this.command_base = channel_config.command_base; - this.control_base = channel_config.control_base; - this.irq = channel_config.irq; + this.command_base = hw_settings.command_base; + this.control_base = hw_settings.control_base; + this.irq = hw_settings.irq; this.name = "ide" + channel_nr; const create_interface = interface_nr => { - const config = controller_config && controller_config[channel_nr] ? - controller_config[channel_nr][interface_nr] : undefined; + const config = channel_config ? channel_config[interface_nr] : undefined; const buffer = config ? config.buffer : undefined; const is_cdrom = config ? !!config.is_cdrom : false; return new IDEInterface(this, this.cpu, buffer, is_cdrom, channel_nr, interface_nr, this.bus); @@ -413,7 +415,7 @@ function IDEChannel(controller, controller_config, channel_nr, channel_config) // Bus Master Registers: bus_master_base + 0...15 (BAR4: B400h, lower 8 for primary and upper 8 for secondary channel) // - const bus_master_base = channel_config.bus_master_base; + const bus_master_base = hw_settings.bus_master_base; cpu.io.register_read(bus_master_base, this, this.dma_read_command8, undefined, this.dma_read_command); @@ -1089,11 +1091,11 @@ IDEInterface.prototype.atapi_handle = function() this.data_pointer = 0; this.current_atapi_command = this.data[0]; - if(!this.buffer && (this.current_atapi_command === MMC_CMD_READ_CAPACITY || - this.current_atapi_command === MMC_CMD_READ || - this.current_atapi_command === MMC_CMD_READ_SUBCHANNEL || - this.current_atapi_command === MMC_CMD_READ_TOC_PMA_ATIP || - this.current_atapi_command === MMC_CMD_READ_DISK_INFORMATION)) + if(!this.buffer && (this.current_atapi_command === ATAPI_CMD_READ_CAPACITY || + this.current_atapi_command === ATAPI_CMD_READ || + this.current_atapi_command === ATAPI_CMD_READ_SUBCHANNEL || + this.current_atapi_command === ATAPI_CMD_READ_TOC_PMA_ATIP || + this.current_atapi_command === ATAPI_CMD_READ_DISK_INFORMATION)) { dbg_log(this.name + ": CD read-related action: no buffer", LOG_DISK); this.status_reg = 0x51; @@ -1107,14 +1109,14 @@ IDEInterface.prototype.atapi_handle = function() switch(this.current_atapi_command) { - case MMC_CMD_TEST_UNIT_READY: + case ATAPI_CMD_TEST_UNIT_READY: dbg_log(this.name + ": ATAPI test unit ready", LOG_DISK); this.data_allocate(0); this.data_end = this.data_length; this.status_reg = 0x50; break; - case MMC_CMD_REQUEST_SENSE: + case ATAPI_CMD_REQUEST_SENSE: dbg_log(this.name + ": ATAPI request sense", LOG_DISK); this.data_allocate(this.data[4]); this.data_end = this.data_length; @@ -1124,7 +1126,7 @@ IDEInterface.prototype.atapi_handle = function() this.data[7] = 8; break; - case MMC_CMD_INQUIRY: + case ATAPI_CMD_INQUIRY: var length = this.data[4]; this.status_reg = 0x58; @@ -1154,21 +1156,21 @@ IDEInterface.prototype.atapi_handle = function() this.data_end = this.data_length = Math.min(36, length); break; - case MMC_CMD_MODE_SENSE_6: + case ATAPI_CMD_MODE_SENSE_6: dbg_log(this.name + ": ATAPI mode sense (6)", LOG_DISK); this.data_allocate(this.data[4]); this.data_end = this.data_length; this.status_reg = 0x58; break; - case MMC_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: + case ATAPI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: dbg_log(this.name + ": ATAPI prevent allow medium removal", LOG_DISK); this.data_allocate(0); this.data_end = this.data_length; this.status_reg = 0x50; break; - case MMC_CMD_READ_CAPACITY: + case ATAPI_CMD_READ_CAPACITY: dbg_log(this.name + ": ATAPI read capacity", LOG_DISK); var count = this.sector_count - 1; this.data_set(new Uint8Array([ @@ -1185,7 +1187,7 @@ IDEInterface.prototype.atapi_handle = function() this.status_reg = 0x58; break; - case MMC_CMD_READ: + case ATAPI_CMD_READ: if(this.features_reg & 1) { this.atapi_read_dma(this.data); @@ -1196,7 +1198,7 @@ IDEInterface.prototype.atapi_handle = function() } break; - case MMC_CMD_READ_SUBCHANNEL: + case ATAPI_CMD_READ_SUBCHANNEL: var length = this.data[8]; this.data_allocate(Math.min(8, length)); this.data_end = this.data_length; @@ -1204,7 +1206,7 @@ IDEInterface.prototype.atapi_handle = function() this.status_reg = 0x58; break; - case MMC_CMD_READ_TOC_PMA_ATIP: + case ATAPI_CMD_READ_TOC_PMA_ATIP: var length = this.data[8] | this.data[7] << 8; var format = this.data[9] >> 6; @@ -1257,7 +1259,7 @@ IDEInterface.prototype.atapi_handle = function() this.status_reg = 0x58; break; - case MMC_CMD_GET_CONFIGURATION: + case ATAPI_CMD_GET_CONFIGURATION: var length = Math.min(this.data[8] | this.data[7] << 8, 32); dbg_log(this.name + ": ATAPI get configuration: length=" + length, LOG_DISK); this.data_allocate(length); @@ -1271,21 +1273,21 @@ IDEInterface.prototype.atapi_handle = function() this.status_reg = 0x58; break; - case MMC_CMD_READ_DISK_INFORMATION: + case ATAPI_CMD_READ_DISK_INFORMATION: dbg_log(this.name + ": ATAPI read disk information", LOG_DISK); this.data_allocate(0); this.data_end = this.data_length; this.status_reg = 0x50; break; - case MMC_CMD_READ_TRACK_INFORMATION: + case ATAPI_CMD_READ_TRACK_INFORMATION: dbg_log(this.name + ": ATAPI read track information (unimplemented)", LOG_DISK); this.status_reg = 0x51; this.data_length = 0; this.error_reg = 5 << 4; break; - case MMC_CMD_MODE_SENSE_10: + case ATAPI_CMD_MODE_SENSE_10: var length = this.data[8] | this.data[7] << 8; var page_code = this.data[2]; dbg_log(this.name + ": ATAPI mode sense (10): page_code=" + h(page_code) + " length=" + length, LOG_DISK); @@ -1297,7 +1299,7 @@ IDEInterface.prototype.atapi_handle = function() this.status_reg = 0x58; break; - case MMC_CMD_MECHANISM_STATUS: + case ATAPI_CMD_MECHANISM_STATUS: dbg_log(this.name + ": ATAPI mechanism status", LOG_DISK); this.data_allocate(this.data[9] | this.data[8] << 8); this.data_end = this.data_length; @@ -1305,14 +1307,14 @@ IDEInterface.prototype.atapi_handle = function() this.status_reg = 0x58; break; - case MMC_CMD_GET_EVENT_STATUS_NOTIFICATION: + case ATAPI_CMD_GET_EVENT_STATUS_NOTIFICATION: dbg_log(this.name + ": ATAPI get event status notification (unimplemented)", LOG_DISK); this.status_reg = 0x51; this.data_length = 0; this.error_reg = 5 << 4; break; - case MMC_CMD_READ_CD: + case ATAPI_CMD_READ_CD: dbg_log(this.name + ": ATAPI read cd (unimplemented)", LOG_DISK); this.data_allocate(0); this.data_end = this.data_length; @@ -1329,12 +1331,12 @@ IDEInterface.prototype.atapi_handle = function() this.sector_count_reg = this.sector_count_reg & ~7 | 2; - if((this.status_reg & 0x80) === 0) + if((this.status_reg & ATA_SR_BSY) === 0) { this.push_irq(); } - if((this.status_reg & 0x80) === 0 && this.data_length === 0) + if((this.status_reg & ATA_SR_BSY) === 0 && this.data_length === 0) { this.sector_count_reg |= 1; this.status_reg &= ~8; From a000334266f9bdf4f549b90bf156921caa8eaab4 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sat, 17 May 2025 12:14:10 +0200 Subject: [PATCH 086/301] added references to SAM-3 (SCSI Architecture Model) --- src/ide.js | 57 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/src/ide.js b/src/ide.js index 149b41d4..3e518488 100644 --- a/src/ide.js +++ b/src/ide.js @@ -16,6 +16,9 @@ import { BusConnector } from "./bus.js"; // - [ATA-6] // AT Attachment with Packet Interface - 6 (ATA/ATAPI-6) (Rev. 3a; Dec. 14, 2001) // https://technion-csl.github.io/ose/readings/hardware/ATA-d1410r3a.pdf +// - [SAM-3] +// SCSI Architecture Model - 3 (SAM-3) (Rev. 14; Sep. 21, 2004) +// https://dn790004.ca.archive.org/0/items/SCSISpecificationDocumentsSCSIDocuments/SCSI%20Architecture%20Model/SCSI%20Architecture%20Model%203%20rev%2014.pdf // - [SPC-3] // SCSI Primary Commands - 3 (SPC-3) (Jul 20, 2008) // https://www.t10.org/ftp/t10/document.08/08-309r0.pdf @@ -60,11 +63,11 @@ const ATA_SR_BSY = 0x80; // Busy // Device register bits: // Bits 0x20/0x80 are obsolete and 0x01/0x02/0x04/0x08/0x40 are command dependent. -const ATA_DR_DEV = 0x10; // Device select, slave device if set, else master device +const ATA_DR_DEV = 0x10; // Device select; slave device if set, else master device // Device Control register bits: // Bits 0x08/0x10/0x20/0x40 are reserved and bit 0x01 is always zero. -const ATA_CR_nIEN = 0x02; // Interrupt disable (not Interrupt Enable) +const ATA_CR_nIEN = 0x02; // Interrupt disable (not Interrupt ENable) const ATA_CR_SRST = 0x04; // Software reset const ATA_CR_HOB = 0x80; // 48-bit Address feature set @@ -121,6 +124,33 @@ const ATAPI_CMD_GET_EVENT_STATUS_NOTIFICATION = 0x4A; // see [MMC-3] 5.6 const ATAPI_CMD_READ_CD = 0xBE; // see [MMC-3] 5.17 //const ATAPI_CMD_LOAD_UNLOAD_MEDIUM = 0xA6; // see [MMC-3] 5.8, TODO: not implemented, might be useful +// ATAPI Status Code, see [SAM-3] 5.3.1 +const ATAPI_SC_GOOD = 0x00; +const ATAPI_SC_CHECK_CONDITION = 0x02; +const ATAPI_SC_CONDITION_MET = 0x04; +const ATAPI_SC_BUSY = 0x08; +const ATAPI_SC_INTERMEDIATE = 0x10; +const ATAPI_SC_INTERMEDIATE_CONDITION_MET = 0x14; +const ATAPI_SC_RESERVATION_CONFLICT = 0x18; +const ATAPI_SC_TASK_SET_FULL = 0x28; +const ATAPI_SC_ACA_ACTIVE = 0x30; +const ATAPI_SC_TASK_ABORTED = 0x40; + +// ATAPI 4-bit Sense Key, see [SPC-3] 4.5.6 (Table 27) +const ATAPI_SK_NO_SENSE = 0x00; +const ATAPI_SK_RECOVERED_ERROR = 0x01; +const ATAPI_SK_NOT_READY = 0x02; +const ATAPI_SK_MEDIUM_ERROR = 0x03; +const ATAPI_SK_HARDWARE_ERROR = 0x04; +const ATAPI_SK_ILLEGAL_REQUEST = 0x05; +const ATAPI_SK_UNIT_ATTENTION = 0x06; +const ATAPI_SK_DATA_PROTECT = 0x07; +const ATAPI_SK_BLANK_CHECK = 0x08; +const ATAPI_SK_ABORTED_COMMAND = 0x0B; + +// ATAPI 8-bit Additional Sense Code, see [SPC-3] 4.5.6 (Table 28) +const ATAPI_ASC_MEDIUM_NOT_PRESENT = 0x3A; + /** * @constructor * @param {CPU} cpu @@ -152,17 +182,20 @@ export function IDEController(cpu, bus, ide_config) const has_primary = ide_config && ide_config[0][0]; const has_secondary = ide_config && ide_config[1][0]; - if(has_primary || has_secondary) { + if(has_primary || has_secondary) + { const bus_master_base = 0xB400; - if(has_primary) { + if(has_primary) + { this.primary = new IDEChannel(this, 0, ide_config[0], { command_base: 0x1f0, control_base: 0x3f4, bus_master_base: bus_master_base, irq: 14 }); this.channels[0] = this.primary; } - if(has_secondary) { + if(has_secondary) + { this.secondary = new IDEChannel(this, 1, ide_config[1], { - command_base: 0x170, control_base: 0x374, bus_master_base: bus_master_base | 0x8, irq: 15 + command_base: 0x170, control_base: 0x374, bus_master_base: bus_master_base + 8, irq: 15 }); this.channels[1] = this.secondary; } @@ -322,7 +355,8 @@ function IDEChannel(controller, channel_nr, channel_config, hw_settings) return this.current_interface.device_reg & 0xFF; }); - cpu.io.register_read(this.command_base | ATA_REG_STATUS, this, function() { + cpu.io.register_read(this.command_base | ATA_REG_STATUS, this, function() + { dbg_log(this.current_interface.name + ": read Status register", LOG_DISK); dbg_log(this.current_interface.name + ": lower IRQ " + this.irq, LOG_DISK); this.cpu.device_lower_irq(this.irq); @@ -624,7 +658,7 @@ function IDEInterface(channel, cpu, buffer, is_cd, channel_nr, interface_nr, bus this.drive_connected = is_cd || buffer; /** @type {boolean} */ - this.media_changed = false; + this.media_changed = false; // TODO /** @type {number} */ this.sector_size = is_cd ? CDROM_SECTOR_SIZE : HD_SECTOR_SIZE; @@ -796,7 +830,8 @@ IDEInterface.prototype.set_disk_buffer = function(buffer) rtc.cmos_write(reg + 7, this.cylinder_count >> 8 & 0xFF); rtc.cmos_write(reg + 8, this.sectors_per_track & 0xFF); - if(this.channel.cpu) { + if(this.channel.cpu) + { this.push_irq(); } }; @@ -1339,7 +1374,7 @@ IDEInterface.prototype.atapi_handle = function() if((this.status_reg & ATA_SR_BSY) === 0 && this.data_length === 0) { this.sector_count_reg |= 1; - this.status_reg &= ~8; + this.status_reg &= ~ATA_SR_DRQ; } }; @@ -1491,7 +1526,7 @@ IDEInterface.prototype.do_atapi_dma = function() return; } - if((this.status_reg & 0x8) === 0) + if((this.status_reg & ATA_SR_DRQ) === 0) { dbg_log(this.name + ": do_atapi_dma: DRQ not set", LOG_DISK); return; From c51a0c1f5303128b22da833b48677818a51acaea Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sat, 17 May 2025 15:38:11 +0200 Subject: [PATCH 087/301] reintroduced ATA status register bit DSC In the original code, status register bit DSC (Drive Seek Complete, 0x10) is always set when completing without error, but ATA/ATAPI-6 specifies this bit to be command dependent. This PR now sets the status register exactly like it was before, just using named constants instead of magic numbers. Also replaced most of the remaining magic number assignments to the ATA status register. --- src/ide.js | 125 ++++++++++++++++++++++++++++------------------------- 1 file changed, 65 insertions(+), 60 deletions(-) diff --git a/src/ide.js b/src/ide.js index 3e518488..42fd2ce9 100644 --- a/src/ide.js +++ b/src/ide.js @@ -56,8 +56,13 @@ const ATA_ER_ABRT = 0x04; // Command aborted // Bits 0x02/0x04 are obsolete and 0x10/0x20 are command dependent. // Bit 0x02 used to be called "Index" (IDX), bit 0x04 "Corrected data" (CORR), // bit 0x10 "Drive seek complete" (DSC), and bit 0x20 "Drive write fault" (DF). +// NOTE: +// This code uses ATA_SR_DSC in the old style, this is either unneccessary +// or means that this code was originally not based on ATA/ATAPI-6 but an +// older release (ATA/ATAPI-4 or even older). const ATA_SR_ERR = 0x01; // Error const ATA_SR_DRQ = 0x08; // Data request ready +const ATA_SR_DSC = 0x10; // Drive seek complete const ATA_SR_DRDY = 0x40; // Drive ready const ATA_SR_BSY = 0x80; // Busy @@ -703,7 +708,7 @@ function IDEInterface(channel, cpu, buffer, is_cd, channel_nr, interface_nr, bus this.device_reg = 0; /** @type {number} */ - this.status_reg = ATA_SR_DRDY; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; /** @type {number} */ this.sectors_per_drq = 0x80; @@ -892,7 +897,7 @@ IDEInterface.prototype.ata_command = function(cmd) case ATA_CMD_10h: dbg_log(this.name + ": ATA calibrate drive", LOG_DISK); this.lba_mid_reg = 0; - this.status_reg = 0x50; // unknown, likely ATA_SR_DRDY + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DSC; this.push_irq(); break; @@ -903,7 +908,7 @@ IDEInterface.prototype.ata_command = function(cmd) this.lba_mid_reg = last_sector >> 8 & 0xFF; this.lba_high_reg = last_sector >> 16 & 0xFF; this.device_reg = this.device_reg & ATA_DR_DEV | last_sector >> 24 & 0x0F; - this.status_reg = ATA_SR_DRDY; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; @@ -922,7 +927,7 @@ IDEInterface.prototype.ata_command = function(cmd) this.lba_mid_reg = last_sector >> 32 & 0xFF; this.lba_high_reg = last_sector >> 40 & 0xFF; } - this.status_reg = ATA_SR_DRDY; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; @@ -966,12 +971,12 @@ IDEInterface.prototype.ata_command = function(cmd) this.error_reg = 0x00; // Slave drive failed } } - this.status_reg = ATA_SR_DRDY; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; case ATA_CMD_INITIALIZE_DEVICE_PARAMETERS: - this.status_reg = 0x50; // unknown, likely ATA_SR_DRDY + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; @@ -980,8 +985,8 @@ IDEInterface.prototype.ata_command = function(cmd) { this.data_allocate(12); this.data_end = 12; - this.sector_count_reg = 0x01; // 0x01: indicates transfer of a command packet (C/D) - this.status_reg = ATA_SR_DRDY|ATA_SR_DRQ|0x10; // 0x10: another command is ready to be serviced (SERV) + this.sector_count_reg = 0x01; // 0x01: indicates transfer of a command packet (C/D) + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; // 0x10: another command is ready to be serviced (SERV) this.push_irq(); } break; @@ -991,7 +996,7 @@ IDEInterface.prototype.ata_command = function(cmd) if(this.is_atapi) { this.create_identify_packet(); - this.status_reg = ATA_SR_DRDY|ATA_SR_DRQ; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; } else { @@ -1006,7 +1011,7 @@ IDEInterface.prototype.ata_command = function(cmd) // Logical sectors per DRQ Block in word 1 dbg_log(this.name + ": logical sectors per DRQ Block: " + h(this.sector_count_reg & 0xFF), LOG_DISK); this.sectors_per_drq = this.sector_count_reg & 0xFF; - this.status_reg = ATA_SR_DRDY; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; @@ -1023,13 +1028,12 @@ IDEInterface.prototype.ata_command = function(cmd) case ATA_CMD_READ_VERIFY_SECTORS: dbg_log(this.name + ": ATA read verify sector(s)", LOG_DISK); // TODO: check that lba_low/mid/high and sector_count regs are within the bounds of the disk's size - this.status_reg = ATA_SR_DRDY; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; case ATA_CMD_GET_MEDIA_STATUS: dbg_log(this.name + ": ATA get media status", LOG_DISK); - this.error_reg = 0; if(this.is_atapi) { if(!this.buffer) @@ -1043,31 +1047,31 @@ IDEInterface.prototype.ata_command = function(cmd) } this.error_reg |= 0x40; // WP: Write Protect } - this.status_reg = ATA_SR_DRDY; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; case ATA_CMD_STANDBY_IMMEDIATE: dbg_log(this.name + ": ATA standby immediate", LOG_DISK); - this.status_reg = ATA_SR_DRDY; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; case ATA_CMD_IDLE_IMMEDIATE: dbg_log(this.name + ": ATA idle immediate", LOG_DISK); - this.status_reg = ATA_SR_DRDY; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; case ATA_CMD_FLUSH_CACHE: dbg_log(this.name + ": ATA flush cache", LOG_DISK); - this.status_reg = ATA_SR_DRDY; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; case ATA_CMD_FLUSH_CACHE_EXT: dbg_log(this.name + ": ATA flush cache ext", LOG_DISK); - this.status_reg = ATA_SR_DRDY; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; @@ -1081,7 +1085,7 @@ IDEInterface.prototype.ata_command = function(cmd) else { this.create_identify_packet(); - this.status_reg = ATA_SR_DRDY|ATA_SR_DRQ; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; } this.push_irq(); break; @@ -1089,7 +1093,7 @@ IDEInterface.prototype.ata_command = function(cmd) case ATA_CMD_SET_FEATURES: dbg_log(this.name + ": ATA set features: " + h(this.sector_count_reg & 0xFF), LOG_DISK); // TODO: this one is important, accept/refuse requested device features - this.status_reg = 0x50; // TODO + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; @@ -1101,7 +1105,7 @@ IDEInterface.prototype.ata_command = function(cmd) case ATA_CMD_SECURITY_FREEZE_LOCK: dbg_log(this.name + ": ATA security freeze lock", LOG_DISK); - this.status_reg = ATA_SR_DRDY; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; @@ -1133,8 +1137,8 @@ IDEInterface.prototype.atapi_handle = function() this.current_atapi_command === ATAPI_CMD_READ_DISK_INFORMATION)) { dbg_log(this.name + ": CD read-related action: no buffer", LOG_DISK); - this.status_reg = 0x51; - this.error_reg = 0x21; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_ERR; + this.error_reg = 0x21; // TODO this.data_allocate(0); this.data_end = this.data_length; this.sector_count_reg = this.sector_count_reg & ~7 | 2 | 1; @@ -1148,22 +1152,23 @@ IDEInterface.prototype.atapi_handle = function() dbg_log(this.name + ": ATAPI test unit ready", LOG_DISK); this.data_allocate(0); this.data_end = this.data_length; - this.status_reg = 0x50; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; break; case ATAPI_CMD_REQUEST_SENSE: dbg_log(this.name + ": ATAPI request sense", LOG_DISK); this.data_allocate(this.data[4]); this.data_end = this.data_length; - this.status_reg = 0x58; - this.data[0] = 0x80 | 0x70; - this.data[2] = this.error_reg >> 4; - this.data[7] = 8; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; + this.data[0] = 0x80 | 0x70; // valid | SCSI error code + this.data[2] = this.error_reg >> 4; // SCSI sense key (TODO: always 0, which means no errors or warnings) + this.data[7] = 8; // SCSI additional sense length, fixed + this.data[12] = 0; // SCSI additional sense code break; case ATAPI_CMD_INQUIRY: var length = this.data[4]; - this.status_reg = 0x58; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; dbg_log(this.name + ": ATAPI inquiry: " + h(this.data[1], 2) + " length=" + length, LOG_DISK); @@ -1195,14 +1200,14 @@ IDEInterface.prototype.atapi_handle = function() dbg_log(this.name + ": ATAPI mode sense (6)", LOG_DISK); this.data_allocate(this.data[4]); this.data_end = this.data_length; - this.status_reg = 0x58; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; break; case ATAPI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: dbg_log(this.name + ": ATAPI prevent allow medium removal", LOG_DISK); this.data_allocate(0); this.data_end = this.data_length; - this.status_reg = 0x50; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; break; case ATAPI_CMD_READ_CAPACITY: @@ -1219,7 +1224,7 @@ IDEInterface.prototype.atapi_handle = function() this.sector_size & 0xFF, ])); this.data_end = this.data_length; - this.status_reg = 0x58; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; break; case ATAPI_CMD_READ: @@ -1238,7 +1243,7 @@ IDEInterface.prototype.atapi_handle = function() this.data_allocate(Math.min(8, length)); this.data_end = this.data_length; dbg_log(this.name + ": ATAPI read subchannel: length=" + length, LOG_DISK); - this.status_reg = 0x58; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; break; case ATAPI_CMD_READ_TOC_PMA_ATIP: @@ -1291,7 +1296,7 @@ IDEInterface.prototype.atapi_handle = function() dbg_assert(false, this.name + ": unimplemented format: " + format); } - this.status_reg = 0x58; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; break; case ATAPI_CMD_GET_CONFIGURATION: @@ -1305,19 +1310,19 @@ IDEInterface.prototype.atapi_handle = function() this.data[3] = length - 4 & 0xFF; this.data[6] = 0x08; this.data[10] = 3; - this.status_reg = 0x58; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; break; case ATAPI_CMD_READ_DISK_INFORMATION: dbg_log(this.name + ": ATAPI read disk information", LOG_DISK); this.data_allocate(0); this.data_end = this.data_length; - this.status_reg = 0x50; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; break; case ATAPI_CMD_READ_TRACK_INFORMATION: dbg_log(this.name + ": ATAPI read track information (unimplemented)", LOG_DISK); - this.status_reg = 0x51; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_ERR; this.data_length = 0; this.error_reg = 5 << 4; break; @@ -1331,7 +1336,7 @@ IDEInterface.prototype.atapi_handle = function() this.data_allocate(Math.min(30, length)); } this.data_end = this.data_length; - this.status_reg = 0x58; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; break; case ATAPI_CMD_MECHANISM_STATUS: @@ -1339,12 +1344,12 @@ IDEInterface.prototype.atapi_handle = function() this.data_allocate(this.data[9] | this.data[8] << 8); this.data_end = this.data_length; this.data[5] = 1; - this.status_reg = 0x58; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; break; case ATAPI_CMD_GET_EVENT_STATUS_NOTIFICATION: dbg_log(this.name + ": ATAPI get event status notification (unimplemented)", LOG_DISK); - this.status_reg = 0x51; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_ERR; this.data_length = 0; this.error_reg = 5 << 4; break; @@ -1353,11 +1358,11 @@ IDEInterface.prototype.atapi_handle = function() dbg_log(this.name + ": ATAPI read cd (unimplemented)", LOG_DISK); this.data_allocate(0); this.data_end = this.data_length; - this.status_reg = 0x50; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; break; default: - this.status_reg = 0x51; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_ERR; this.data_length = 0; this.error_reg = 5 << 4; dbg_log(this.name + ": unimplemented ATAPI command: " + h(this.data[0]), LOG_DISK); @@ -1380,7 +1385,7 @@ IDEInterface.prototype.atapi_handle = function() IDEInterface.prototype.do_write = function() { - this.status_reg = 0x50; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; dbg_assert(this.data_length <= this.data.length); var data = this.data.subarray(0, this.data_length); @@ -1441,7 +1446,7 @@ IDEInterface.prototype.atapi_read = function(cmd) } else if(byte_count === 0) { - this.status_reg = 0x50; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.data_pointer = 0; //this.push_irq(); @@ -1449,7 +1454,7 @@ IDEInterface.prototype.atapi_read = function(cmd) else { byte_count = Math.min(byte_count, this.buffer.byteLength - start); - this.status_reg = 0x50 | 0x80; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC | 0x80; // TODO: 0x80 this.report_read_start(); this.read_buffer(start, byte_count, (data) => @@ -1457,7 +1462,7 @@ IDEInterface.prototype.atapi_read = function(cmd) //setTimeout(() => { dbg_log(this.name + ": CD read: data arrived", LOG_DISK); this.data_set(data); - this.status_reg = 0x58; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; this.sector_count_reg = this.sector_count_reg & ~7 | 2; this.push_irq(); @@ -1502,14 +1507,14 @@ IDEInterface.prototype.atapi_read_dma = function(cmd) } else { - this.status_reg = 0x50 | 0x80; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC | 0x80; // TODO: 0x80 this.report_read_start(); this.read_buffer(start, byte_count, (data) => { dbg_log(this.name + ": atapi_read_dma: Data arrived"); this.report_read_end(byte_count); - this.status_reg = 0x58; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; this.sector_count_reg = this.sector_count_reg & ~7 | 2; this.data_set(data); @@ -1568,7 +1573,7 @@ IDEInterface.prototype.do_atapi_dma = function() dbg_log(this.name + ": end offset=" + offset, LOG_DISK); - this.status_reg = 0x50; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.channel.dma_status &= ~1; this.sector_count_reg = this.sector_count_reg & ~7 | 3; this.push_irq(); @@ -1630,13 +1635,13 @@ IDEInterface.prototype.read_end = function() { if(this.data_end === this.data_length) { - this.status_reg = 0x50; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.sector_count_reg = this.sector_count_reg & ~7 | 3; this.push_irq(); } else { - this.status_reg = 0x58; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; this.sector_count_reg = this.sector_count_reg & ~7 | 2; this.push_irq(); var byte_count = this.lba_high_reg << 8 & 0xFF00 | this.lba_mid_reg & 0xFF; @@ -1659,7 +1664,7 @@ IDEInterface.prototype.read_end = function() this.error_reg = 0; if(this.data_pointer >= this.data_length) { - this.status_reg = 0x50; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; } else { @@ -1676,7 +1681,7 @@ IDEInterface.prototype.read_end = function() } this.ata_advance(this.current_command, sector_count); this.data_end += 512 * sector_count; - this.status_reg = 0x58; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; this.push_irq(); } } @@ -1762,7 +1767,7 @@ IDEInterface.prototype.write_end = function() // XXX: Should advance here, but do_write does all the advancing //this.ata_advance(this.current_command, 1); - this.status_reg = 0x58; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; this.data_end += 512; this.push_irq(); } @@ -1834,7 +1839,7 @@ IDEInterface.prototype.ata_read_sectors = function(cmd) } else { - this.status_reg = 0x80 | 0x40; + this.status_reg = 0x80 | 0x40; // TODO this.report_read_start(); this.read_buffer(start, byte_count, (data) => @@ -1843,7 +1848,7 @@ IDEInterface.prototype.ata_read_sectors = function(cmd) dbg_log(this.name + ": ata_read: Data arrived", LOG_DISK); this.data_set(data); - this.status_reg = 0x58; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; this.data_end = is_single ? 512 : Math.min(byte_count, this.sectors_per_drq * 512); this.ata_advance(cmd, is_single ? 1 : Math.min(count, this.sectors_per_track)); @@ -1876,7 +1881,7 @@ IDEInterface.prototype.ata_read_sectors_dma = function(cmd) return; } - this.status_reg = 0x58; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; this.channel.dma_status |= 1; }; @@ -1929,7 +1934,7 @@ IDEInterface.prototype.do_ata_read_sectors_dma = function() dbg_assert(offset === byte_count); this.ata_advance(this.current_command, count); - this.status_reg = 0x50; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.channel.dma_status &= ~1; this.current_command = -1; @@ -1964,7 +1969,7 @@ IDEInterface.prototype.ata_write_sectors = function(cmd) } else { - this.status_reg = 0x58; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; this.data_allocate_noclear(byte_count); this.data_end = is_single ? 512 : Math.min(byte_count, this.sectors_per_drq * 512); this.write_dest = start; @@ -1993,7 +1998,7 @@ IDEInterface.prototype.ata_write_sectors_dma = function(cmd) return; } - this.status_reg = 0x58; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; this.channel.dma_status |= 1; }; @@ -2049,7 +2054,7 @@ IDEInterface.prototype.do_ata_write_sectors_dma = function() { dbg_log(this.name + ": DMA write completed", LOG_DISK); this.ata_advance(this.current_command, count); - this.status_reg = 0x50; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); this.channel.dma_status &= ~1; this.current_command = -1; From 20acdacfcc332b624371ee582bf53cc2c0a06658 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sat, 17 May 2025 17:58:48 +0200 Subject: [PATCH 088/301] downgraded ATAPI CD-ROM commands from SCSI-3 to SCSI-2 In the context of ATAPI there's only a single linked document in ide.js: https://www.t10.org/ftp/x3t9.2/document.87/87-106r0.txt. This link points to "PROPOSAL FOR CD-ROM IN SCSI-2" from 1987 (!) and its content matches the current code in v86, which the SCSI-3 documents did not at all (only the command codes matched, but otherwise SCSI-3 is very different from SCSI-2). --- src/ide.js | 108 +++++++++++++++++------------------------------------ 1 file changed, 34 insertions(+), 74 deletions(-) diff --git a/src/ide.js b/src/ide.js index 42fd2ce9..ca267346 100644 --- a/src/ide.js +++ b/src/ide.js @@ -7,24 +7,16 @@ import { CMOS_BIOS_DISKTRANSFLAG, CMOS_DISK_DATA, CMOS_DISK_DRIVE1_CYL, CMOS_DIS import { CPU } from "./cpu.js"; import { BusConnector } from "./bus.js"; -// ATA/ATAPI-6 IDE Controller with support for: -// - up to 4 IDE devices (2 IDE channels with 2 IDE interfaces per channel) -// - ATA hard-disk devices -// - ATAPI CD-ROM devices +// ATA/ATAPI-6 IDE Controller // // References // - [ATA-6] // AT Attachment with Packet Interface - 6 (ATA/ATAPI-6) (Rev. 3a; Dec. 14, 2001) // https://technion-csl.github.io/ose/readings/hardware/ATA-d1410r3a.pdf -// - [SAM-3] -// SCSI Architecture Model - 3 (SAM-3) (Rev. 14; Sep. 21, 2004) -// https://dn790004.ca.archive.org/0/items/SCSISpecificationDocumentsSCSIDocuments/SCSI%20Architecture%20Model/SCSI%20Architecture%20Model%203%20rev%2014.pdf -// - [SPC-3] -// SCSI Primary Commands - 3 (SPC-3) (Jul 20, 2008) -// https://www.t10.org/ftp/t10/document.08/08-309r0.pdf -// - [MMC-3] -// SCSI Multimedia Commands - 3 (MMC-3) (Rev. 10g; Nov. 12, 2001) -// https://ia902808.us.archive.org/33/items/mmc3r10g/mmc3r10g.pdf +// - [CD-SCSI-2] +// PROPOSAL FOR CD-ROM IN SCSI-2 (X3T9.2/87) (Rev. 0, Jun. 30, 1987) +// https://www.t10.org/ftp/x3t9.2/document.87/87-106r0.txt +// https://www.t10.org/ftp/x3t9.2/document.87/87-106r1.txt (errata to r0) const CDROM_SECTOR_SIZE = 2048; const HD_SECTOR_SIZE = 512; @@ -110,51 +102,24 @@ const ATA_CMD_MEDIA_LOCK = 0xDE; // see [ATA-6] 8.20 const ATA_CMD_SECURITY_FREEZE_LOCK = 0xF5; // see [ATA-6] 8.41 const ATA_CMD_SET_MAX = 0xF9; // see [ATA-6] 8.47 -// ATAPI commands -const ATAPI_CMD_TEST_UNIT_READY = 0x00; // see [SPC-3] 6.33 -const ATAPI_CMD_REQUEST_SENSE = 0x03; // see [SPC-3] 6.27 -const ATAPI_CMD_INQUIRY = 0x12; // see [SPC-3] 6.4 -const ATAPI_CMD_MODE_SENSE_6 = 0x1A; // see [SPC-3] 6.9 -const ATAPI_CMD_MODE_SENSE_10 = 0x5A; // see [SPC-3] 6.10 -const ATAPI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1E; // see [SPC-3] 6.13 -const ATAPI_CMD_READ_CAPACITY = 0x25; // see [MMC-3] 5.16 -const ATAPI_CMD_READ = 0x28; // see [MMC-3] 5.14 -const ATAPI_CMD_READ_SUBCHANNEL = 0x42; // see [MMC-3] 5.22 -const ATAPI_CMD_READ_TOC_PMA_ATIP = 0x43; // see [MMC-3] 5.23 -const ATAPI_CMD_GET_CONFIGURATION = 0x46; // see [MMC-3] 5.5 -const ATAPI_CMD_READ_DISK_INFORMATION = 0x51; // see [MMC-3] 5.19 -const ATAPI_CMD_READ_TRACK_INFORMATION = 0x52; // see [MMC-3] 5.24 -const ATAPI_CMD_MECHANISM_STATUS = 0xBD; // see [MMC-3] 5.9 -const ATAPI_CMD_GET_EVENT_STATUS_NOTIFICATION = 0x4A; // see [MMC-3] 5.6 -const ATAPI_CMD_READ_CD = 0xBE; // see [MMC-3] 5.17 -//const ATAPI_CMD_LOAD_UNLOAD_MEDIUM = 0xA6; // see [MMC-3] 5.8, TODO: not implemented, might be useful - -// ATAPI Status Code, see [SAM-3] 5.3.1 -const ATAPI_SC_GOOD = 0x00; -const ATAPI_SC_CHECK_CONDITION = 0x02; -const ATAPI_SC_CONDITION_MET = 0x04; -const ATAPI_SC_BUSY = 0x08; -const ATAPI_SC_INTERMEDIATE = 0x10; -const ATAPI_SC_INTERMEDIATE_CONDITION_MET = 0x14; -const ATAPI_SC_RESERVATION_CONFLICT = 0x18; -const ATAPI_SC_TASK_SET_FULL = 0x28; -const ATAPI_SC_ACA_ACTIVE = 0x30; -const ATAPI_SC_TASK_ABORTED = 0x40; - -// ATAPI 4-bit Sense Key, see [SPC-3] 4.5.6 (Table 27) -const ATAPI_SK_NO_SENSE = 0x00; -const ATAPI_SK_RECOVERED_ERROR = 0x01; -const ATAPI_SK_NOT_READY = 0x02; -const ATAPI_SK_MEDIUM_ERROR = 0x03; -const ATAPI_SK_HARDWARE_ERROR = 0x04; -const ATAPI_SK_ILLEGAL_REQUEST = 0x05; -const ATAPI_SK_UNIT_ATTENTION = 0x06; -const ATAPI_SK_DATA_PROTECT = 0x07; -const ATAPI_SK_BLANK_CHECK = 0x08; -const ATAPI_SK_ABORTED_COMMAND = 0x0B; - -// ATAPI 8-bit Additional Sense Code, see [SPC-3] 4.5.6 (Table 28) -const ATAPI_ASC_MEDIUM_NOT_PRESENT = 0x3A; +// ATAPI (SCSI-2) commands, see [CD-SCSI-2] +const ATAPI_CMD_TEST_UNIT_READY = 0x00; +const ATAPI_CMD_REQUEST_SENSE = 0x03; +const ATAPI_CMD_INQUIRY = 0x12; +const ATAPI_CMD_MODE_SENSE_6 = 0x1A; +const ATAPI_CMD_MODE_SENSE_10 = 0x5A; +const ATAPI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1E; +const ATAPI_CMD_READ_CAPACITY = 0x25; +const ATAPI_CMD_READ = 0x28; +const ATAPI_CMD_READ_SUBCHANNEL = 0x42; +const ATAPI_CMD_READ_TOC_PMA_ATIP = 0x43; +const ATAPI_CMD_GET_CONFIGURATION = 0x46; +const ATAPI_CMD_READ_DISK_INFORMATION = 0x51; +const ATAPI_CMD_READ_TRACK_INFORMATION = 0x52; +const ATAPI_CMD_MECHANISM_STATUS = 0xBD; +const ATAPI_CMD_GET_EVENT_STATUS_NOTIFICATION = 0x4A; +const ATAPI_CMD_READ_CD = 0xBE; +//const ATAPI_CMD_LOAD_UNLOAD_MEDIUM = 0xA6; // TODO: not implemented, might be useful /** * @constructor @@ -1162,35 +1127,30 @@ IDEInterface.prototype.atapi_handle = function() this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; this.data[0] = 0x80 | 0x70; // valid | SCSI error code this.data[2] = this.error_reg >> 4; // SCSI sense key (TODO: always 0, which means no errors or warnings) - this.data[7] = 8; // SCSI additional sense length, fixed + this.data[7] = 8; // SCSI additional sense length (fixed 8 for this error code 0x70) this.data[12] = 0; // SCSI additional sense code + this.data[13] = 0; // SCSI additional sense code qualifier break; case ATAPI_CMD_INQUIRY: var length = this.data[4]; this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; - dbg_log(this.name + ": ATAPI inquiry: " + h(this.data[1], 2) + " length=" + length, LOG_DISK); - - // http://www.t10.org/ftp/x3t9.2/document.87/87-106r0.txt - //this.data_allocate(36); + // for data layout see [CD-SCSI-2] "INQUIRY Command" this.data.set([ - 0x05, 0x80, 0x01, 0x31, - // additional length - 31, - 0, 0, 0, - - // 8 + // 0: Device-type, Removable, ANSI-Version, Response Format + this.drive_connected ? 0x05 : 0x7F, 0x80, 0x01, 0x31, // TODO: drive_connected likely not needed + // 4: Additional length, Reserved, Reserved, Reserved + 0x31, 0, 0, 0, + // 8: Vendor Identification "SONY " 0x53, 0x4F, 0x4E, 0x59, 0x20, 0x20, 0x20, 0x20, - - // 16 + // 16: Product Identification "CD-ROM CDU-1000 " 0x43, 0x44, 0x2D, 0x52, 0x4F, 0x4D, 0x20, 0x43, 0x44, 0x55, 0x2D, 0x31, 0x30, 0x30, 0x30, 0x20, - - // 32 + // 32: Product Revision Level "1.1a" 0x31, 0x2E, 0x31, 0x61, ]); this.data_end = this.data_length = Math.min(36, length); @@ -1240,9 +1200,9 @@ IDEInterface.prototype.atapi_handle = function() case ATAPI_CMD_READ_SUBCHANNEL: var length = this.data[8]; + dbg_log(this.name + ": ATAPI read subchannel: length=" + length, LOG_DISK); this.data_allocate(Math.min(8, length)); this.data_end = this.data_length; - dbg_log(this.name + ": ATAPI read subchannel: length=" + length, LOG_DISK); this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; break; From 40dab74fa5957c358c847e9bf8651e720f3f0614 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sat, 17 May 2025 19:20:34 +0200 Subject: [PATCH 089/301] added references to MMC-2 (SCSI-2 Multimedia Commands) --- src/ide.js | 87 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/src/ide.js b/src/ide.js index ca267346..67f950fe 100644 --- a/src/ide.js +++ b/src/ide.js @@ -17,6 +17,9 @@ import { BusConnector } from "./bus.js"; // PROPOSAL FOR CD-ROM IN SCSI-2 (X3T9.2/87) (Rev. 0, Jun. 30, 1987) // https://www.t10.org/ftp/x3t9.2/document.87/87-106r0.txt // https://www.t10.org/ftp/x3t9.2/document.87/87-106r1.txt (errata to r0) +// - [MMC-2] +// Packet Commands for C/DVD Devices (1997) +// https://www.t10.org/ftp/t10/document.97/97-108r0.pdf const CDROM_SECTOR_SIZE = 2048; const HD_SECTOR_SIZE = 512; @@ -70,56 +73,56 @@ const ATA_CR_HOB = 0x80; // 48-bit Address feature set // ATA commands const ATA_CMD_DEVICE_RESET = 0x08; // see [ATA-6] 8.10 and 9.11 -const ATA_CMD_10h = 0x10; // command obsolete, see [ATA-6] Table E.2 +const ATA_CMD_EXECUTE_DEVICE_DIAGNOSTIC = 0x90; // see [ATA-6] 8.11 +const ATA_CMD_FLUSH_CACHE = 0xE7; // see [ATA-6] 8.12 +const ATA_CMD_FLUSH_CACHE_EXT = 0xEA; // see [ATA-6] 8.13 +const ATA_CMD_GET_MEDIA_STATUS = 0xDA; // see [ATA-6] 8.14 +const ATA_CMD_IDENTIFY_DEVICE = 0xEC; // see [ATA-6] 8.15 +const ATA_CMD_IDENTIFY_PACKET_DEVICE = 0xA1; // see [ATA-6] 8.16 +const ATA_CMD_IDLE_IMMEDIATE = 0xE1; // see [ATA-6] 8.18 +const ATA_CMD_INITIALIZE_DEVICE_PARAMETERS = 0x91; // not mentioned in [ATA-6] +const ATA_CMD_MEDIA_LOCK = 0xDE; // see [ATA-6] 8.20 +const ATA_CMD_PACKET = 0xA0; // see [ATA-6] 8.23 +const ATA_CMD_READ_DMA = 0xC8; // see [ATA-6] 8.26 +const ATA_CMD_READ_DMA_EXT = 0x25; // see [ATA-6] 8.25 +const ATA_CMD_READ_MULTIPLE = 0x29; // see [ATA-6] 8.30 +const ATA_CMD_READ_MULTIPLE_EXT = 0xC4; // see [ATA-6] 8.31 const ATA_CMD_READ_NATIVE_MAX_ADDRESS = 0xF8; // see [ATA-6] 8.32 const ATA_CMD_READ_NATIVE_MAX_ADDRESS_EXT = 0x27; // see [ATA-6] 8.33 const ATA_CMD_READ_SECTORS = 0x20; // see [ATA-6] 8.34 const ATA_CMD_READ_SECTORS_EXT = 0x24; // see [ATA-6] 8.35 -const ATA_CMD_READ_MULTIPLE = 0x29; // see [ATA-6] 8.30 -const ATA_CMD_READ_MULTIPLE_EXT = 0xC4; // see [ATA-6] 8.31 -const ATA_CMD_WRITE_SECTORS = 0x30; // see [ATA-6] 8.62 -const ATA_CMD_WRITE_SECTORS_EXT = 0x34; // see [ATA-6] 8.63 -const ATA_CMD_WRITE_MULTIPLE = 0x39; // see [ATA-6] 8.60 -const ATA_CMD_WRITE_MULTIPLE_EXT = 0xC5; // see [ATA-6] 8.61 -const ATA_CMD_EXECUTE_DEVICE_DIAGNOSTIC = 0x90; // see [ATA-6] 8.11 -const ATA_CMD_INITIALIZE_DEVICE_PARAMETERS = 0x91; // not mentioned in [ATA-6] -const ATA_CMD_PACKET = 0xA0; // see [ATA-6] 8.23 -const ATA_CMD_IDENTIFY_PACKET_DEVICE = 0xA1; // see [ATA-6] 8.16 +const ATA_CMD_READ_VERIFY_SECTORS = 0x40; // see [ATA-6] 8.36 +const ATA_CMD_SECURITY_FREEZE_LOCK = 0xF5; // see [ATA-6] 8.41 +const ATA_CMD_SET_FEATURES = 0xEF; // see [ATA-6] 8.46 +const ATA_CMD_SET_MAX = 0xF9; // see [ATA-6] 8.47 const ATA_CMD_SET_MULTIPLE_MODE = 0xC6; // see [ATA-6] 8.49 -const ATA_CMD_READ_DMA = 0xC8; // see [ATA-6] 8.26 -const ATA_CMD_READ_DMA_EXT = 0x25; // see [ATA-6] 8.25 +const ATA_CMD_STANDBY_IMMEDIATE = 0xE0; // see [ATA-6] 8.53 const ATA_CMD_WRITE_DMA = 0xCA; // see [ATA-6] 8.55 const ATA_CMD_WRITE_DMA_EXT = 0x35; // see [ATA-6] 8.56 -const ATA_CMD_READ_VERIFY_SECTORS = 0x40; // see [ATA-6] 8.36 -const ATA_CMD_GET_MEDIA_STATUS = 0xDA; // see [ATA-6] 8.14 -const ATA_CMD_STANDBY_IMMEDIATE = 0xE0; // see [ATA-6] 8.53 -const ATA_CMD_IDLE_IMMEDIATE = 0xE1; // see [ATA-6] 8.18 -const ATA_CMD_FLUSH_CACHE = 0xE7; // see [ATA-6] 8.12 -const ATA_CMD_FLUSH_CACHE_EXT = 0xEA; // see [ATA-6] 8.13 -const ATA_CMD_IDENTIFY_DEVICE = 0xEC; // see [ATA-6] 8.15 -const ATA_CMD_SET_FEATURES = 0xEF; // see [ATA-6] 8.46 -const ATA_CMD_MEDIA_LOCK = 0xDE; // see [ATA-6] 8.20 -const ATA_CMD_SECURITY_FREEZE_LOCK = 0xF5; // see [ATA-6] 8.41 -const ATA_CMD_SET_MAX = 0xF9; // see [ATA-6] 8.47 +const ATA_CMD_WRITE_MULTIPLE = 0x39; // see [ATA-6] 8.60 +const ATA_CMD_WRITE_MULTIPLE_EXT = 0xC5; // see [ATA-6] 8.61 +const ATA_CMD_WRITE_SECTORS = 0x30; // see [ATA-6] 8.62 +const ATA_CMD_WRITE_SECTORS_EXT = 0x34; // see [ATA-6] 8.63 +const ATA_CMD_10h = 0x10; // command obsolete/unknown, see [ATA-6] Table E.2 -// ATAPI (SCSI-2) commands, see [CD-SCSI-2] -const ATAPI_CMD_TEST_UNIT_READY = 0x00; -const ATAPI_CMD_REQUEST_SENSE = 0x03; -const ATAPI_CMD_INQUIRY = 0x12; -const ATAPI_CMD_MODE_SENSE_6 = 0x1A; -const ATAPI_CMD_MODE_SENSE_10 = 0x5A; -const ATAPI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1E; -const ATAPI_CMD_READ_CAPACITY = 0x25; -const ATAPI_CMD_READ = 0x28; -const ATAPI_CMD_READ_SUBCHANNEL = 0x42; -const ATAPI_CMD_READ_TOC_PMA_ATIP = 0x43; -const ATAPI_CMD_GET_CONFIGURATION = 0x46; -const ATAPI_CMD_READ_DISK_INFORMATION = 0x51; -const ATAPI_CMD_READ_TRACK_INFORMATION = 0x52; -const ATAPI_CMD_MECHANISM_STATUS = 0xBD; -const ATAPI_CMD_GET_EVENT_STATUS_NOTIFICATION = 0x4A; -const ATAPI_CMD_READ_CD = 0xBE; -//const ATAPI_CMD_LOAD_UNLOAD_MEDIUM = 0xA6; // TODO: not implemented, might be useful +// ATAPI (SCSI-2/MMC-2) commands +const ATAPI_CMD_GET_CONFIGURATION = 0x46; // see [CD-SCSI-2] +const ATAPI_CMD_GET_EVENT_STATUS_NOTIFICATION = 0x4A; // see [MMC-2] 9.1.2 +const ATAPI_CMD_INQUIRY = 0x12; // see [MMC-2] 9.1.3 +//const ATAPI_CMD_LOAD_UNLOAD_MEDIUM = 0xA6; // see [MMC-2] 9.1.4 -- TODO: not implemented, might be useful +const ATAPI_CMD_MECHANISM_STATUS = 0xBD; // see [MMC-2] 9.1.5 +const ATAPI_CMD_MODE_SENSE_6 = 0x1A; // see [CD-SCSI-2] +const ATAPI_CMD_MODE_SENSE_10 = 0x5A; // see [MMC-2] 9.1.7 +const ATAPI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1E; // see [MMC-2] 9.1.9 +const ATAPI_CMD_READ = 0x28; // see [CD-SCSI-2] +const ATAPI_CMD_READ_CAPACITY = 0x25; // see [MMC-2] 9.1.12 +const ATAPI_CMD_READ_CD = 0xBE; // see [CD-SCSI-2] +const ATAPI_CMD_READ_DISK_INFORMATION = 0x51; // see [CD-SCSI-2] +const ATAPI_CMD_READ_SUBCHANNEL = 0x42; // see [CD-SCSI-2] +const ATAPI_CMD_READ_TOC_PMA_ATIP = 0x43; // see [CD-SCSI-2] +const ATAPI_CMD_READ_TRACK_INFORMATION = 0x52; // see [CD-SCSI-2] +const ATAPI_CMD_REQUEST_SENSE = 0x03; // see [MMC-2] 9.1.18 +const ATAPI_CMD_TEST_UNIT_READY = 0x00; // see [MMC-2] 9.1.20 /** * @constructor From 2add432dbf3ba82790788946b025e00d2a0ddddc Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sun, 18 May 2025 14:58:08 +0200 Subject: [PATCH 090/301] added some comments to IDE CMOS setup --- src/ide.js | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/ide.js b/src/ide.js index 67f950fe..ac1b5773 100644 --- a/src/ide.js +++ b/src/ide.js @@ -614,7 +614,7 @@ function IDEInterface(channel, cpu, buffer, is_cd, channel_nr, interface_nr, bus * @const * @type {number} */ - this.nr = channel_nr; + this.channel_nr = channel_nr; /** * @const @@ -786,22 +786,32 @@ IDEInterface.prototype.set_disk_buffer = function(buffer) this.cylinder_count = Math.floor(this.cylinder_count); } - var rtc = this.cpu.devices.rtc; + // for CMOS see: + // https://github.com/copy/v86/blob/master/src/rtc.js + // https://github.com/coreboot/seabios/blob/master/src/hw/rtc.h + // https://web.archive.org/web/20240119203005/http://www.bioscentral.com/misc/cmosmap.htm + const rtc = this.cpu.devices.rtc; + // master - rtc.cmos_write(CMOS_BIOS_DISKTRANSFLAG, - rtc.cmos_read(CMOS_BIOS_DISKTRANSFLAG) | 1 << this.nr * 4); + rtc.cmos_write(CMOS_BIOS_DISKTRANSFLAG, // TODO: what is this doing, setting LBA translation? + rtc.cmos_read(CMOS_BIOS_DISKTRANSFLAG) | 1 << this.channel_nr * 4); + + // set hard disk type (CMOS_DISK_DATA = 0x12) of C: to 0b1111, keep type of D: + // bits 0-3: hard disk type of D: + // bits 4-7: hard disk type of C: + // TODO: should this not also set CMOS_DISK_DRIVE1_TYPE to a hard disk type (see SeaBIOS rtc.h)? rtc.cmos_write(CMOS_DISK_DATA, rtc.cmos_read(CMOS_DISK_DATA) & 0x0F | 0xF0); - var reg = this.nr === 0 ? CMOS_DISK_DRIVE1_CYL : CMOS_DISK_DRIVE2_CYL; - rtc.cmos_write(reg + 0, this.cylinder_count & 0xFF); - rtc.cmos_write(reg + 1, this.cylinder_count >> 8 & 0xFF); - rtc.cmos_write(reg + 2, this.head_count & 0xFF); - rtc.cmos_write(reg + 3, 0xFF); - rtc.cmos_write(reg + 4, 0xFF); - rtc.cmos_write(reg + 5, 0xC8); - rtc.cmos_write(reg + 6, this.cylinder_count & 0xFF); - rtc.cmos_write(reg + 7, this.cylinder_count >> 8 & 0xFF); - rtc.cmos_write(reg + 8, this.sectors_per_track & 0xFF); + const drive_reg = this.channel_nr === 0 ? CMOS_DISK_DRIVE1_CYL : CMOS_DISK_DRIVE2_CYL; // 0x1B : 0x24 (drive C: or D:) + rtc.cmos_write(drive_reg + 0, this.cylinder_count & 0xFF); // number of cylinders least significant byte + rtc.cmos_write(drive_reg + 1, this.cylinder_count >> 8 & 0xFF); // number of cylinders most significant byte + rtc.cmos_write(drive_reg + 2, this.head_count & 0xFF); // number of heads + rtc.cmos_write(drive_reg + 3, 0xFF); // write precomp cylinder least significant byte + rtc.cmos_write(drive_reg + 4, 0xFF); // write precomp cylinder most significant byte + rtc.cmos_write(drive_reg + 5, 0xC8); // control byte + rtc.cmos_write(drive_reg + 6, this.cylinder_count & 0xFF); // landing zone least significant byte + rtc.cmos_write(drive_reg + 7, this.cylinder_count >> 8 & 0xFF); // landing zone most significant byte + rtc.cmos_write(drive_reg + 8, this.sectors_per_track & 0xFF); // number of sectors if(this.channel.cpu) { @@ -2214,13 +2224,13 @@ IDEInterface.prototype.report_read_start = function() IDEInterface.prototype.report_read_end = function(byte_count) { const sector_count = byte_count / this.sector_size | 0; - this.bus.send("ide-read-end", [this.nr, byte_count, sector_count]); + this.bus.send("ide-read-end", [this.channel_nr, byte_count, sector_count]); }; IDEInterface.prototype.report_write = function(byte_count) { const sector_count = byte_count / this.sector_size | 0; - this.bus.send("ide-write-end", [this.nr, byte_count, sector_count]); + this.bus.send("ide-write-end", [this.channel_nr, byte_count, sector_count]); }; IDEInterface.prototype.read_buffer = function(start, length, callback) From 3f121c831f2131117b7fabf440c15e48a23fb171 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sun, 18 May 2025 18:11:00 +0200 Subject: [PATCH 091/301] fixed SCSI Sense responses (CHECK CONDITION) The SCSI Status field ("Status Key" and "Additional Status Code" aka. ASC) was incorrectly passed from the SCSI layer to the ATA register layer, and ATAPI command "TEST UNIT READY" was not implemented correctly. This led to Linux not properly detecting when no CD was inserted into the CD-ROM device, which led to error output in "hwinfo" like (it attempted to read a disk that's no inserted): > root@debian-iscsi:~# hwinfo --cdrom > > block.5.1: /dev/sr0 cache[ 78.371349] I/O error, dev sr0, sector 64 op 0x0:(READ) flags 0x80700 phys_seg 1 prio class 2 > [ 78.412349] I/O error, dev sr0, sector 64 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 2 > [ 78.412349] Buffer I/O error on dev sr0, logical block 16, async page read > [ 78.448365] I/O error, dev sr0, sector 68 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 2 > [ 78.448369] Buffer I/O error on dev sr0, logical block 17, async page read 08: SCSI 100.0: 10602 CD-ROM This commit fixes that problem by now properly returning SCSI Sense and Additional Sense responses to the ATAPI host. --- src/ide.js | 97 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 70 insertions(+), 27 deletions(-) diff --git a/src/ide.js b/src/ide.js index ac1b5773..047710e6 100644 --- a/src/ide.js +++ b/src/ide.js @@ -124,6 +124,22 @@ const ATAPI_CMD_READ_TRACK_INFORMATION = 0x52; // see [CD-SCSI-2] const ATAPI_CMD_REQUEST_SENSE = 0x03; // see [MMC-2] 9.1.18 const ATAPI_CMD_TEST_UNIT_READY = 0x00; // see [MMC-2] 9.1.20 +// ATAPI 4-bit Sense Keys, see [MMC-2] 9.1.18.3, Table 123 +const ATAPI_SK_NO_SENSE = 0; +const ATAPI_SK_RECOVERED_ERROR = 1; +const ATAPI_SK_NOT_READY = 2; +const ATAPI_SK_MEDIUM_ERROR = 3; +const ATAPI_SK_HARDWARE_ERROR = 4; +const ATAPI_SK_ILLEGAL_REQUEST = 5; +const ATAPI_SK_UNIT_ATTENTION = 6; +const ATAPI_SK_DATA_PROTECT = 7; +const ATAPI_SK_BLANK_CHECK = 8; +const ATAPI_SK_ABORTED_COMMAND = 11; + +// ATAPI 8-bit Additional Sense Codes, see [MMC-2] 9.1.18.3, Table 124 +const ATAPI_ASC_INV_FIELD_IN_CMD_PACKET = 0x24; +const ATAPI_ASC_MEDIUM_NOT_PRESENT = 0x3A; + /** * @constructor * @param {CPU} cpu @@ -630,9 +646,6 @@ function IDEInterface(channel, cpu, buffer, is_cd, channel_nr, interface_nr, bus /** @type {boolean} */ this.drive_connected = is_cd || buffer; - /** @type {boolean} */ - this.media_changed = false; // TODO - /** @type {number} */ this.sector_size = is_cd ? CDROM_SECTOR_SIZE : HD_SECTOR_SIZE; @@ -700,9 +713,6 @@ function IDEInterface(channel, cpu, buffer, is_cd, channel_nr, interface_nr, bus /** @type {number} */ this.current_command = -1; - /** @type {number} */ - this.current_atapi_command = -1; - /** @type {number} */ this.write_dest = 0; @@ -711,6 +721,19 @@ function IDEInterface(channel, cpu, buffer, is_cd, channel_nr, interface_nr, bus this.in_progress_io_ids = new Set(); this.cancelled_io_ids = new Set(); + // ATAPI-only + /** @type {number} */ + this.current_atapi_command = -1; + + /** @type {number} */ + this.atapi_sense_key = 0; + + /** @type {number} */ + this.atapi_add_sense = 0; + + /** @type {boolean} */ + this.media_changed = false; // TODO + // caller must call this.init_interface() to complete object initialization this.inital_buffer = buffer; } @@ -1105,9 +1128,15 @@ IDEInterface.prototype.ata_command = function(cmd) IDEInterface.prototype.atapi_handle = function() { dbg_log(this.name + ": ATAPI Command: " + h(this.data[0]), LOG_DISK); - this.data_pointer = 0; this.current_atapi_command = this.data[0]; + + if(this.current_atapi_command !== ATAPI_CMD_REQUEST_SENSE) + { + this.atapi_sense_key = 0; + this.atapi_add_sense = 0; + } + if(!this.buffer && (this.current_atapi_command === ATAPI_CMD_READ_CAPACITY || this.current_atapi_command === ATAPI_CMD_READ || this.current_atapi_command === ATAPI_CMD_READ_SUBCHANNEL || @@ -1115,11 +1144,7 @@ IDEInterface.prototype.atapi_handle = function() this.current_atapi_command === ATAPI_CMD_READ_DISK_INFORMATION)) { dbg_log(this.name + ": CD read-related action: no buffer", LOG_DISK); - this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_ERR; - this.error_reg = 0x21; // TODO - this.data_allocate(0); - this.data_end = this.data_length; - this.sector_count_reg = this.sector_count_reg & ~7 | 2 | 1; + this.atapi_check_condition_response(ATAPI_SK_NOT_READY, ATAPI_ASC_MEDIUM_NOT_PRESENT); this.push_irq(); return; } @@ -1128,9 +1153,16 @@ IDEInterface.prototype.atapi_handle = function() { case ATAPI_CMD_TEST_UNIT_READY: dbg_log(this.name + ": ATAPI test unit ready", LOG_DISK); - this.data_allocate(0); - this.data_end = this.data_length; - this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; + if(this.buffer) + { + this.data_allocate(0); + this.data_end = this.data_length; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; + } + else + { + this.atapi_check_condition_response(ATAPI_SK_NOT_READY, ATAPI_ASC_MEDIUM_NOT_PRESENT); + } break; case ATAPI_CMD_REQUEST_SENSE: @@ -1139,10 +1171,11 @@ IDEInterface.prototype.atapi_handle = function() this.data_end = this.data_length; this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; this.data[0] = 0x80 | 0x70; // valid | SCSI error code - this.data[2] = this.error_reg >> 4; // SCSI sense key (TODO: always 0, which means no errors or warnings) + this.data[2] = this.atapi_sense_key; // SCSI sense key this.data[7] = 8; // SCSI additional sense length (fixed 8 for this error code 0x70) - this.data[12] = 0; // SCSI additional sense code - this.data[13] = 0; // SCSI additional sense code qualifier + this.data[12] = this.atapi_add_sense; // SCSI additional sense code + this.atapi_sense_key = 0; + this.atapi_add_sense = 0; break; case ATAPI_CMD_INQUIRY: @@ -1295,9 +1328,7 @@ IDEInterface.prototype.atapi_handle = function() case ATAPI_CMD_READ_TRACK_INFORMATION: dbg_log(this.name + ": ATAPI read track information (unimplemented)", LOG_DISK); - this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_ERR; - this.data_length = 0; - this.error_reg = 5 << 4; + this.atapi_check_condition_response(ATAPI_SK_ILLEGAL_REQUEST, ATAPI_ASC_INV_FIELD_IN_CMD_PACKET); break; case ATAPI_CMD_MODE_SENSE_10: @@ -1322,9 +1353,7 @@ IDEInterface.prototype.atapi_handle = function() case ATAPI_CMD_GET_EVENT_STATUS_NOTIFICATION: dbg_log(this.name + ": ATAPI get event status notification (unimplemented)", LOG_DISK); - this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_ERR; - this.data_length = 0; - this.error_reg = 5 << 4; + this.atapi_check_condition_response(ATAPI_SK_ILLEGAL_REQUEST, ATAPI_ASC_INV_FIELD_IN_CMD_PACKET); break; case ATAPI_CMD_READ_CD: @@ -1335,9 +1364,7 @@ IDEInterface.prototype.atapi_handle = function() break; default: - this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_ERR; - this.data_length = 0; - this.error_reg = 5 << 4; + this.atapi_check_condition_response(ATAPI_SK_ILLEGAL_REQUEST, ATAPI_ASC_INV_FIELD_IN_CMD_PACKET); dbg_log(this.name + ": unimplemented ATAPI command: " + h(this.data[0]), LOG_DISK); dbg_assert(false); } @@ -1356,6 +1383,22 @@ IDEInterface.prototype.atapi_handle = function() } }; +IDEInterface.prototype.atapi_check_condition_response = function(sense_key, additional_sense) +{ + // Setup ATA registers to CHECK CONDITION state. + // The sense state (sense_key and additional_sense) must be requested + // by the host using ATAPI_CMD_REQUEST_SENSE immediately following a + // CHECK CONDITION response or else it will be lost. + // https://github.com/qemu/qemu/blob/757a34115e7491744a63dfc3d291fd1de5297ee2/hw/ide/atapi.c#L186 + this.data_allocate(0); + this.data_end = this.data_length; + this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; + this.error_reg = sense_key << 4; + this.sector_count_reg = (this.sector_count_reg & ~7) | 2 | 1; + this.atapi_sense_key = sense_key; + this.atapi_add_sense = additional_sense; +}; + IDEInterface.prototype.do_write = function() { this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; From 56cab05d2628d8f3490fbe8f3d57f234e5f427b0 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Mon, 19 May 2025 19:28:48 +0200 Subject: [PATCH 092/301] documented IDENTIFY binary response, and more code cleanup --- src/ide.js | 357 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 215 insertions(+), 142 deletions(-) diff --git a/src/ide.js b/src/ide.js index 047710e6..fa4dea48 100644 --- a/src/ide.js +++ b/src/ide.js @@ -20,13 +20,15 @@ import { BusConnector } from "./bus.js"; // - [MMC-2] // Packet Commands for C/DVD Devices (1997) // https://www.t10.org/ftp/t10/document.97/97-108r0.pdf +// - [BMI-1] +// Programming Interface for Bus Master IDE Controller, Revision 1.0, 5/16/94 +// https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/u9proj/idems100.pdf const CDROM_SECTOR_SIZE = 2048; const HD_SECTOR_SIZE = 512; -// Per-channel PIO register offsets, legend: +// Per-channel ATA register offsets, legend: // (*1*) Control block register (BAR1/3), else: Command block register (BAR0/2) -// // Read-only registers: const ATA_REG_ERROR = 0x01; // Error register, see [ATA-6] 7.9 const ATA_REG_STATUS = 0x07; // Status register, see [ATA-6] 7.15 @@ -43,6 +45,12 @@ const ATA_REG_FEATURES = 0x01; // Features register, see [ATA-6] 7.10 const ATA_REG_COMMAND = 0x07; // Command register, see [ATA-6] 7.4 const ATA_REG_CONTROL = 0x02; // (*1*) Device Control register, see [ATA-6] 7.8 +// Per-channel Bus Master IDE register offsets (BAR4), see [BMI-1] 2.0 +// these are the primary channel's offsets, add 8 for secondary +const BMI_REG_COMMAND = 0x00; // Bus Master IDE Command register +const BMI_REG_STATUS = 0x02; // Bus Master IDE Status register +const BMI_REG_PRDT = 0x04; // Bus Master IDE PRD Table Address register + // Error register bits: // All bits except for bit 0x04 are command dependent. const ATA_ER_ABRT = 0x04; // Command aborted @@ -184,7 +192,7 @@ export function IDEController(cpu, bus, ide_config) if(has_secondary) { this.secondary = new IDEChannel(this, 1, ide_config[1], { - command_base: 0x170, control_base: 0x374, bus_master_base: bus_master_base + 8, irq: 15 + command_base: 0x170, control_base: 0x374, bus_master_base: bus_master_base, irq: 15 }); this.channels[1] = this.secondary; } @@ -250,6 +258,7 @@ export function IDEController(cpu, bus, ide_config) function IDEChannel(controller, channel_nr, channel_config, hw_settings) { this.controller = controller; + this.channel_nr = channel_nr; this.cpu = controller.cpu; this.bus = controller.bus; this.command_base = hw_settings.command_base; @@ -257,17 +266,25 @@ function IDEChannel(controller, channel_nr, channel_config, hw_settings) this.irq = hw_settings.irq; this.name = "ide" + channel_nr; - const create_interface = interface_nr => { + this.interfaces = []; + for(let interface_nr = 0; interface_nr < 2; interface_nr++) + { const config = channel_config ? channel_config[interface_nr] : undefined; const buffer = config ? config.buffer : undefined; const is_cdrom = config ? !!config.is_cdrom : false; - return new IDEInterface(this, this.cpu, buffer, is_cdrom, channel_nr, interface_nr, this.bus); - }; - - this.master = create_interface(0); - this.slave = create_interface(1); - this.interfaces = [this.master, this.slave]; - this.current_interface = this.master; + const ide_interface = new IDEInterface(this, interface_nr, buffer, is_cdrom); + if(interface_nr === 0) + { + dbg_assert(ide_interface.drive_connected, this.name + ": missing master device", LOG_DISK); + this.master = ide_interface; + this.current_interface = ide_interface; + } + else + { + this.slave = ide_interface; + } + this.interfaces[interface_nr] = ide_interface; + } this.master.init_interface(); this.slave.init_interface(); @@ -305,8 +322,7 @@ function IDEChannel(controller, channel_nr, channel_config, hw_settings) cpu.io.register_read(this.command_base | ATA_REG_ERROR, this, function() { dbg_log(this.current_interface.name + ": read Error register: " + - h(this.current_interface.error_reg & 0xFF) + " slave=" + - (this.current_interface === this.slave), LOG_DISK); + h(this.current_interface.error_reg & 0xFF), LOG_DISK); return this.current_interface.error_reg & 0xFF; }); @@ -395,11 +411,11 @@ function IDEChannel(controller, channel_nr, channel_config, hw_settings) cpu.io.register_write(this.command_base | ATA_REG_DEVICE, this, function(data) { - const slave = data & 0x10; + const select_slave = data & ATA_DR_DEV; dbg_log(this.current_interface.name + ": write Device register: " + h(data, 2), LOG_DISK); - if((slave && this.current_interface === this.master) || (!slave && this.current_interface === this.slave)) + if((select_slave && this.current_interface === this.master) || (!select_slave && this.current_interface === this.slave)) { - if(slave) + if(select_slave) { dbg_log(this.current_interface.name + ": select slave device", LOG_DISK); this.current_interface = this.slave; @@ -411,16 +427,16 @@ function IDEChannel(controller, channel_nr, channel_config, hw_settings) } } this.current_interface.device_reg = data; - this.current_interface.is_lba = data >> 6 & 1; - this.current_interface.head = data & 0xF; + this.current_interface.is_lba = data >> 6 & 1; // TODO: where does this definition of bit 6 come from? not in [ATA-6] or [ATA-4]! + this.current_interface.head = data & 0xF; // TODO: same for lower nibble? }); cpu.io.register_write(this.command_base | ATA_REG_COMMAND, this, function(data) { dbg_log(this.current_interface.name + ": write Command register", LOG_DISK); - dbg_log(this.current_interface.name + ": lower IRQ " + this.irq, LOG_DISK); - this.current_interface.status_reg &= ~(ATA_SR_ERR | 0x20); // 0x20: command dependent, used to be Drive write fault (DF) + this.current_interface.status_reg &= ~(ATA_SR_ERR | 0x20); // 0x20: command dependent, used to be Drive Write Fault (DF) this.current_interface.ata_command(data); + dbg_log(this.current_interface.name + ": lower IRQ " + this.irq, LOG_DISK); this.cpu.device_lower_irq(this.irq); }); @@ -435,21 +451,29 @@ function IDEChannel(controller, channel_nr, channel_config, hw_settings) cpu.io.register_write(this.control_base | ATA_REG_CONTROL, this, this.write_control); // - // Bus Master Registers: bus_master_base + 0...15 (BAR4: B400h, lower 8 for primary and upper 8 for secondary channel) + // Bus Master Registers: bus_master_base + 0...15 (BAR4: B400h) + // primary channel: bus_master_base + 0...7, secondary: bus_master_base + 8...15 // - const bus_master_base = hw_settings.bus_master_base; + const bus_master_base = hw_settings.bus_master_base + channel_nr * 8; - cpu.io.register_read(bus_master_base, this, - this.dma_read_command8, undefined, this.dma_read_command); - cpu.io.register_write(bus_master_base, this, - this.dma_write_command8, undefined, this.dma_write_command); + // read/write Bus Master IDE Command register + cpu.io.register_read(bus_master_base | BMI_REG_COMMAND, + this, this.dma_read_command8, undefined, this.dma_read_command); + cpu.io.register_write(bus_master_base | BMI_REG_COMMAND, + this, this.dma_write_command8, undefined, this.dma_write_command); - cpu.io.register_read(bus_master_base | 2, this, this.dma_read_status); - cpu.io.register_write(bus_master_base | 2, this, this.dma_write_status); + // read/write Bus Master IDE Status register + cpu.io.register_read(bus_master_base | BMI_REG_STATUS, + this, this.dma_read_status); + cpu.io.register_write(bus_master_base | BMI_REG_STATUS, + this, this.dma_write_status); - cpu.io.register_read(bus_master_base | 4, this, undefined, undefined, this.dma_read_addr); - cpu.io.register_write(bus_master_base | 4, this, undefined, undefined, this.dma_set_addr); + // read/write Bus Master IDE PRD Table Address register + cpu.io.register_read(bus_master_base | BMI_REG_PRDT, + this, undefined, undefined, this.dma_read_addr); + cpu.io.register_write(bus_master_base | BMI_REG_PRDT, + this, undefined, undefined, this.dma_set_addr); DEBUG && Object.seal(this); } @@ -612,25 +636,22 @@ IDEChannel.prototype.set_state = function(state) /** * @constructor * @param {IDEChannel} channel - * @param {CPU} cpu - * @param {boolean} is_cd - * @param {number} channel_nr * @param {number} interface_nr - * @param {BusConnector} bus + * @param {boolean} is_cd */ -function IDEInterface(channel, cpu, buffer, is_cd, channel_nr, interface_nr, bus) +function IDEInterface(channel, interface_nr, buffer, is_cd) { this.channel = channel; this.name = channel.name + "." + interface_nr; /** @const @type {BusConnector} */ - this.bus = bus; + this.bus = channel.bus; /** * @const * @type {number} */ - this.channel_nr = channel_nr; + this.channel_nr = channel.channel_nr; /** * @const @@ -639,7 +660,7 @@ function IDEInterface(channel, cpu, buffer, is_cd, channel_nr, interface_nr, bus this.interface_nr = interface_nr; /** @const @type {CPU} */ - this.cpu = cpu; + this.cpu = channel.cpu; this.buffer = null; @@ -732,7 +753,7 @@ function IDEInterface(channel, cpu, buffer, is_cd, channel_nr, interface_nr, bus this.atapi_add_sense = 0; /** @type {boolean} */ - this.media_changed = false; // TODO + this.medium_changed = false; // caller must call this.init_interface() to complete object initialization this.inital_buffer = buffer; @@ -749,10 +770,10 @@ IDEInterface.prototype.eject = function() { if(this.is_atapi && this.buffer) { - this.media_changed = true; + this.medium_changed = true; this.buffer = null; - this.status_reg = 0x59; // TODO - this.error_reg = 0x60; // TODO + this.status_reg = 0x59; // TODO: needs documentation, same in this.set_disk_buffer() below + this.error_reg = 0x60; // TODO: is this perhaps a CHECK CONDITION event for Sense UNIT ATTENTION? this.push_irq(); } }; @@ -762,7 +783,7 @@ IDEInterface.prototype.set_cdrom = function(buffer) if(this.is_atapi && buffer) { this.set_disk_buffer(buffer); - this.media_changed = true; + this.medium_changed = true; } }; @@ -776,7 +797,7 @@ IDEInterface.prototype.set_disk_buffer = function(buffer) this.buffer = buffer; if(this.is_atapi) { - this.status_reg = 0x59; // TODO + this.status_reg = 0x59; // TODO: see this.eject() this.error_reg = 0x60; // TODO } this.sector_count = this.buffer.byteLength / this.sector_size; @@ -877,7 +898,7 @@ IDEInterface.prototype.ata_command = function(cmd) if(!this.drive_connected && cmd !== ATA_CMD_EXECUTE_DEVICE_DIAGNOSTIC) { - dbg_log(this.name + ": ATA command ignored: No slave drive connected", LOG_DISK); + dbg_log(this.name + ": ATA command " + h(cmd) + " ignored: No slave drive connected", LOG_DISK); return; } @@ -898,7 +919,7 @@ IDEInterface.prototype.ata_command = function(cmd) case ATA_CMD_10h: dbg_log(this.name + ": ATA calibrate drive", LOG_DISK); this.lba_mid_reg = 0; - this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DSC; + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; @@ -948,32 +969,16 @@ IDEInterface.prototype.ata_command = function(cmd) case ATA_CMD_EXECUTE_DEVICE_DIAGNOSTIC: dbg_log(this.name + ": ATA execute device diagnostic", LOG_DISK); - // TODO: this code is completely wrong (master device replies if slave does not exist) - // assign diagnostic code (used to be 0x101?) to error register - if(this.interface_nr === 0) + // the behaviour of this command is independent of the selected device + this.channel.master.status_reg = ATA_SR_DRDY|ATA_SR_DSC; + this.channel.master.error_reg = 0x01; // Master drive passed, slave drive passed or not present + this.channel.master.push_irq(); + if(this.channel.slave.drive_connected) { - if(this.channel.slave.drive_connected) - { - this.error_reg = 0x01; // Master drive passed, slave passed or not present - } - else - { - this.error_reg = 0x81; // Master drive passed, slave failed - } + this.channel.slave.status_reg = ATA_SR_DRDY|ATA_SR_DSC; + this.channel.slave.error_reg = 0x01; // Slave drive passed + this.channel.slave.push_irq(); } - else - { - if(this.channel.slave.drive_connected) - { - this.error_reg = 0x01; // Slave drive passed - } - else - { - this.error_reg = 0x00; // Slave drive failed - } - } - this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; - this.push_irq(); break; case ATA_CMD_INITIALIZE_DEVICE_PARAMETERS: @@ -1041,10 +1046,10 @@ IDEInterface.prototype.ata_command = function(cmd) { this.error_reg |= 0x02; // NM: No Media } - if(this.media_changed) + if(this.medium_changed) { this.error_reg |= 0x20; // MC: Media Change - this.media_changed = false; + this.medium_changed = false; } this.error_reg |= 0x40; // WP: Write Protect } @@ -1093,7 +1098,6 @@ IDEInterface.prototype.ata_command = function(cmd) case ATA_CMD_SET_FEATURES: dbg_log(this.name + ": ATA set features: " + h(this.sector_count_reg & 0xFF), LOG_DISK); - // TODO: this one is important, accept/refuse requested device features this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; @@ -1185,7 +1189,7 @@ IDEInterface.prototype.atapi_handle = function() // for data layout see [CD-SCSI-2] "INQUIRY Command" this.data.set([ // 0: Device-type, Removable, ANSI-Version, Response Format - this.drive_connected ? 0x05 : 0x7F, 0x80, 0x01, 0x31, // TODO: drive_connected likely not needed + 0x05, 0x80, 0x01, 0x31, // 4: Additional length, Reserved, Reserved, Reserved 0x31, 0, 0, 0, // 8: Vendor Identification "SONY " @@ -1470,7 +1474,7 @@ IDEInterface.prototype.atapi_read = function(cmd) else { byte_count = Math.min(byte_count, this.buffer.byteLength - start); - this.status_reg = ATA_SR_DRDY|ATA_SR_DSC | 0x80; // TODO: 0x80 + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC | 0x80; // TODO: meaning of 0x80? this.report_read_start(); this.read_buffer(start, byte_count, (data) => @@ -1523,7 +1527,7 @@ IDEInterface.prototype.atapi_read_dma = function(cmd) } else { - this.status_reg = ATA_SR_DRDY|ATA_SR_DSC | 0x80; // TODO: 0x80 + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC | 0x80; // TODO: meaning of 0x80? this.report_read_start(); this.read_buffer(start, byte_count, (data) => @@ -1855,7 +1859,7 @@ IDEInterface.prototype.ata_read_sectors = function(cmd) } else { - this.status_reg = 0x80 | 0x40; // TODO + this.status_reg = 0x80 | 0x40; // TODO: meaning? this.report_read_start(); this.read_buffer(start, byte_count, (data) => @@ -2141,95 +2145,166 @@ IDEInterface.prototype.get_count = function(is_lba48) IDEInterface.prototype.create_identify_packet = function() { - // http://bochs.sourceforge.net/cgi-bin/lxr/source/iodev/harddrv.cc#L2821 + const cylinder_count = Math.min(16383, this.cylinder_count); + const strcpy_be16 = (out_buffer, ofs16, len16, str) => { + let ofs8 = ofs16 << 1; + const len8 = len16 << 1; + const end8 = ofs8 + len8; + out_buffer.fill(32, ofs8, len8); // fill output buffer with ASCII whitespace + for(let i_str = 0; i_str < str.length && ofs8 < end8; i_str++) { + if(i_str & 1) { + out_buffer[ofs8] = str.charCodeAt(i_str); + ofs8 += 2; + } + else { + out_buffer[ofs8 + 1] = str.charCodeAt(i_str); + } + } + }; - for(var i = 0; i < 512; i++) - { - this.data[i] = 0; - } + // Initialize array of 256 16-bit words (big-endian) + // Best source for the lower 64 words of the memory layout used below: + // - [ATA-retro] + // AT Attachment Interface for Disk Drives, Revision 4c + // https://dn790009.ca.archive.org/0/items/SCSISpecificationDocumentsATAATAPI/ATA_ATAPI/AT%20Attachment%20Interface%20for%20Disk%20Drives%20Revision%204c.pdf + // For the words above 64 see [ATA-6] Table 27. + // + // dead link: http://bochs.sourceforge.net/cgi-bin/lxr/source/iodev/harddrv.cc#L2821 - var cylinder_count = Math.min(16383, this.cylinder_count); + // most significant bit indicates ATAPI CD-ROM device + const general_cfg = this.is_atapi ? 0x8540 : 0x0040; + // multiword DMA transfer mode, meaning of 0x0407: + // - 0x0007: Multiword DMA modes 2, 1 and 0 are supported + // - 0x0400: Multiword DMA mode 2 is selected + const multiword_dma_mode = this.current_command === ATA_CMD_PACKET ? 0 : 0x0407; + // Major version number: bits 3/4/5/6 indicate support for ATA/ATAPI-3/4/5/6 (bits 0/1/2 are obsolete in [ATA-6]) + const major_version = 0b01111110; + this.data.fill(0, 0, 512); this.data_set([ - 0x40, this.is_atapi ? 0x85 : 0, - // 1 cylinders - cylinder_count, cylinder_count >> 8, + // 0: General configuration + general_cfg & 0xFF, general_cfg >> 8 & 0xFF, + // 1: Number of cylinders + cylinder_count & 0xFF, cylinder_count >> 8 & 0xFF, + // 2: reserved 0, 0, - - // 3 heads - this.head_count, this.head_count >> 8, - this.sectors_per_track / 512, this.sectors_per_track / 512 >> 8, - // 5 + // 3: Number of heads + this.head_count & 0xFF, this.head_count >> 8 & 0xFF, + // 4: Number of unformatted bytes per track + this.sectors_per_track / 512 & 0xFF, this.sectors_per_track / 512 >> 8 & 0xFF, + // 5: Number of unformatted bytes per sector 0, 512 >> 8, - // sectors per track - this.sectors_per_track, this.sectors_per_track >> 8, - 0, 0, 0, 0, 0, 0, - // 10-19 serial number - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // 15 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // 20 + // 6: Number of sectors per track + this.sectors_per_track & 0xFF, this.sectors_per_track >> 8 & 0xFF, + // 7-9: Vendor-unique + 0, 0, 0, 0, 0, 0, + // 10-19: Serial number (20 ASCII characters) + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + // 20: Buffer type 3, 0, + // 21: Buffer size in 512 byte increments 0, 2, + // 22: Number of ECC bytes avail on read/write long cmds 4, 0, - // 23-26 firmware revision - 0, 0, 0, 0, 0, 0, 0, 0, - - // 27 model number - 56, 118, 32, 54, 68, 72, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - - // 47 max value for set multiple mode + // 23-26: Firmware revision (8 ASCII characters) + 0, 0, 0, 0, 0, 0, 0, 0, + // 27-46: Model number (40 ASCII characters) + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + // 47: Max. number of sectors per interrupt on read/write multiple commands (1st byte) and Vendor-unique (2nd) 0x80, 0, + // 48: Indicates whether can perform doubleword I/O (1st byte) [0: no, 1: yes] 1, 0, - //0, 3, // capabilities, 2: Only LBA / 3: LBA and DMA - 0, 2, // capabilities, 2: Only LBA / 3: LBA and DMA - // 50 + // 49: Vendor-unique (1st byte) and Capabilities (2nd) [2: Only LBA, 3: LBA and DMA] + 0, 2, + // 50: reserved 0, 0, + // 51: PIO data transfer cycle timing mode 0, 2, + // 52: DMA data transfer cycle timing mode 0, 2, + // 53: Indicates whether fields 54-58 are valid (1st byte) [0: no, 1: yes] 7, 0, - - // 54 cylinders - cylinder_count, cylinder_count >> 8, - // 55 heads - this.head_count, this.head_count >> 8, - // 56 sectors per track + // 54: Number of current cylinders + cylinder_count & 0xFF, cylinder_count >> 8 & 0xFF, + // 55: Number of current heads + this.head_count & 0xFF, this.head_count >> 8 & 0xFF, + // 56: Number of current sectors per track this.sectors_per_track, 0, - // capacity in sectors + // 57-58: Current capacity in sectors this.sector_count & 0xFF, this.sector_count >> 8 & 0xFF, this.sector_count >> 16 & 0xFF, this.sector_count >> 24 & 0xFF, - + // 59: Multiple sector setting 0, 0, - // 60 + // 60-61: Total number of user addressable sectors (LBA mode only) this.sector_count & 0xFF, this.sector_count >> 8 & 0xFF, this.sector_count >> 16 & 0xFF, this.sector_count >> 24 & 0xFF, - + // 62: Single word DMA transfer mode 0, 0, - // 63, dma supported mode, dma selected mode - this.current_command === ATA_CMD_PACKET ? 0 : 7, this.current_command === ATA_CMD_PACKET ? 0 : 4, - //0, 0, // no DMA + // 63: Multiword DMA transfer mode (DMA supported mode, DMA selected mode) + multiword_dma_mode & 0xFF, multiword_dma_mode >> 8 & 0xFF, + // 64: PIO modes supported 0, 0, - // 65 - 30, 0, 30, 0, 30, 0, 30, 0, 0, 0, - // 70 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // 75 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // 80 - 0x7E, 0, 0, 0, 0, 0, 0, 0x74, 0, 0x40, - // 85 - 0, 0x40, 0, 0x74, 0, 0x40, 0, 0, 0, 0, - // 90 - 0, 0, 0, 0, 0, 0, 1, 0x60, 0, 0, - // 95 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // 100 + // 65-68: fields related to cycle-time + 30, 0, 30, 0, 30, 0, 30, 0, + // 69-74: reserved + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + // 75: Queue depth + 0, 0, + // 76-79: reserved + 0, 0, 0, 0, 0, 0, 0, 0, + // 80: Major version number + major_version & 0xFF, major_version >> 8 & 0xFF, + // 81: Minor version number + 0, 0, + // 82: Command set supported + 0, 0, + // 83: Command set supported + 0, 0x74, + // 84: Command set/feature supported extension + 0, 0x40, + // 85: Command set/feature enabled + 0, 0x40, + // 86: Command set/feature enabled + 0, 0x74, + // 87: Command set/feature default + 0, 0x40, + // 88: DMA related field + 0, 0, + // 89: Time required for security erase unit completion + 0, 0, + // 90: Time required for Enhanced security erase completion + 0, 0, + // 91: Current advanced power management value + 0, 0, + // 92: Master Password Revision Code + 0, 0, + // 93: Hardware reset result + 1, 0x60, + // 94: Acoustic management value + 0, 0, + // 95-99: reserved + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, + // 100-103: Maximum user LBA for 48-bit Address feature set. this.sector_count & 0xFF, this.sector_count >> 8 & 0xFF, this.sector_count >> 16 & 0xFF, this.sector_count >> 24 & 0xFF, ]); + // 10-19 serial number + strcpy_be16(this.data, 10, 10, "86" + this.channel_nr.toString() + this.interface_nr.toString()); + // 23-26 firmware revision + strcpy_be16(this.data, 23, 4, "1.00"); + // 27-46 model number + strcpy_be16(this.data, 27, 20, this.is_atapi ? "v86 ATAPI CD-ROM" : "v86 ATA HD"); + this.data_length = 512; this.data_end = 512; }; @@ -2337,8 +2412,6 @@ IDEInterface.prototype.get_state = function() state[26] = this.data_end; state[27] = this.current_atapi_command; state[28] = this.buffer; - state[29] = this.drive_connected; - state[30] = this.media_changed; return state; }; @@ -2376,6 +2449,6 @@ IDEInterface.prototype.set_state = function(state) this.buffer && this.buffer.set_state(state[28]); - this.drive_connected = state[29] === undefined ? this.is_atapi || this.buffer : state[29]; - this.media_changed = state[30] === undefined ? false : state[30]; + this.drive_connected = this.is_atapi || this.buffer; + this.medium_changed = false; }; From add68c3202b9fc080ae5e7fbfea85b4f869786e7 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Thu, 22 May 2025 18:33:37 +0200 Subject: [PATCH 093/301] restored IDEInterface.device_reset() back to its original code --- src/browser/starter.js | 8 -------- src/cpu.js | 7 +------ src/ide.js | 12 +++++------- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/src/browser/starter.js b/src/browser/starter.js index e2445a39..83c77d5d 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -119,14 +119,6 @@ import { EEXIST, ENOENT } from "../../lib/9p.js"; * } * ``` * - * In order to create a CD-ROM device with ejected disk use: - * - * ```javascript - * cdrom: { - * ejected: true - * } - * ``` - * * @param {{ disable_mouse: (boolean|undefined), disable_keyboard: (boolean|undefined), diff --git a/src/cpu.js b/src/cpu.js index 00a2992a..182de8c1 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -1113,12 +1113,7 @@ CPU.prototype.init = function(settings, device_bus) ide_config[0][1] = {buffer: settings.hdb}; cdrom_channel++; } - if(settings.cdrom) { - ide_config[cdrom_channel][0] = { - is_cdrom: true, - buffer: settings.cdrom.ejected ? undefined : settings.cdrom - }; - } + ide_config[cdrom_channel][0] = {is_cdrom: true, buffer: settings.cdrom}; this.devices.ide = new IDEController(this, device_bus, ide_config); diff --git a/src/ide.js b/src/ide.js index fa4dea48..462616e3 100644 --- a/src/ide.js +++ b/src/ide.js @@ -307,7 +307,6 @@ function IDEChannel(controller, channel_nr, channel_config, hw_settings) // Command Block Registers: command_base + 0...7 (BAR0: 1F0h, BAR2: 170h) // - // read Data register cpu.io.register_read(this.command_base | ATA_REG_DATA, this, function() { return this.current_interface.read_data(1); @@ -865,25 +864,24 @@ IDEInterface.prototype.set_disk_buffer = function(buffer) IDEInterface.prototype.device_reset = function() { - // for details, see [ATA-6] 9.11 "DEVICE RESET command protocol" if(this.is_atapi) { + this.status_reg = 0; this.sector_count_reg = 1; + this.error_reg = 1; this.lba_low_reg = 1; this.lba_mid_reg = 0x14; this.lba_high_reg = 0xEB; - this.device_reg &= ATA_DR_DEV; } else { + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_ERR; this.sector_count_reg = 1; + this.error_reg = 1; this.lba_low_reg = 1; this.lba_mid_reg = 0; this.lba_high_reg = 0; - this.device_reg = 0; } - this.error_reg &= ~0x80; // clear bit 7 only (explicitly) - this.status_reg &= ~(ATA_SR_ERR|ATA_SR_BSY); this.cancel_io_operations(); }; @@ -2293,7 +2291,7 @@ IDEInterface.prototype.create_identify_packet = function() // 95-99: reserved 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // 100-103: Maximum user LBA for 48-bit Address feature set. + // 100-101: Maximum user LBA for 48-bit Address feature set. this.sector_count & 0xFF, this.sector_count >> 8 & 0xFF, this.sector_count >> 16 & 0xFF, this.sector_count >> 24 & 0xFF, ]); From f3e29baa9c05cff2e4c119345a925de164c7dc49 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Thu, 22 May 2025 20:08:38 +0200 Subject: [PATCH 094/301] bugfix: cd-rom device now exists unconditionally --- src/cpu.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/cpu.js b/src/cpu.js index 182de8c1..329177e4 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -1121,9 +1121,7 @@ CPU.prototype.init = function(settings, device_bus) this.devices.hda = this.devices.ide.channels[0]; // this.devices.hdb = ? // TODO: this.devices.hda/hdb/cdrom should point to IDEInterface, not IDEDevice objects?! } - if(settings.cdrom) { - this.devices.cdrom = this.devices.ide.channels[cdrom_channel]; - } + this.devices.cdrom = this.devices.ide.channels[cdrom_channel]; } this.devices.pit = new PIT(this, device_bus); From 0257cb83c93160b18a6bc079799e496eb1a32297 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Thu, 22 May 2025 20:41:23 +0200 Subject: [PATCH 095/301] added "Insert/Eject CD" button to web UI --- index.html | 1 + src/browser/main.js | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/index.html b/index.html index 9557b766..35591075 100644 --- a/index.html +++ b/index.html @@ -241,6 +241,7 @@ + diff --git a/src/browser/main.js b/src/browser/main.js index a702f022..3e9735d1 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -2395,6 +2395,32 @@ function init_ui(profile, settings, emulator) $("change_fda_image").blur(); }; + $("change_cdrom_image").value = settings.cdrom ? "Eject CD image" : "Insert CD image"; + $("change_cdrom_image").onclick = function() + { + if(emulator.v86.cpu.devices.cdrom.master.buffer) + { + emulator.eject_cdrom(); + $("change_cdrom_image").value = "Insert CD image"; + } + else + { + const file_input = document.createElement("input"); + file_input.type = "file"; + file_input.onchange = async function(e) + { + const file = file_input.files[0]; + if(file) + { + await emulator.set_cdrom({ buffer: file }); + $("change_cdrom_image").value = "Eject CD image"; + } + }; + file_input.click(); + } + $("change_cdrom_image").blur(); + }; + $("memory_dump").onclick = function() { const mem8 = emulator.v86.cpu.mem8; From b63e488844760f5629eed2b980b36ee6ef485fde Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Thu, 22 May 2025 20:56:34 +0200 Subject: [PATCH 096/301] cleanup: cd-rom device now exists unconditionally --- src/browser/starter.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/browser/starter.js b/src/browser/starter.js index 83c77d5d..7f5c84c7 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -457,13 +457,6 @@ V86.prototype.continue_init = async function(emulator, options) return; } - if(name === "cdrom" && file.ejected) - { - // the "ejected file" is a special CD-ROM file object, pass it to settings.cdrom and let CPU.init() handle it - settings.cdrom = file; - return; - } - if(file.get && file.set && file.load) { files_to_load.push({ From 59a697ca93717e6ef7887147509382b42722c5b1 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Fri, 23 May 2025 16:03:43 +0200 Subject: [PATCH 097/301] added ATA command "NOP" --- src/ide.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ide.js b/src/ide.js index 462616e3..413ab30f 100644 --- a/src/ide.js +++ b/src/ide.js @@ -90,6 +90,7 @@ const ATA_CMD_IDENTIFY_PACKET_DEVICE = 0xA1; // see [ATA-6] 8.16 const ATA_CMD_IDLE_IMMEDIATE = 0xE1; // see [ATA-6] 8.18 const ATA_CMD_INITIALIZE_DEVICE_PARAMETERS = 0x91; // not mentioned in [ATA-6] const ATA_CMD_MEDIA_LOCK = 0xDE; // see [ATA-6] 8.20 +const ATA_CMD_NOP = 0x00; // see [ATA-6] 8.22 const ATA_CMD_PACKET = 0xA0; // see [ATA-6] 8.23 const ATA_CMD_READ_DMA = 0xC8; // see [ATA-6] 8.26 const ATA_CMD_READ_DMA_EXT = 0x25; // see [ATA-6] 8.25 @@ -1119,6 +1120,13 @@ IDEInterface.prototype.ata_command = function(cmd) this.push_irq(); break; + case ATA_CMD_NOP: + dbg_log(this.name + ": ATA nop", LOG_DISK); + this.error_reg = ATA_ER_ABRT; + this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; + this.push_irq(); + break; + default: dbg_assert(false, this.name + ": unhandled ATA command: " + h(cmd), LOG_DISK); this.error_reg = ATA_ER_ABRT; From e1610ff1a957a812c7b282730f0d2b98fc92b58e Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Fri, 23 May 2025 16:54:24 +0200 Subject: [PATCH 098/301] abort any ATA READ command called on ejected device (W95) --- src/ide.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ide.js b/src/ide.js index 413ab30f..12b03936 100644 --- a/src/ide.js +++ b/src/ide.js @@ -1856,7 +1856,14 @@ IDEInterface.prototype.ata_read_sectors = function(cmd) " lbacount=" + h(count) + " bytecount=" + h(byte_count), LOG_DISK); - if(start + byte_count > this.buffer.byteLength) + if(!this.buffer) + { + // TODO: Windows 95 treats our (empty) CD-ROM device as an ATA device, maybe a driver issue? + this.error_reg = ATA_ER_ABRT; + this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; + this.push_irq(); + } + else if(start + byte_count > this.buffer.byteLength) { dbg_assert(false, this.name + ": ATA read: Outside of disk", LOG_DISK); From 694537f51ec572fd0ad12aa5ddbd0b00697508c3 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Fri, 23 May 2025 16:56:16 +0200 Subject: [PATCH 099/301] added "Insert/Eject CD" button to debug.html --- debug.html | 1 + 1 file changed, 1 insertion(+) diff --git a/debug.html b/debug.html index 36c79c83..f37feb70 100644 --- a/debug.html +++ b/debug.html @@ -212,6 +212,7 @@ + From e98f30b4e58c3385200da98fb6df17221d9a804c Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sat, 24 May 2025 09:59:09 +0200 Subject: [PATCH 100/301] reduced DMA-related debug log output --- src/ide.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/ide.js b/src/ide.js index 12b03936..bd3cc4e9 100644 --- a/src/ide.js +++ b/src/ide.js @@ -502,13 +502,13 @@ IDEChannel.prototype.write_control = function(data) IDEChannel.prototype.dma_read_addr = function() { - dbg_log(this.current_interface.name + ": DMA get address: " + h(this.prdt_addr, 8), LOG_DISK); + //dbg_log(this.current_interface.name + ": DMA get address: " + h(this.prdt_addr, 8), LOG_DISK); return this.prdt_addr; }; IDEChannel.prototype.dma_set_addr = function(data) { - dbg_log(this.current_interface.name + ": DMA set address: " + h(data, 8), LOG_DISK); + //dbg_log(this.current_interface.name + ": DMA set address: " + h(data, 8), LOG_DISK); this.prdt_addr = data; }; @@ -531,21 +531,20 @@ IDEChannel.prototype.dma_read_command = function() IDEChannel.prototype.dma_read_command8 = function() { - dbg_log(this.current_interface.name + ": DMA read command: " + h(this.dma_command), LOG_DISK); + //dbg_log(this.current_interface.name + ": DMA read command: " + h(this.dma_command), LOG_DISK); return this.dma_command; }; IDEChannel.prototype.dma_write_command = function(value) { - dbg_log(this.current_interface.name + ": DMA write command: " + h(value), LOG_DISK); - + //dbg_log(this.current_interface.name + ": DMA write command: " + h(value), LOG_DISK); this.dma_write_command8(value & 0xFF); this.dma_write_status(value >> 16 & 0xFF); }; IDEChannel.prototype.dma_write_command8 = function(value) { - dbg_log(this.current_interface.name + ": DMA write command8: " + h(value), LOG_DISK); + //dbg_log(this.current_interface.name + ": DMA write command8: " + h(value), LOG_DISK); const old_command = this.dma_command; this.dma_command = value & 0x09; From 9e45314ed2090edbfd532231e2a93a3caa533148 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sat, 24 May 2025 11:01:25 +0200 Subject: [PATCH 101/301] abort any ATA read/write attempt on ATAPI device --- src/ide.js | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/ide.js b/src/ide.js index bd3cc4e9..ad1e7ffd 100644 --- a/src/ide.js +++ b/src/ide.js @@ -955,14 +955,32 @@ IDEInterface.prototype.ata_command = function(cmd) case ATA_CMD_READ_SECTORS_EXT: case ATA_CMD_READ_MULTIPLE: case ATA_CMD_READ_MULTIPLE_EXT: - this.ata_read_sectors(cmd); + if(this.is_atapi) + { + this.error_reg = ATA_ER_ABRT; + this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; + this.push_irq(); + } + else + { + this.ata_read_sectors(cmd); + } break; case ATA_CMD_WRITE_SECTORS: case ATA_CMD_WRITE_SECTORS_EXT: case ATA_CMD_WRITE_MULTIPLE: case ATA_CMD_WRITE_MULTIPLE_EXT: - this.ata_write_sectors(cmd); + if(this.is_atapi) + { + this.error_reg = ATA_ER_ABRT; + this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; + this.push_irq(); + } + else + { + this.ata_write_sectors(cmd); + } break; case ATA_CMD_EXECUTE_DEVICE_DIAGNOSTIC: @@ -1855,14 +1873,7 @@ IDEInterface.prototype.ata_read_sectors = function(cmd) " lbacount=" + h(count) + " bytecount=" + h(byte_count), LOG_DISK); - if(!this.buffer) - { - // TODO: Windows 95 treats our (empty) CD-ROM device as an ATA device, maybe a driver issue? - this.error_reg = ATA_ER_ABRT; - this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; - this.push_irq(); - } - else if(start + byte_count > this.buffer.byteLength) + if(start + byte_count > this.buffer.byteLength) { dbg_assert(false, this.name + ": ATA read: Outside of disk", LOG_DISK); From 0cd7ea7a3d0e5ac47b001d7375ea8b74bad19c0c Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sat, 24 May 2025 11:16:47 +0200 Subject: [PATCH 102/301] added IDEInterface.ata_abort_command() to reduce code duplication --- src/ide.js | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/src/ide.js b/src/ide.js index ad1e7ffd..6d7adb63 100644 --- a/src/ide.js +++ b/src/ide.js @@ -890,6 +890,14 @@ IDEInterface.prototype.push_irq = function() this.channel.push_irq(); }; +IDEInterface.prototype.ata_abort_command = function() +{ + dbg_log(this.name + ": ATA Command " + h(this.current_command) + " aborted", LOG_DISK); + this.error_reg = ATA_ER_ABRT; + this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; + this.push_irq(); +}; + IDEInterface.prototype.ata_command = function(cmd) { dbg_log(this.name + ": ATA Command: " + h(cmd), LOG_DISK); @@ -957,9 +965,7 @@ IDEInterface.prototype.ata_command = function(cmd) case ATA_CMD_READ_MULTIPLE_EXT: if(this.is_atapi) { - this.error_reg = ATA_ER_ABRT; - this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; - this.push_irq(); + this.ata_abort_command(); } else { @@ -973,9 +979,7 @@ IDEInterface.prototype.ata_command = function(cmd) case ATA_CMD_WRITE_MULTIPLE_EXT: if(this.is_atapi) { - this.error_reg = ATA_ER_ABRT; - this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; - this.push_irq(); + this.ata_abort_command(); } else { @@ -1019,13 +1023,12 @@ IDEInterface.prototype.ata_command = function(cmd) { this.create_identify_packet(); this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; + this.push_irq(); } else { - this.error_reg = ATA_ER_ABRT; - this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; + this.ata_abort_command(); } - this.push_irq(); break; case ATA_CMD_SET_MULTIPLE_MODE: @@ -1101,15 +1104,14 @@ IDEInterface.prototype.ata_command = function(cmd) dbg_log(this.name + ": ATA identify device", LOG_DISK); if(this.is_atapi) { - this.error_reg = ATA_ER_ABRT; - this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; + this.ata_abort_command(); } else { this.create_identify_packet(); this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; + this.push_irq(); } - this.push_irq(); break; case ATA_CMD_SET_FEATURES: @@ -1132,23 +1134,18 @@ IDEInterface.prototype.ata_command = function(cmd) case ATA_CMD_SET_MAX: dbg_log(this.name + ": ATA set max address (unimplemented)", LOG_DISK); - this.error_reg = ATA_ER_ABRT; - this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; - this.push_irq(); + this.ata_abort_command(); break; case ATA_CMD_NOP: dbg_log(this.name + ": ATA nop", LOG_DISK); - this.error_reg = ATA_ER_ABRT; - this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; - this.push_irq(); + this.ata_abort_command(); break; default: dbg_assert(false, this.name + ": unhandled ATA command: " + h(cmd), LOG_DISK); - this.error_reg = ATA_ER_ABRT; - this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; - this.push_irq(); + this.ata_abort_command(); + break; } }; From eb374437e334bba5f50231091e3d84d923ce82d2 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sat, 24 May 2025 19:27:40 +0200 Subject: [PATCH 103/301] made the debug output more comprehensible Debug log messages in ide.js are currently at maximum granularity and emitted at a very high volume. Under rare conditions this output can be very useful, else it clutters the view and makes comprehension of and reasoning about the runtime behaviour too challenging. Changes: - Erros and warnings are always logged, as well as important IDE state-related messages. - Debug log messages are now split into 5 categories, 4 of them (the highly specific and also high-frequency ones) are optional and by default disabled. - The 4 optional categories are REG_IO (log messages related to register I/O), IRQ, RW (read/write data) and RW_DMA (read/write data with DMA). - ATA/ATAPI commands are always logged unless they fall into one of the 4 optional categories. - Commands are logged with their name and including the full register state before and after command execution (input and output arguments). Normal debug log output (with all 4 optional categories disabled, the default) is now mostly reduced to the actual ATA/ATAPI command flow with input/output command arguments, enough so that it's much easier to understand what happens. The 4 optional debug categories can only be enabled programmatically in ide.js, they are meant to be used in advanced debugging sessions. --- src/ide.js | 540 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 367 insertions(+), 173 deletions(-) diff --git a/src/ide.js b/src/ide.js index 6d7adb63..c3e4c5a1 100644 --- a/src/ide.js +++ b/src/ide.js @@ -114,11 +114,47 @@ const ATA_CMD_WRITE_SECTORS = 0x30; // see [ATA-6] 8.62 const ATA_CMD_WRITE_SECTORS_EXT = 0x34; // see [ATA-6] 8.63 const ATA_CMD_10h = 0x10; // command obsolete/unknown, see [ATA-6] Table E.2 +const ATA_CMD_NAME = +{ + [ATA_CMD_DEVICE_RESET]: "DEVICE RESET", + [ATA_CMD_EXECUTE_DEVICE_DIAGNOSTIC]: "EXECUTE DEVICE DIAGNOSTIC", + [ATA_CMD_FLUSH_CACHE]: "FLUSH CACHE", + [ATA_CMD_FLUSH_CACHE_EXT]: "FLUSH CACHE EXT", + [ATA_CMD_GET_MEDIA_STATUS]: "GET MEDIA STATUS", + [ATA_CMD_IDENTIFY_DEVICE]: "IDENTIFY DEVICE", + [ATA_CMD_IDENTIFY_PACKET_DEVICE]: "IDENTIFY PACKET DEVICE", + [ATA_CMD_IDLE_IMMEDIATE]: "IDLE IMMEDIATE", + [ATA_CMD_INITIALIZE_DEVICE_PARAMETERS]: "INITIALIZE DEVICE PARAMETERS", + [ATA_CMD_MEDIA_LOCK]: "MEDIA LOCK", + [ATA_CMD_NOP]: "NOP", + [ATA_CMD_PACKET]: "PACKET", + [ATA_CMD_READ_DMA]: "READ DMA", + [ATA_CMD_READ_DMA_EXT]: "READ DMA EXT", + [ATA_CMD_READ_MULTIPLE]: "READ MULTIPLE", + [ATA_CMD_READ_MULTIPLE_EXT]: "READ MULTIPLE EXT", + [ATA_CMD_READ_NATIVE_MAX_ADDRESS]: "READ NATIVE MAX ADDRESS", + [ATA_CMD_READ_NATIVE_MAX_ADDRESS_EXT]: "READ NATIVE MAX ADDRESS EXT", + [ATA_CMD_READ_SECTORS]: "READ SECTORS", + [ATA_CMD_READ_SECTORS_EXT]: "READ SECTORS EXT", + [ATA_CMD_READ_VERIFY_SECTORS]: "READ VERIFY SECTORS", + [ATA_CMD_SECURITY_FREEZE_LOCK]: "SECURITY FREEZE LOCK", + [ATA_CMD_SET_FEATURES]: "SET FEATURES", + [ATA_CMD_SET_MAX]: "SET MAX", + [ATA_CMD_SET_MULTIPLE_MODE]: "SET MULTIPLE MODE", + [ATA_CMD_STANDBY_IMMEDIATE]: "STANDBY IMMEDIATE", + [ATA_CMD_WRITE_DMA]: "WRITE DMA", + [ATA_CMD_WRITE_DMA_EXT]: "WRITE DMA EXT", + [ATA_CMD_WRITE_MULTIPLE]: "WRITE MULTIPLE", + [ATA_CMD_WRITE_MULTIPLE_EXT]: "WRITE MULTIPLE EXT", + [ATA_CMD_WRITE_SECTORS]: "WRITE SECTORS", + [ATA_CMD_WRITE_SECTORS_EXT]: "WRITE SECTORS EXT", + [ATA_CMD_10h]: "", +}; + // ATAPI (SCSI-2/MMC-2) commands const ATAPI_CMD_GET_CONFIGURATION = 0x46; // see [CD-SCSI-2] const ATAPI_CMD_GET_EVENT_STATUS_NOTIFICATION = 0x4A; // see [MMC-2] 9.1.2 const ATAPI_CMD_INQUIRY = 0x12; // see [MMC-2] 9.1.3 -//const ATAPI_CMD_LOAD_UNLOAD_MEDIUM = 0xA6; // see [MMC-2] 9.1.4 -- TODO: not implemented, might be useful const ATAPI_CMD_MECHANISM_STATUS = 0xBD; // see [MMC-2] 9.1.5 const ATAPI_CMD_MODE_SENSE_6 = 0x1A; // see [CD-SCSI-2] const ATAPI_CMD_MODE_SENSE_10 = 0x5A; // see [MMC-2] 9.1.7 @@ -133,6 +169,26 @@ const ATAPI_CMD_READ_TRACK_INFORMATION = 0x52; // see [CD-SCSI-2] const ATAPI_CMD_REQUEST_SENSE = 0x03; // see [MMC-2] 9.1.18 const ATAPI_CMD_TEST_UNIT_READY = 0x00; // see [MMC-2] 9.1.20 +const ATAPI_CMD_NAME = +{ + [ATAPI_CMD_GET_CONFIGURATION]: "GET CONFIGURATION", + [ATAPI_CMD_GET_EVENT_STATUS_NOTIFICATION]: "GET EVENT STATUS NOTIFICATION", + [ATAPI_CMD_INQUIRY]: "INQUIRY", + [ATAPI_CMD_MECHANISM_STATUS]: "MECHANISM STATUS", + [ATAPI_CMD_MODE_SENSE_6]: "MODE SENSE (6)", + [ATAPI_CMD_MODE_SENSE_10]: "MODE SENSE (10)", + [ATAPI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL]: "PREVENT ALLOW MEDIUM REMOVAL", + [ATAPI_CMD_READ]: "READ", + [ATAPI_CMD_READ_CAPACITY]: "READ CAPACITY", + [ATAPI_CMD_READ_CD]: "READ CD", + [ATAPI_CMD_READ_DISK_INFORMATION]: "READ DISK INFORMATION", + [ATAPI_CMD_READ_SUBCHANNEL]: "READ SUBCHANNEL", + [ATAPI_CMD_READ_TOC_PMA_ATIP]: "READ TOC PMA ATIP", + [ATAPI_CMD_READ_TRACK_INFORMATION]: "READ TRACK INFORMATION", + [ATAPI_CMD_REQUEST_SENSE]: "REQUEST SENSE", + [ATAPI_CMD_TEST_UNIT_READY]: "TEST UNIT READY", +}; + // ATAPI 4-bit Sense Keys, see [MMC-2] 9.1.18.3, Table 123 const ATAPI_SK_NO_SENSE = 0; const ATAPI_SK_RECOVERED_ERROR = 1; @@ -149,6 +205,16 @@ const ATAPI_SK_ABORTED_COMMAND = 11; const ATAPI_ASC_INV_FIELD_IN_CMD_PACKET = 0x24; const ATAPI_ASC_MEDIUM_NOT_PRESENT = 0x3A; +// Debug log detail bits (internal to this module) +const LOG_DETAIL_NONE = 0x00; // disable debug logging of details +const LOG_DETAIL_REG_IO = 0x01; // log register read/write access +const LOG_DETAIL_IRQ = 0x02; // log IRQ raise/lower events +const LOG_DETAIL_RW = 0x04; // log data read/write-related events +const LOG_DETAIL_RW_DMA = 0x08; // log DMA data read/write-related events +const LOG_DETAIL_ALL = 0xFF; // log all details +// the bitset of active log details (should be 0 when not in DEBUG mode) +const LOG_DETAILS = DEBUG ? LOG_DETAIL_NONE : 0; + /** * @constructor * @param {CPU} cpu @@ -267,26 +333,13 @@ function IDEChannel(controller, channel_nr, channel_config, hw_settings) this.irq = hw_settings.irq; this.name = "ide" + channel_nr; - this.interfaces = []; - for(let interface_nr = 0; interface_nr < 2; interface_nr++) - { - const config = channel_config ? channel_config[interface_nr] : undefined; - const buffer = config ? config.buffer : undefined; - const is_cdrom = config ? !!config.is_cdrom : false; - const ide_interface = new IDEInterface(this, interface_nr, buffer, is_cdrom); - if(interface_nr === 0) - { - dbg_assert(ide_interface.drive_connected, this.name + ": missing master device", LOG_DISK); - this.master = ide_interface; - this.current_interface = ide_interface; - } - else - { - this.slave = ide_interface; - } - this.interfaces[interface_nr] = ide_interface; - } + const master_cfg = channel_config ? channel_config[0] : undefined; + const slave_cfg = channel_config ? channel_config[1] : undefined; + this.master = new IDEInterface(this, 0, master_cfg ? master_cfg.buffer : undefined, master_cfg ? !!master_cfg.is_cdrom : false); + this.slave = new IDEInterface(this, 1, slave_cfg ? slave_cfg.buffer : undefined, slave_cfg ? !!slave_cfg.is_cdrom : false); + this.interfaces = [this.master, this.slave]; + this.current_interface = this.master; this.master.init_interface(); this.slave.init_interface(); @@ -321,51 +374,72 @@ function IDEChannel(controller, channel_nr, channel_config, hw_settings) cpu.io.register_read(this.command_base | ATA_REG_ERROR, this, function() { - dbg_log(this.current_interface.name + ": read Error register: " + - h(this.current_interface.error_reg & 0xFF), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_REG_IO) + { + dbg_log(this.current_interface.name + ": read Error register: " + + h(this.current_interface.error_reg & 0xFF), LOG_DISK); + } return this.current_interface.error_reg & 0xFF; }); cpu.io.register_read(this.command_base | ATA_REG_SECTOR, this, function() { - dbg_log(this.current_interface.name + ": read Sector Count register: " + - h(this.current_interface.sector_count_reg & 0xFF), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_REG_IO) + { + dbg_log(this.current_interface.name + ": read Sector Count register: " + + h(this.current_interface.sector_count_reg & 0xFF), LOG_DISK); + } return this.current_interface.sector_count_reg & 0xFF; }); cpu.io.register_read(this.command_base | ATA_REG_LBA_LOW, this, function() { - dbg_log(this.current_interface.name + ": read LBA Low register: " + - h(this.current_interface.lba_low_reg & 0xFF), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_REG_IO) + { + dbg_log(this.current_interface.name + ": read LBA Low register: " + + h(this.current_interface.lba_low_reg & 0xFF), LOG_DISK); + } return this.current_interface.lba_low_reg & 0xFF; }); cpu.io.register_read(this.command_base | ATA_REG_LBA_MID, this, function() { - dbg_log(this.current_interface.name + ": read LBA Mid register: " + - h(this.current_interface.lba_mid_reg & 0xFF), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_REG_IO) + { + dbg_log(this.current_interface.name + ": read LBA Mid register: " + + h(this.current_interface.lba_mid_reg & 0xFF), LOG_DISK); + } return this.current_interface.lba_mid_reg & 0xFF; }); cpu.io.register_read(this.command_base | ATA_REG_LBA_HIGH, this, function() { - dbg_log(this.current_interface.name + ": read LBA High register: " + - h(this.current_interface.lba_high_reg & 0xFF), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_REG_IO) + { + dbg_log(this.current_interface.name + ": read LBA High register: " + + h(this.current_interface.lba_high_reg & 0xFF), LOG_DISK); + } return this.current_interface.lba_high_reg & 0xFF; }); cpu.io.register_read(this.command_base | ATA_REG_DEVICE, this, function() { - dbg_log(this.current_interface.name + ": read Device register", LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_REG_IO) + { + dbg_log(this.current_interface.name + ": read Device register", LOG_DISK); + } return this.current_interface.device_reg & 0xFF; }); cpu.io.register_read(this.command_base | ATA_REG_STATUS, this, function() { - dbg_log(this.current_interface.name + ": read Status register", LOG_DISK); - dbg_log(this.current_interface.name + ": lower IRQ " + this.irq, LOG_DISK); + const status = this.read_status(); + if(LOG_DETAILS & (LOG_DETAIL_REG_IO | LOG_DETAIL_IRQ)) + { + dbg_log(`${this.current_interface.name}: read Status register: ${h(status, 2)} (lower IRQ ${this.irq})`, LOG_DISK); + } this.cpu.device_lower_irq(this.irq); - return this.read_status(); + return status; }); cpu.io.register_write(this.command_base | ATA_REG_DATA, this, function(data) @@ -381,38 +455,56 @@ function IDEChannel(controller, channel_nr, channel_config, hw_settings) cpu.io.register_write(this.command_base | ATA_REG_FEATURES, this, function(data) { - dbg_log(this.current_interface.name + ": write Features register: " + h(data), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_REG_IO) + { + dbg_log(this.current_interface.name + ": write Features register: " + h(data), LOG_DISK); + } this.current_interface.features_reg = (this.current_interface.features_reg << 8 | data) & 0xFFFF; }); cpu.io.register_write(this.command_base | ATA_REG_SECTOR, this, function(data) { - dbg_log(this.current_interface.name + ": write Sector Count register: " + h(data), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_REG_IO) + { + dbg_log(this.current_interface.name + ": write Sector Count register: " + h(data), LOG_DISK); + } this.current_interface.sector_count_reg = (this.current_interface.sector_count_reg << 8 | data) & 0xFFFF; }); cpu.io.register_write(this.command_base | ATA_REG_LBA_LOW, this, function(data) { - dbg_log(this.current_interface.name + ": write LBA Low register: " + h(data), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_REG_IO) + { + dbg_log(this.current_interface.name + ": write LBA Low register: " + h(data), LOG_DISK); + } this.current_interface.lba_low_reg = (this.current_interface.lba_low_reg << 8 | data) & 0xFFFF; }); cpu.io.register_write(this.command_base | ATA_REG_LBA_MID, this, function(data) { - dbg_log(this.current_interface.name + ": write LBA Mid register: " + h(data), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_REG_IO) + { + dbg_log(this.current_interface.name + ": write LBA Mid register: " + h(data), LOG_DISK); + } this.current_interface.lba_mid_reg = (this.current_interface.lba_mid_reg << 8 | data) & 0xFFFF; }); cpu.io.register_write(this.command_base | ATA_REG_LBA_HIGH, this, function(data) { - dbg_log(this.current_interface.name + ": write LBA High register: " + h(data), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_REG_IO) + { + dbg_log(this.current_interface.name + ": write LBA High register: " + h(data), LOG_DISK); + } this.current_interface.lba_high_reg = (this.current_interface.lba_high_reg << 8 | data) & 0xFFFF; }); cpu.io.register_write(this.command_base | ATA_REG_DEVICE, this, function(data) { const select_slave = data & ATA_DR_DEV; - dbg_log(this.current_interface.name + ": write Device register: " + h(data, 2), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_REG_IO) + { + dbg_log(this.current_interface.name + ": write Device register: " + h(data, 2), LOG_DISK); + } if((select_slave && this.current_interface === this.master) || (!select_slave && this.current_interface === this.slave)) { if(select_slave) @@ -433,10 +525,16 @@ function IDEChannel(controller, channel_nr, channel_config, hw_settings) cpu.io.register_write(this.command_base | ATA_REG_COMMAND, this, function(data) { - dbg_log(this.current_interface.name + ": write Command register", LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_REG_IO) + { + dbg_log(this.current_interface.name + ": write Command register", LOG_DISK); + } this.current_interface.status_reg &= ~(ATA_SR_ERR | 0x20); // 0x20: command dependent, used to be Drive Write Fault (DF) this.current_interface.ata_command(data); - dbg_log(this.current_interface.name + ": lower IRQ " + this.irq, LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_IRQ) + { + dbg_log(this.current_interface.name + ": lower IRQ " + this.irq, LOG_DISK); + } this.cpu.device_lower_irq(this.irq); }); @@ -480,19 +578,19 @@ function IDEChannel(controller, channel_nr, channel_config, hw_settings) IDEChannel.prototype.read_status = function() { - const ret = this.current_interface.drive_connected ? this.current_interface.status_reg : 0; - dbg_log(this.current_interface.name + ": ATA read status: " + h(ret, 2), LOG_DISK); - return ret; + return this.current_interface.drive_connected ? this.current_interface.status_reg : 0; }; IDEChannel.prototype.write_control = function(data) { - dbg_log(this.current_interface.name + ": write Device Control register: " + - h(data, 2) + " interrupts " + ((data & ATA_CR_nIEN) ? "disabled" : "enabled"), LOG_DISK); + if(LOG_DETAILS & (LOG_DETAIL_REG_IO | LOG_DETAIL_IRQ)) + { + dbg_log(this.current_interface.name + ": write Device Control register: " + + h(data, 2) + " interrupts " + ((data & ATA_CR_nIEN) ? "disabled" : "enabled"), LOG_DISK); + } if(data & ATA_CR_SRST) { - dbg_log(this.current_interface.name + ": soft reset via control port", LOG_DISK); - dbg_log(this.current_interface.name + ": lower IRQ " + this.irq, LOG_DISK); + dbg_log(`${this.current_interface.name}: soft reset via control port (lower IRQ ${this.irq})`, LOG_DISK); this.cpu.device_lower_irq(this.irq); this.master.device_reset(); this.slave.device_reset(); @@ -502,25 +600,37 @@ IDEChannel.prototype.write_control = function(data) IDEChannel.prototype.dma_read_addr = function() { - //dbg_log(this.current_interface.name + ": DMA get address: " + h(this.prdt_addr, 8), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW_DMA) + { + dbg_log(this.current_interface.name + ": DMA get address: " + h(this.prdt_addr, 8), LOG_DISK); + } return this.prdt_addr; }; IDEChannel.prototype.dma_set_addr = function(data) { - //dbg_log(this.current_interface.name + ": DMA set address: " + h(data, 8), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW_DMA) + { + dbg_log(this.current_interface.name + ": DMA set address: " + h(data, 8), LOG_DISK); + } this.prdt_addr = data; }; IDEChannel.prototype.dma_read_status = function() { - dbg_log(this.current_interface.name + ": DMA read status: " + h(this.dma_status), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW_DMA) + { + dbg_log(this.current_interface.name + ": DMA read status: " + h(this.dma_status), LOG_DISK); + } return this.dma_status; }; IDEChannel.prototype.dma_write_status = function(value) { - dbg_log(this.current_interface.name + ": DMA write status: " + h(value), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW_DMA) + { + dbg_log(this.current_interface.name + ": DMA write status: " + h(value), LOG_DISK); + } this.dma_status &= ~(value & 6); }; @@ -531,20 +641,29 @@ IDEChannel.prototype.dma_read_command = function() IDEChannel.prototype.dma_read_command8 = function() { - //dbg_log(this.current_interface.name + ": DMA read command: " + h(this.dma_command), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW_DMA) + { + dbg_log(this.current_interface.name + ": DMA read command: " + h(this.dma_command), LOG_DISK); + } return this.dma_command; }; IDEChannel.prototype.dma_write_command = function(value) { - //dbg_log(this.current_interface.name + ": DMA write command: " + h(value), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW_DMA) + { + dbg_log(this.current_interface.name + ": DMA write command: " + h(value), LOG_DISK); + } this.dma_write_command8(value & 0xFF); this.dma_write_status(value >> 16 & 0xFF); }; IDEChannel.prototype.dma_write_command8 = function(value) { - //dbg_log(this.current_interface.name + ": DMA write command8: " + h(value), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW_DMA) + { + dbg_log(this.current_interface.name + ": DMA write command8: " + h(value), LOG_DISK); + } const old_command = this.dma_command; this.dma_command = value & 0x09; @@ -590,7 +709,10 @@ IDEChannel.prototype.push_irq = function() { if((this.device_control_reg & ATA_CR_nIEN) === 0) { - dbg_log(this.current_interface.name + ": push IRQ " + this.irq, LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_IRQ) + { + dbg_log(this.current_interface.name + ": push IRQ " + this.irq, LOG_DISK); + } this.dma_status |= 4; this.cpu.device_raise_irq(this.irq); } @@ -646,16 +768,10 @@ function IDEInterface(channel, interface_nr, buffer, is_cd) /** @const @type {BusConnector} */ this.bus = channel.bus; - /** - * @const - * @type {number} - */ + /** @const @type {number} */ this.channel_nr = channel.channel_nr; - /** - * @const - * @type {number} - */ + /** @const @type {number} */ this.interface_nr = interface_nr; /** @const @type {CPU} */ @@ -763,6 +879,11 @@ IDEInterface.prototype.init_interface = function() this.set_disk_buffer(this.inital_buffer); delete this.inital_buffer; Object.seal(this); + + if(this.drive_connected) + { + dbg_log(`${this.name}: ${this.is_atapi ? "ATAPI CD-ROM" : "ATA HD"} device ready`, LOG_DISK); + } }; IDEInterface.prototype.eject = function() @@ -803,7 +924,7 @@ IDEInterface.prototype.set_disk_buffer = function(buffer) if(this.sector_count !== (this.sector_count | 0)) { - dbg_log(this.name + ": Warning: disk size not aligned with sector size", LOG_DISK); + dbg_log(this.name + ": warning: disk size not aligned with sector size", LOG_DISK); this.sector_count = Math.ceil(this.sector_count); } @@ -825,7 +946,7 @@ IDEInterface.prototype.set_disk_buffer = function(buffer) if(this.cylinder_count !== (this.cylinder_count | 0)) { - dbg_log(this.name + ": Warning: rounding up cylinder count, choose different head number", LOG_DISK); + dbg_log(this.name + ": warning: rounding up cylinder count, choose different head number", LOG_DISK); this.cylinder_count = Math.floor(this.cylinder_count); } @@ -892,29 +1013,36 @@ IDEInterface.prototype.push_irq = function() IDEInterface.prototype.ata_abort_command = function() { - dbg_log(this.name + ": ATA Command " + h(this.current_command) + " aborted", LOG_DISK); this.error_reg = ATA_ER_ABRT; this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; this.push_irq(); }; +IDEInterface.prototype.capture_regs = function() +{ + return `ST=${h(this.status_reg)} ER=${h(this.error_reg)} ` + + `SC=${h(this.sector_count_reg)} LL=${h(this.lba_low_reg)} ` + + `LM=${h(this.lba_mid_reg)} LH=${h(this.lba_high_reg)} ` + + `FE=${h(this.features_reg)} DE=${h(this.device_reg)}`; +}; + IDEInterface.prototype.ata_command = function(cmd) { - dbg_log(this.name + ": ATA Command: " + h(cmd), LOG_DISK); - if(!this.drive_connected && cmd !== ATA_CMD_EXECUTE_DEVICE_DIAGNOSTIC) { - dbg_log(this.name + ": ATA command " + h(cmd) + " ignored: No slave drive connected", LOG_DISK); + dbg_log(`${this.name}: ATA command ${ATA_CMD_NAME[cmd]} (${h(cmd)}) ignored: no slave drive connected`, LOG_DISK); return; } + const regs_pre = DEBUG ? this.capture_regs() : undefined; + let do_dbg_log = DEBUG; + this.current_command = cmd; this.error_reg = 0; switch(cmd) { case ATA_CMD_DEVICE_RESET: - dbg_log(this.name + ": ATA device reset", LOG_DISK); this.data_pointer = 0; this.data_end = 0; this.data_length = 0; @@ -923,14 +1051,12 @@ IDEInterface.prototype.ata_command = function(cmd) break; case ATA_CMD_10h: - dbg_log(this.name + ": ATA calibrate drive", LOG_DISK); this.lba_mid_reg = 0; this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; case ATA_CMD_READ_NATIVE_MAX_ADDRESS: - dbg_log(this.name + ": ATA read native max address", LOG_DISK); var last_sector = this.sector_count - 1; this.lba_low_reg = last_sector & 0xFF; this.lba_mid_reg = last_sector >> 8 & 0xFF; @@ -941,7 +1067,6 @@ IDEInterface.prototype.ata_command = function(cmd) break; case ATA_CMD_READ_NATIVE_MAX_ADDRESS_EXT: - dbg_log(this.name + ": ATA read native max address ext", LOG_DISK); var last_sector = this.sector_count - 1; if(this.channel.device_control_reg & ATA_CR_HOB === 0) { @@ -963,6 +1088,7 @@ IDEInterface.prototype.ata_command = function(cmd) case ATA_CMD_READ_SECTORS_EXT: case ATA_CMD_READ_MULTIPLE: case ATA_CMD_READ_MULTIPLE_EXT: + do_dbg_log = LOG_DETAILS & LOG_DETAIL_RW; if(this.is_atapi) { this.ata_abort_command(); @@ -977,6 +1103,7 @@ IDEInterface.prototype.ata_command = function(cmd) case ATA_CMD_WRITE_SECTORS_EXT: case ATA_CMD_WRITE_MULTIPLE: case ATA_CMD_WRITE_MULTIPLE_EXT: + do_dbg_log = LOG_DETAILS & LOG_DETAIL_RW; if(this.is_atapi) { this.ata_abort_command(); @@ -988,7 +1115,6 @@ IDEInterface.prototype.ata_command = function(cmd) break; case ATA_CMD_EXECUTE_DEVICE_DIAGNOSTIC: - dbg_log(this.name + ": ATA execute device diagnostic", LOG_DISK); // the behaviour of this command is independent of the selected device this.channel.master.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.channel.master.error_reg = 0x01; // Master drive passed, slave drive passed or not present @@ -1009,16 +1135,20 @@ IDEInterface.prototype.ata_command = function(cmd) case ATA_CMD_PACKET: if(this.is_atapi) { + do_dbg_log = false; this.data_allocate(12); this.data_end = 12; this.sector_count_reg = 0x01; // 0x01: indicates transfer of a command packet (C/D) this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; // 0x10: another command is ready to be serviced (SERV) this.push_irq(); } + else + { + this.ata_abort_command(); + } break; case ATA_CMD_IDENTIFY_PACKET_DEVICE: - dbg_log(this.name + ": ATA identify packet device", LOG_DISK); if(this.is_atapi) { this.create_identify_packet(); @@ -1032,7 +1162,6 @@ IDEInterface.prototype.ata_command = function(cmd) break; case ATA_CMD_SET_MULTIPLE_MODE: - dbg_log(this.name + ": ATA set multiple mode", LOG_DISK); // Logical sectors per DRQ Block in word 1 dbg_log(this.name + ": logical sectors per DRQ Block: " + h(this.sector_count_reg & 0xFF), LOG_DISK); this.sectors_per_drq = this.sector_count_reg & 0xFF; @@ -1042,23 +1171,23 @@ IDEInterface.prototype.ata_command = function(cmd) case ATA_CMD_READ_DMA: case ATA_CMD_READ_DMA_EXT: + do_dbg_log = LOG_DETAILS & LOG_DETAIL_RW_DMA; this.ata_read_sectors_dma(cmd); break; case ATA_CMD_WRITE_DMA: case ATA_CMD_WRITE_DMA_EXT: + do_dbg_log = LOG_DETAILS & LOG_DETAIL_RW_DMA; this.ata_write_sectors_dma(cmd); break; case ATA_CMD_READ_VERIFY_SECTORS: - dbg_log(this.name + ": ATA read verify sector(s)", LOG_DISK); // TODO: check that lba_low/mid/high and sector_count regs are within the bounds of the disk's size this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; case ATA_CMD_GET_MEDIA_STATUS: - dbg_log(this.name + ": ATA get media status", LOG_DISK); if(this.is_atapi) { if(!this.buffer) @@ -1077,31 +1206,26 @@ IDEInterface.prototype.ata_command = function(cmd) break; case ATA_CMD_STANDBY_IMMEDIATE: - dbg_log(this.name + ": ATA standby immediate", LOG_DISK); this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; case ATA_CMD_IDLE_IMMEDIATE: - dbg_log(this.name + ": ATA idle immediate", LOG_DISK); this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; case ATA_CMD_FLUSH_CACHE: - dbg_log(this.name + ": ATA flush cache", LOG_DISK); this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; case ATA_CMD_FLUSH_CACHE_EXT: - dbg_log(this.name + ": ATA flush cache ext", LOG_DISK); this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; case ATA_CMD_IDENTIFY_DEVICE: - dbg_log(this.name + ": ATA identify device", LOG_DISK); if(this.is_atapi) { this.ata_abort_command(); @@ -1115,68 +1239,77 @@ IDEInterface.prototype.ata_command = function(cmd) break; case ATA_CMD_SET_FEATURES: - dbg_log(this.name + ": ATA set features: " + h(this.sector_count_reg & 0xFF), LOG_DISK); this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; case ATA_CMD_MEDIA_LOCK: - dbg_log(this.name + ": ATA media lock", LOG_DISK); this.status_reg = ATA_SR_DRDY; this.push_irq(); break; case ATA_CMD_SECURITY_FREEZE_LOCK: - dbg_log(this.name + ": ATA security freeze lock", LOG_DISK); this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; case ATA_CMD_SET_MAX: - dbg_log(this.name + ": ATA set max address (unimplemented)", LOG_DISK); this.ata_abort_command(); break; case ATA_CMD_NOP: - dbg_log(this.name + ": ATA nop", LOG_DISK); this.ata_abort_command(); break; default: - dbg_assert(false, this.name + ": unhandled ATA command: " + h(cmd), LOG_DISK); + dbg_assert(false, `${this.name}: error: unimplemented ATA command ${h(cmd)}: ABORT [${this.capture_regs()}]`, LOG_DISK); this.ata_abort_command(); break; } + + if(DEBUG && do_dbg_log) + { + const regs_msg = `[${regs_pre}] -> [${this.capture_regs()}]`; + const result = this.status_reg & ATA_SR_ERR ? (this.error_reg & ATA_ER_ABRT ? "ABORT" : "ERROR") : "OK"; + dbg_log(`${this.name}: ATA command ${ATA_CMD_NAME[cmd]} (${h(cmd)}): ${result} ${regs_msg}`, LOG_DISK); + } }; IDEInterface.prototype.atapi_handle = function() { - dbg_log(this.name + ": ATAPI Command: " + h(this.data[0]), LOG_DISK); - this.data_pointer = 0; - this.current_atapi_command = this.data[0]; + const cmd = this.data[0]; + const regs_pre = DEBUG ? this.capture_regs() : undefined; + let do_dbg_log = DEBUG; + let dbg_log_extra; - if(this.current_atapi_command !== ATAPI_CMD_REQUEST_SENSE) + this.data_pointer = 0; + this.current_atapi_command = cmd; + + if(cmd !== ATAPI_CMD_REQUEST_SENSE) { this.atapi_sense_key = 0; this.atapi_add_sense = 0; } - if(!this.buffer && (this.current_atapi_command === ATAPI_CMD_READ_CAPACITY || - this.current_atapi_command === ATAPI_CMD_READ || - this.current_atapi_command === ATAPI_CMD_READ_SUBCHANNEL || - this.current_atapi_command === ATAPI_CMD_READ_TOC_PMA_ATIP || - this.current_atapi_command === ATAPI_CMD_READ_DISK_INFORMATION)) + if(!this.buffer && (cmd === ATAPI_CMD_READ_CAPACITY || + cmd === ATAPI_CMD_READ || + cmd === ATAPI_CMD_READ_SUBCHANNEL || + cmd === ATAPI_CMD_READ_TOC_PMA_ATIP || + cmd === ATAPI_CMD_READ_DISK_INFORMATION)) { - dbg_log(this.name + ": CD read-related action: no buffer", LOG_DISK); this.atapi_check_condition_response(ATAPI_SK_NOT_READY, ATAPI_ASC_MEDIUM_NOT_PRESENT); this.push_irq(); + if(DEBUG) + { + const result = this.status_reg & ATA_SR_ERR ? (this.error_reg & ATA_ER_ABRT ? "ABORT" : "ERROR") : "OK"; + dbg_log(`${this.name}: ATAPI command ${ATAPI_CMD_NAME[cmd]} (${h(cmd)}) without medium: ${result} [${regs_pre}]`, LOG_DISK); + } return; } - switch(this.current_atapi_command) + switch(cmd) { case ATAPI_CMD_TEST_UNIT_READY: - dbg_log(this.name + ": ATAPI test unit ready", LOG_DISK); if(this.buffer) { this.data_allocate(0); @@ -1190,7 +1323,6 @@ IDEInterface.prototype.atapi_handle = function() break; case ATAPI_CMD_REQUEST_SENSE: - dbg_log(this.name + ": ATAPI request sense", LOG_DISK); this.data_allocate(this.data[4]); this.data_end = this.data_length; this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; @@ -1205,7 +1337,7 @@ IDEInterface.prototype.atapi_handle = function() case ATAPI_CMD_INQUIRY: var length = this.data[4]; this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; - dbg_log(this.name + ": ATAPI inquiry: " + h(this.data[1], 2) + " length=" + length, LOG_DISK); + dbg_log_extra = h(this.data[1], 2) + " length=" + length; // for data layout see [CD-SCSI-2] "INQUIRY Command" this.data.set([ // 0: Device-type, Removable, ANSI-Version, Response Format @@ -1227,21 +1359,18 @@ IDEInterface.prototype.atapi_handle = function() break; case ATAPI_CMD_MODE_SENSE_6: - dbg_log(this.name + ": ATAPI mode sense (6)", LOG_DISK); this.data_allocate(this.data[4]); this.data_end = this.data_length; this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; break; case ATAPI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: - dbg_log(this.name + ": ATAPI prevent allow medium removal", LOG_DISK); this.data_allocate(0); this.data_end = this.data_length; this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; break; case ATAPI_CMD_READ_CAPACITY: - dbg_log(this.name + ": ATAPI read capacity", LOG_DISK); var count = this.sector_count - 1; this.data_set(new Uint8Array([ count >> 24 & 0xFF, @@ -1260,17 +1389,19 @@ IDEInterface.prototype.atapi_handle = function() case ATAPI_CMD_READ: if(this.features_reg & 1) { + do_dbg_log = LOG_DETAILS & LOG_DETAIL_RW_DMA; this.atapi_read_dma(this.data); } else { + do_dbg_log = LOG_DETAILS & LOG_DETAIL_RW; this.atapi_read(this.data); } break; case ATAPI_CMD_READ_SUBCHANNEL: var length = this.data[8]; - dbg_log(this.name + ": ATAPI read subchannel: length=" + length, LOG_DISK); + dbg_log_extra = "length=" + length; this.data_allocate(Math.min(8, length)); this.data_end = this.data_length; this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; @@ -1279,14 +1410,10 @@ IDEInterface.prototype.atapi_handle = function() case ATAPI_CMD_READ_TOC_PMA_ATIP: var length = this.data[8] | this.data[7] << 8; var format = this.data[9] >> 6; + dbg_log_extra = `${h(format, 2)} length=${length} ${!!(this.data[1] & 2)} ${h(this.data[6])}`; this.data_allocate(length); this.data_end = this.data_length; - dbg_log(this.name + ": ATAPI read toc/pma/atip: " + h(format, 2) + - " length=" + length + - " " + (this.data[1] & 2) + - " " + h(this.data[6]), LOG_DISK); - if(format === 0) { const sector_count = this.sector_count; @@ -1323,7 +1450,7 @@ IDEInterface.prototype.atapi_handle = function() } else { - dbg_assert(false, this.name + ": unimplemented format: " + format); + dbg_assert(false, this.name + ": error: unimplemented format: " + format); } this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; @@ -1331,7 +1458,7 @@ IDEInterface.prototype.atapi_handle = function() case ATAPI_CMD_GET_CONFIGURATION: var length = Math.min(this.data[8] | this.data[7] << 8, 32); - dbg_log(this.name + ": ATAPI get configuration: length=" + length, LOG_DISK); + dbg_log_extra = "length=" + length; this.data_allocate(length); this.data_end = this.data_length; this.data[0] = length - 4 >> 24 & 0xFF; @@ -1344,21 +1471,20 @@ IDEInterface.prototype.atapi_handle = function() break; case ATAPI_CMD_READ_DISK_INFORMATION: - dbg_log(this.name + ": ATAPI read disk information", LOG_DISK); this.data_allocate(0); this.data_end = this.data_length; this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; break; case ATAPI_CMD_READ_TRACK_INFORMATION: - dbg_log(this.name + ": ATAPI read track information (unimplemented)", LOG_DISK); + dbg_log_extra = "unimplemented"; this.atapi_check_condition_response(ATAPI_SK_ILLEGAL_REQUEST, ATAPI_ASC_INV_FIELD_IN_CMD_PACKET); break; case ATAPI_CMD_MODE_SENSE_10: var length = this.data[8] | this.data[7] << 8; var page_code = this.data[2]; - dbg_log(this.name + ": ATAPI mode sense (10): page_code=" + h(page_code) + " length=" + length, LOG_DISK); + dbg_log_extra = "page_code=" + h(page_code) + " length=" + length; if(page_code === 0x2A) { this.data_allocate(Math.min(30, length)); @@ -1368,7 +1494,6 @@ IDEInterface.prototype.atapi_handle = function() break; case ATAPI_CMD_MECHANISM_STATUS: - dbg_log(this.name + ": ATAPI mechanism status", LOG_DISK); this.data_allocate(this.data[9] | this.data[8] << 8); this.data_end = this.data_length; this.data[5] = 1; @@ -1376,21 +1501,21 @@ IDEInterface.prototype.atapi_handle = function() break; case ATAPI_CMD_GET_EVENT_STATUS_NOTIFICATION: - dbg_log(this.name + ": ATAPI get event status notification (unimplemented)", LOG_DISK); + dbg_log_extra = "unimplemented"; this.atapi_check_condition_response(ATAPI_SK_ILLEGAL_REQUEST, ATAPI_ASC_INV_FIELD_IN_CMD_PACKET); break; case ATAPI_CMD_READ_CD: - dbg_log(this.name + ": ATAPI read cd (unimplemented)", LOG_DISK); + dbg_log_extra = "unimplemented"; this.data_allocate(0); this.data_end = this.data_length; this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; break; default: + dbg_assert(false, `${this.name}: error: unimplemented ATAPI command ${h(this.data[0])}`, LOG_DISK); this.atapi_check_condition_response(ATAPI_SK_ILLEGAL_REQUEST, ATAPI_ASC_INV_FIELD_IN_CMD_PACKET); - dbg_log(this.name + ": unimplemented ATAPI command: " + h(this.data[0]), LOG_DISK); - dbg_assert(false); + break; } this.sector_count_reg = this.sector_count_reg & ~7 | 2; @@ -1405,6 +1530,14 @@ IDEInterface.prototype.atapi_handle = function() this.sector_count_reg |= 1; this.status_reg &= ~ATA_SR_DRQ; } + + if(DEBUG && do_dbg_log) + { + const regs_msg = `[${regs_pre}] -> [${this.capture_regs()}]`; + const result = this.status_reg & ATA_SR_ERR ? (this.error_reg & ATA_ER_ABRT ? "ABORT" : "ERROR") : "OK"; + dbg_log_extra = dbg_log_extra ? ` ${dbg_log_extra}:` : ""; + dbg_log(`${this.name}: ATAPI command ${ATAPI_CMD_NAME[cmd]} (${h(cmd)}):${dbg_log_extra} ${result} ${regs_msg}`, LOG_DISK); + } }; IDEInterface.prototype.atapi_check_condition_response = function(sense_key, additional_sense) @@ -1499,7 +1632,6 @@ IDEInterface.prototype.atapi_read = function(cmd) this.read_buffer(start, byte_count, (data) => { - //setTimeout(() => { dbg_log(this.name + ": CD read: data arrived", LOG_DISK); this.data_set(data); this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; @@ -1518,7 +1650,6 @@ IDEInterface.prototype.atapi_read = function(cmd) this.lba_high_reg = this.data_end >> 8 & 0xFF; this.report_read_end(byte_count); - //}, 10); }); } }; @@ -1577,7 +1708,10 @@ IDEInterface.prototype.do_atapi_dma = function() return; } - dbg_log(this.name + ": ATAPI DMA transfer len=" + this.data_length, LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW_DMA) + { + dbg_log(this.name + ": ATAPI DMA transfer len=" + this.data_length, LOG_DISK); + } var prdt_start = this.channel.prdt_addr; var offset = 0; @@ -1594,7 +1728,10 @@ IDEInterface.prototype.do_atapi_dma = function() count = 0x10000; } - dbg_log(this.name + ": DMA read dest=" + h(addr) + " count=" + h(count) + " datalen=" + h(this.data_length), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW_DMA) + { + dbg_log(this.name + ": DMA read dest=" + h(addr) + " count=" + h(count) + " datalen=" + h(this.data_length), LOG_DISK); + } this.cpu.write_blob(data.subarray(offset, Math.min(offset + count, this.data_length)), addr); offset += count; @@ -1602,16 +1739,22 @@ IDEInterface.prototype.do_atapi_dma = function() if(offset >= this.data_length && !end) { - dbg_log(this.name + ": leave early end=" + (+end) + - " offset=" + h(offset) + - " data_length=" + h(this.data_length) + - " cmd=" + h(this.current_command), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW_DMA) + { + dbg_log(this.name + ": leave early end=" + (+end) + + " offset=" + h(offset) + + " data_length=" + h(this.data_length) + + " cmd=" + h(this.current_command), LOG_DISK); + } break; } } while(!end); - dbg_log(this.name + ": end offset=" + offset, LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW_DMA) + { + dbg_log(this.name + ": end offset=" + offset, LOG_DISK); + } this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.channel.dma_status &= ~1; @@ -1642,11 +1785,15 @@ IDEInterface.prototype.read_data = function(length) this.data_pointer += length; var align = (this.data_end & 0xFFF) === 0 ? 0xFFF : 0xFF; - if((this.data_pointer & align) === 0) + + if(LOG_DETAILS & LOG_DETAIL_RW) { - dbg_log(this.name + ": read 1F0: " + h(this.data[this.data_pointer], 2) + - " cur=" + h(this.data_pointer) + - " cnt=" + h(this.data_length), LOG_DISK); + if((this.data_pointer & align) === 0) + { + dbg_log(this.name + ": read 1F0: " + h(this.data[this.data_pointer], 2) + + " cur=" + h(this.data_pointer) + + " cnt=" + h(this.data_length), LOG_DISK); + } } if(this.data_pointer >= this.data_end) @@ -1658,8 +1805,10 @@ IDEInterface.prototype.read_data = function(length) } else { - dbg_log(this.name + ": read 1F0: empty", LOG_DISK); - + if(LOG_DETAILS & LOG_DETAIL_RW) + { + dbg_log(this.name + ": read 1F0: empty", LOG_DISK); + } this.data_pointer += length; return 0; } @@ -1667,9 +1816,12 @@ IDEInterface.prototype.read_data = function(length) IDEInterface.prototype.read_end = function() { - dbg_log(this.name + ": read_end cmd=" + h(this.current_command) + - " data_pointer=" + h(this.data_pointer) + " end=" + h(this.data_end) + - " length=" + h(this.data_length), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW) + { + dbg_log(this.name + ": read_end cmd=" + h(this.current_command) + + " data_pointer=" + h(this.data_pointer) + " end=" + h(this.data_end) + + " length=" + h(this.data_length), LOG_DISK); + } if(this.current_command === ATA_CMD_PACKET) { @@ -1696,7 +1848,10 @@ IDEInterface.prototype.read_end = function() { this.data_end += byte_count; } - dbg_log(this.name + ": data_end=" + h(this.data_end), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW) + { + dbg_log(this.name + ": data_end=" + h(this.data_end), LOG_DISK); + } } } else @@ -1739,10 +1894,13 @@ IDEInterface.prototype.write_data_port = function(data, length) else { var align = (this.data_end & 0xFFF) === 0 ? 0xFFF : 0xFF; - if((this.data_pointer + length & align) === 0 || this.data_end < 20) + if(LOG_DETAILS & LOG_DETAIL_RW) { - dbg_log(this.name + ": data port: " + h(data >>> 0) + " count=" + - h(this.data_end) + " cur=" + h(this.data_pointer), LOG_DISK); + if((this.data_pointer + length & align) === 0 || this.data_end < 20) + { + dbg_log(this.name + ": data port: " + h(data >>> 0) + " count=" + + h(this.data_end) + " cur=" + h(this.data_pointer), LOG_DISK); + } } if(length === 1) @@ -1791,8 +1949,11 @@ IDEInterface.prototype.write_end = function() } else { - dbg_log(this.name + ": write_end data_pointer=" + h(this.data_pointer) + + if(LOG_DETAILS & LOG_DETAIL_RW) + { + dbg_log(this.name + ": write_end data_pointer=" + h(this.data_pointer) + " data_length=" + h(this.data_length), LOG_DISK); + } if(this.data_pointer >= this.data_length) { @@ -1816,7 +1977,10 @@ IDEInterface.prototype.write_end = function() IDEInterface.prototype.ata_advance = function(cmd, sectors) { - dbg_log(this.name + ": advance sectors=" + sectors + " old_sector_count_reg=" + this.sector_count_reg, LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW) + { + dbg_log(this.name + ": advance sectors=" + sectors + " old_sector_count_reg=" + this.sector_count_reg, LOG_DISK); + } this.sector_count_reg -= sectors; if(cmd === ATA_CMD_READ_SECTORS_EXT || @@ -1864,11 +2028,14 @@ IDEInterface.prototype.ata_read_sectors = function(cmd) var byte_count = count * this.sector_size; var start = lba * this.sector_size; - dbg_log(this.name + ": ATA read cmd=" + h(cmd) + - " mode=" + (this.is_lba ? "lba" : "chs") + - " lba=" + h(lba) + - " lbacount=" + h(count) + - " bytecount=" + h(byte_count), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW) + { + dbg_log(this.name + ": ATA read cmd=" + h(cmd) + + " mode=" + (this.is_lba ? "lba" : "chs") + + " lba=" + h(lba) + + " lbacount=" + h(count) + + " bytecount=" + h(byte_count), LOG_DISK); + } if(start + byte_count > this.buffer.byteLength) { @@ -1884,8 +2051,10 @@ IDEInterface.prototype.ata_read_sectors = function(cmd) this.read_buffer(start, byte_count, (data) => { - //setTimeout(() => { - dbg_log(this.name + ": ata_read: Data arrived", LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW) + { + dbg_log(this.name + ": ata_read: Data arrived", LOG_DISK); + } this.data_set(data); this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; @@ -1894,7 +2063,6 @@ IDEInterface.prototype.ata_read_sectors = function(cmd) this.push_irq(); this.report_read_end(byte_count); - //}, 10); }); } }; @@ -1908,9 +2076,12 @@ IDEInterface.prototype.ata_read_sectors_dma = function(cmd) var byte_count = count * this.sector_size; var start = lba * this.sector_size; - dbg_log(this.name + ": ATA DMA read lba=" + h(lba) + - " lbacount=" + h(count) + - " bytecount=" + h(byte_count), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW_DMA) + { + dbg_log(this.name + ": ATA DMA read lba=" + h(lba) + + " lbacount=" + h(count) + + " bytecount=" + h(byte_count), LOG_DISK); + } if(start + byte_count > this.buffer.byteLength) { @@ -1944,8 +2115,10 @@ IDEInterface.prototype.do_ata_read_sectors_dma = function() this.read_buffer(start, byte_count, (data) => { - //setTimeout(function() { - dbg_log(this.name + ": do_ata_read_sectors_dma: Data arrived", LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW_DMA) + { + dbg_log(this.name + ": do_ata_read_sectors_dma: Data arrived", LOG_DISK); + } var prdt_start = this.channel.prdt_addr; var offset = 0; @@ -1959,11 +2132,17 @@ IDEInterface.prototype.do_ata_read_sectors_dma = function() if(!prd_count) { prd_count = 0x10000; - dbg_log(this.name + ": DMA: prd count was 0", LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW_DMA) + { + dbg_log(this.name + ": DMA: prd count was 0", LOG_DISK); + } } - dbg_log(this.name + ": DMA read transfer dest=" + h(prd_addr) + + if(LOG_DETAILS & LOG_DETAIL_RW_DMA) + { + dbg_log(this.name + ": DMA read transfer dest=" + h(prd_addr) + " prd_count=" + h(prd_count), LOG_DISK); + } this.cpu.write_blob(data.subarray(offset, offset + prd_count), prd_addr); offset += prd_count; @@ -1995,10 +2174,13 @@ IDEInterface.prototype.ata_write_sectors = function(cmd) var byte_count = count * this.sector_size; var start = lba * this.sector_size; - dbg_log(this.name + ": ATA write lba=" + h(lba) + - " mode=" + (this.is_lba ? "lba" : "chs") + - " lbacount=" + h(count) + - " bytecount=" + h(byte_count), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW) + { + dbg_log(this.name + ": ATA write lba=" + h(lba) + + " mode=" + (this.is_lba ? "lba" : "chs") + + " lbacount=" + h(count) + + " bytecount=" + h(byte_count), LOG_DISK); + } if(start + byte_count > this.buffer.byteLength) { @@ -2025,9 +2207,12 @@ IDEInterface.prototype.ata_write_sectors_dma = function(cmd) var byte_count = count * this.sector_size; var start = lba * this.sector_size; - dbg_log(this.name + ": ATA DMA write lba=" + h(lba) + - " lbacount=" + h(count) + - " bytecount=" + h(byte_count), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW_DMA) + { + dbg_log(this.name + ": ATA DMA write lba=" + h(lba) + + " lbacount=" + h(count) + + " bytecount=" + h(byte_count), LOG_DISK); + } if(start + byte_count > this.buffer.byteLength) { @@ -2056,7 +2241,10 @@ IDEInterface.prototype.do_ata_write_sectors_dma = function() var prdt_start = this.channel.prdt_addr; var offset = 0; - dbg_log(this.name + ": prdt addr: " + h(prdt_start, 8), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW_DMA) + { + dbg_log(this.name + ": prdt addr: " + h(prdt_start, 8), LOG_DISK); + } const buffer = new Uint8Array(byte_count); @@ -2071,7 +2259,10 @@ IDEInterface.prototype.do_ata_write_sectors_dma = function() dbg_log(this.name + ": DMA: prd count was 0", LOG_DISK); } - dbg_log(this.name + ": DMA write transfer dest=" + h(prd_addr) + " prd_count=" + h(prd_count), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW_DMA) + { + dbg_log(this.name + ": DMA write transfer dest=" + h(prd_addr) + " prd_count=" + h(prd_count), LOG_DISK); + } var slice = this.cpu.mem8.subarray(prd_addr, prd_addr + prd_count); dbg_assert(slice.length === prd_count); @@ -2092,7 +2283,10 @@ IDEInterface.prototype.do_ata_write_sectors_dma = function() this.buffer.set(start, buffer, () => { - dbg_log(this.name + ": DMA write completed", LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW_DMA) + { + dbg_log(this.name + ": DMA write completed", LOG_DISK); + } this.ata_advance(this.current_command, count); this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); From b71ebced7dcd7821e8e23c174fc9b7415b472003 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sun, 25 May 2025 07:36:19 +0200 Subject: [PATCH 104/301] reduced debug log output some more --- src/ide.js | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/ide.js b/src/ide.js index c3e4c5a1..35309b0d 100644 --- a/src/ide.js +++ b/src/ide.js @@ -509,12 +509,12 @@ function IDEChannel(controller, channel_nr, channel_config, hw_settings) { if(select_slave) { - dbg_log(this.current_interface.name + ": select slave device", LOG_DISK); + dbg_log(`${this.current_interface.name}: select slave device (${this.channel_nr ? "secondary" : "primary"})`, LOG_DISK); this.current_interface = this.slave; } else { - dbg_log(this.current_interface.name + ": select master device", LOG_DISK); + dbg_log(`${this.current_interface.name}: select master device (${this.channel_nr ? "secondary" : "primary"})`, LOG_DISK); this.current_interface = this.master; } } @@ -1088,7 +1088,7 @@ IDEInterface.prototype.ata_command = function(cmd) case ATA_CMD_READ_SECTORS_EXT: case ATA_CMD_READ_MULTIPLE: case ATA_CMD_READ_MULTIPLE_EXT: - do_dbg_log = LOG_DETAILS & LOG_DETAIL_RW; + do_dbg_log = false; if(this.is_atapi) { this.ata_abort_command(); @@ -1103,7 +1103,7 @@ IDEInterface.prototype.ata_command = function(cmd) case ATA_CMD_WRITE_SECTORS_EXT: case ATA_CMD_WRITE_MULTIPLE: case ATA_CMD_WRITE_MULTIPLE_EXT: - do_dbg_log = LOG_DETAILS & LOG_DETAIL_RW; + do_dbg_log = false; if(this.is_atapi) { this.ata_abort_command(); @@ -1171,13 +1171,13 @@ IDEInterface.prototype.ata_command = function(cmd) case ATA_CMD_READ_DMA: case ATA_CMD_READ_DMA_EXT: - do_dbg_log = LOG_DETAILS & LOG_DETAIL_RW_DMA; + do_dbg_log = false; this.ata_read_sectors_dma(cmd); break; case ATA_CMD_WRITE_DMA: case ATA_CMD_WRITE_DMA_EXT: - do_dbg_log = LOG_DETAILS & LOG_DETAIL_RW_DMA; + do_dbg_log = false; this.ata_write_sectors_dma(cmd); break; @@ -1337,13 +1337,13 @@ IDEInterface.prototype.atapi_handle = function() case ATAPI_CMD_INQUIRY: var length = this.data[4]; this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; - dbg_log_extra = h(this.data[1], 2) + " length=" + length; + dbg_log_extra = "lun=" + h(this.data[1], 2) + " length=" + length; // for data layout see [CD-SCSI-2] "INQUIRY Command" this.data.set([ // 0: Device-type, Removable, ANSI-Version, Response Format 0x05, 0x80, 0x01, 0x31, // 4: Additional length, Reserved, Reserved, Reserved - 0x31, 0, 0, 0, + 31, 0, 0, 0, // 8: Vendor Identification "SONY " 0x53, 0x4F, 0x4E, 0x59, 0x20, 0x20, 0x20, 0x20, @@ -1387,14 +1387,13 @@ IDEInterface.prototype.atapi_handle = function() break; case ATAPI_CMD_READ: + do_dbg_log = false; if(this.features_reg & 1) { - do_dbg_log = LOG_DETAILS & LOG_DETAIL_RW_DMA; this.atapi_read_dma(this.data); } else { - do_dbg_log = LOG_DETAILS & LOG_DETAIL_RW; this.atapi_read(this.data); } break; @@ -1584,14 +1583,17 @@ IDEInterface.prototype.atapi_read = function(cmd) var byte_count = count * this.sector_size; var start = lba * this.sector_size; - dbg_log(this.name + ": CD read lba=" + h(lba) + - " lbacount=" + h(count) + - " bytecount=" + h(byte_count) + - " flags=" + h(flags), LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW) + { + dbg_log(this.name + ": CD read lba=" + h(lba) + + " lbacount=" + h(count) + + " bytecount=" + h(byte_count) + + " flags=" + h(flags), LOG_DISK); + } this.data_length = 0; var req_length = this.lba_high_reg << 8 & 0xFF00 | this.lba_mid_reg & 0xFF; - dbg_log(this.name + ": " + h(this.lba_high_reg, 2) + " " + h(this.lba_mid_reg, 2), LOG_DISK); + //dbg_log(this.name + ": " + h(this.lba_high_reg, 2) + " " + h(this.lba_mid_reg, 2), LOG_DISK); this.lba_mid_reg = this.lba_high_reg = 0; // oak technology driver (windows 3.0) if(req_length === 0xFFFF) @@ -1632,7 +1634,10 @@ IDEInterface.prototype.atapi_read = function(cmd) this.read_buffer(start, byte_count, (data) => { - dbg_log(this.name + ": CD read: data arrived", LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_RW) + { + dbg_log(this.name + ": CD read: data arrived", LOG_DISK); + } this.data_set(data); this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; this.sector_count_reg = this.sector_count_reg & ~7 | 2; From b1e45723df30f9e58117b026ade12ea1795897c7 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sun, 25 May 2025 11:00:17 +0200 Subject: [PATCH 105/301] removed overly verbose I/O BAR debug output --- src/pci.js | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/src/pci.js b/src/pci.js index 0f0792a6..6c775fe7 100644 --- a/src/pci.js +++ b/src/pci.js @@ -558,20 +558,6 @@ PCI.prototype.set_io_bars = function(bar, from, to) ports[from + i] = this.io.create_empty_entry(); } - if(old_entry.read8 === this.io.empty_port_read8 && - old_entry.read16 === this.io.empty_port_read16 && - old_entry.read32 === this.io.empty_port_read32 && - old_entry.write8 === this.io.empty_port_write && - old_entry.write16 === this.io.empty_port_write && - old_entry.write32 === this.io.empty_port_write) - { - // happens when a device doesn't register its full range (currently ne2k and virtio) - // but it also happens when aligned reads/writes are set up, - // e.g. a 16-bit read registered at 0xB400 will show up as - // no source mapping at 0xB401. - dbg_log("Warning: Bad IO bar: Source not mapped, port=" + h(from + i, 4), LOG_PCI); - } - var entry = bar.entries[i]; var empty_entry = ports[to + i]; dbg_assert(entry && empty_entry); @@ -580,18 +566,6 @@ PCI.prototype.set_io_bars = function(bar, from, to) { ports[to + i] = entry; } - - if(empty_entry.read8 !== this.io.empty_port_read8 || - empty_entry.read16 !== this.io.empty_port_read16 || - empty_entry.read32 !== this.io.empty_port_read32 || - empty_entry.write8 !== this.io.empty_port_write || - empty_entry.write16 !== this.io.empty_port_write || - empty_entry.write32 !== this.io.empty_port_write) - { - // These can fail if the os maps an io port in multiple bars (indicating a bug) - // XXX: Fails during restore_state - dbg_log("Warning: Bad IO bar: Target already mapped, port=" + h(to + i, 4)+" from device "+entry.device.name+" to device "+empty_entry.device.name, LOG_PCI); - } } }; From 0dff08dfa6f2d598893f73f2357c873ac568a45f Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sun, 25 May 2025 18:44:42 +0200 Subject: [PATCH 106/301] changed CPU.devices.cdrom and .hda to point to their IDEInterface objects - changed cdrom and hda to point to the drive's IDEInterface instead of its parent IDEChannel - added boolean method IDEInterface.has_disk() to query whether a Compact Disk is inserted or not - the IDEController object and the CD-ROM device are now created unconditionally --- src/browser/main.js | 2 +- src/browser/starter.js | 6 +++--- src/cpu.js | 37 ++++++++++++++++--------------------- src/ide.js | 5 +++++ 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/browser/main.js b/src/browser/main.js index 3e9735d1..7f71e74b 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -2398,7 +2398,7 @@ function init_ui(profile, settings, emulator) $("change_cdrom_image").value = settings.cdrom ? "Eject CD image" : "Insert CD image"; $("change_cdrom_image").onclick = function() { - if(emulator.v86.cpu.devices.cdrom.master.buffer) + if(emulator.v86.cpu.devices.cdrom.has_disk()) { emulator.eject_cdrom(); $("change_cdrom_image").value = "Insert CD image"; diff --git a/src/browser/starter.js b/src/browser/starter.js index 7f5c84c7..5d8e27ed 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -995,7 +995,7 @@ V86.prototype.set_cdrom = async function(file) load_file(file.url, { done: result => { - this.v86.cpu.devices.cdrom.master.set_cdrom(new SyncBuffer(result)); + this.v86.cpu.devices.cdrom.set_cdrom(new SyncBuffer(result)); }, }); } @@ -1004,7 +1004,7 @@ V86.prototype.set_cdrom = async function(file) const image = buffer_from_object(file, this.zstd_decompress_worker.bind(this)); image.onload = () => { - this.v86.cpu.devices.cdrom.master.set_cdrom(image); + this.v86.cpu.devices.cdrom.set_cdrom(image); }; await image.load(); } @@ -1015,7 +1015,7 @@ V86.prototype.set_cdrom = async function(file) */ V86.prototype.eject_cdrom = function() { - this.v86.cpu.devices.cdrom.master.eject(); + this.v86.cpu.devices.cdrom.eject(); }; /** diff --git a/src/cpu.js b/src/cpu.js index 329177e4..8eb03bed 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -537,8 +537,8 @@ CPU.prototype.get_state = function() state[53] = this.devices.ps2; state[54] = this.devices.uart0; state[55] = this.devices.fdc; - state[56] = this.devices.cdrom; - state[57] = this.devices.hda; + state[56] = this.devices.cdrom.channel; + state[57] = this.devices.hda? this.devices.hda.channel : undefined; state[58] = this.devices.pit; state[59] = this.devices.net; state[60] = this.get_state_pic(); @@ -692,8 +692,8 @@ CPU.prototype.set_state = function(state) this.devices.ps2 && this.devices.ps2.set_state(state[53]); this.devices.uart0 && this.devices.uart0.set_state(state[54]); this.devices.fdc && this.devices.fdc.set_state(state[55]); - this.devices.cdrom && this.devices.cdrom.set_state(state[56]); - this.devices.hda && this.devices.hda.set_state(state[57]); + this.devices.cdrom && this.devices.cdrom.channel.set_state(state[56]); + this.devices.hda && this.devices.hda.channel.set_state(state[57]); this.devices.pit && this.devices.pit.set_state(state[58]); this.devices.net && this.devices.net.set_state(state[59]); this.set_state_pic(state[60]); @@ -1105,24 +1105,19 @@ CPU.prototype.init = function(settings, device_bus) this.devices.fdc = new FloppyController(this, settings.fda, settings.fdb); - if(settings.hda || settings.cdrom) { - let cdrom_channel = 0; - const ide_config = [[undefined, undefined], [undefined, undefined]]; - if(settings.hda) { - ide_config[0][0] = {buffer: settings.hda}; - ide_config[0][1] = {buffer: settings.hdb}; - cdrom_channel++; - } - ide_config[cdrom_channel][0] = {is_cdrom: true, buffer: settings.cdrom}; - - this.devices.ide = new IDEController(this, device_bus, ide_config); - - if(settings.hda) { - this.devices.hda = this.devices.ide.channels[0]; -// this.devices.hdb = ? // TODO: this.devices.hda/hdb/cdrom should point to IDEInterface, not IDEDevice objects?! - } - this.devices.cdrom = this.devices.ide.channels[cdrom_channel]; + let cdrom_channel = 0; + const ide_config = [[undefined, undefined], [undefined, undefined]]; + if(settings.hda) { + ide_config[0][0] = {buffer: settings.hda}; + ide_config[0][1] = {buffer: settings.hdb}; + cdrom_channel++; } + ide_config[cdrom_channel][0] = {is_cdrom: true, buffer: settings.cdrom}; + this.devices.ide = new IDEController(this, device_bus, ide_config); + if(settings.hda) { + this.devices.hda = this.devices.ide.primary.master; + } + this.devices.cdrom = this.devices.ide.channels[cdrom_channel].master; this.devices.pit = new PIT(this, device_bus); diff --git a/src/ide.js b/src/ide.js index 35309b0d..cb5b9d42 100644 --- a/src/ide.js +++ b/src/ide.js @@ -886,6 +886,11 @@ IDEInterface.prototype.init_interface = function() } }; +IDEInterface.prototype.has_disk = function() +{ + return this.buffer; +}; + IDEInterface.prototype.eject = function() { if(this.is_atapi && this.buffer) From 8e95ee4f64d667bda2342a899b0d5965a072d48f Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sun, 25 May 2025 19:35:12 +0200 Subject: [PATCH 107/301] minor fix --- src/ide.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ide.js b/src/ide.js index cb5b9d42..606016b7 100644 --- a/src/ide.js +++ b/src/ide.js @@ -888,7 +888,7 @@ IDEInterface.prototype.init_interface = function() IDEInterface.prototype.has_disk = function() { - return this.buffer; + return !!this.buffer; }; IDEInterface.prototype.eject = function() From 50c153e6c2279c99120737ac0ca8b6065a63cec2 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Mon, 26 May 2025 16:00:27 +0200 Subject: [PATCH 108/301] improved register handling based on ACS-3 (ATA/ATAPI-8) --- src/ide.js | 65 ++++++++++++++++++++++++------------------------------ 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/src/ide.js b/src/ide.js index 606016b7..07e204fb 100644 --- a/src/ide.js +++ b/src/ide.js @@ -56,17 +56,14 @@ const BMI_REG_PRDT = 0x04; // Bus Master IDE PRD Table Address register const ATA_ER_ABRT = 0x04; // Command aborted // Status register bits: -// Bits 0x02/0x04 are obsolete and 0x10/0x20 are command dependent. -// Bit 0x02 used to be called "Index" (IDX), bit 0x04 "Corrected data" (CORR), -// bit 0x10 "Drive seek complete" (DSC), and bit 0x20 "Drive write fault" (DF). -// NOTE: -// This code uses ATA_SR_DSC in the old style, this is either unneccessary -// or means that this code was originally not based on ATA/ATAPI-6 but an -// older release (ATA/ATAPI-4 or even older). -const ATA_SR_ERR = 0x01; // Error -const ATA_SR_DRQ = 0x08; // Data request ready -const ATA_SR_DSC = 0x10; // Drive seek complete -const ATA_SR_DRDY = 0x40; // Drive ready +const ATA_SR_ERR = 0x01; // Error (ATA) +const ATA_SR_COND = 0x01; // Check Condition (ATAPI) +const ATA_SR_SENS = 0x02; // Sense Available (ATAPI) +const ATA_SR_AERR = 0x04; // Alignment Error +const ATA_SR_DRQ = 0x08; // Data Request +const ATA_SR_DSC = 0x10; // Drive Seek Complete / Deferred Write Error +const ATA_SR_DF = 0x20; // Device Fault / Stream Error +const ATA_SR_DRDY = 0x40; // Drive Ready const ATA_SR_BSY = 0x80; // Busy // Device register bits: @@ -211,6 +208,7 @@ const LOG_DETAIL_REG_IO = 0x01; // log register read/write access const LOG_DETAIL_IRQ = 0x02; // log IRQ raise/lower events const LOG_DETAIL_RW = 0x04; // log data read/write-related events const LOG_DETAIL_RW_DMA = 0x08; // log DMA data read/write-related events +const LOG_DETAIL_CHS = 0x10; // log register-CHS to LBA conversions const LOG_DETAIL_ALL = 0xFF; // log all details // the bitset of active log details (should be 0 when not in DEBUG mode) const LOG_DETAILS = DEBUG ? LOG_DETAIL_NONE : 0; @@ -529,7 +527,7 @@ function IDEChannel(controller, channel_nr, channel_config, hw_settings) { dbg_log(this.current_interface.name + ": write Command register", LOG_DISK); } - this.current_interface.status_reg &= ~(ATA_SR_ERR | 0x20); // 0x20: command dependent, used to be Drive Write Fault (DF) + this.current_interface.status_reg &= ~(ATA_SR_ERR|ATA_SR_DF); this.current_interface.ata_command(data); if(LOG_DETAILS & LOG_DETAIL_IRQ) { @@ -897,8 +895,8 @@ IDEInterface.prototype.eject = function() { this.medium_changed = true; this.buffer = null; - this.status_reg = 0x59; // TODO: needs documentation, same in this.set_disk_buffer() below - this.error_reg = 0x60; // TODO: is this perhaps a CHECK CONDITION event for Sense UNIT ATTENTION? + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ|ATA_SR_COND; + this.error_reg = ATAPI_SK_UNIT_ATTENTION << 4; this.push_irq(); } }; @@ -922,8 +920,8 @@ IDEInterface.prototype.set_disk_buffer = function(buffer) this.buffer = buffer; if(this.is_atapi) { - this.status_reg = 0x59; // TODO: see this.eject() - this.error_reg = 0x60; // TODO + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ|ATA_SR_COND; + this.error_reg = ATAPI_SK_UNIT_ATTENTION << 4; } this.sector_count = this.buffer.byteLength / this.sector_size; @@ -1066,25 +1064,17 @@ IDEInterface.prototype.ata_command = function(cmd) this.lba_low_reg = last_sector & 0xFF; this.lba_mid_reg = last_sector >> 8 & 0xFF; this.lba_high_reg = last_sector >> 16 & 0xFF; - this.device_reg = this.device_reg & ATA_DR_DEV | last_sector >> 24 & 0x0F; + this.device_reg = this.device_reg & 0xF0 | last_sector >> 24 & 0x0F; this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; case ATA_CMD_READ_NATIVE_MAX_ADDRESS_EXT: var last_sector = this.sector_count - 1; - if(this.channel.device_control_reg & ATA_CR_HOB === 0) - { - this.lba_low_reg = last_sector & 0xFF; - this.lba_mid_reg = last_sector >> 8 & 0xFF; - this.lba_high_reg = last_sector >> 16 & 0xFF; - } - else - { - this.lba_low_reg = last_sector >> 24 & 0xFF; - this.lba_mid_reg = last_sector >> 32 & 0xFF; - this.lba_high_reg = last_sector >> 40 & 0xFF; - } + this.lba_low_reg = last_sector & 0xFF; + this.lba_mid_reg = last_sector >> 8 & 0xFF; + this.lba_high_reg = last_sector >> 16 & 0xFF; + this.lba_low_reg |= last_sector >> 24 << 8 & 0xFF00; this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; this.push_irq(); break; @@ -1143,8 +1133,8 @@ IDEInterface.prototype.ata_command = function(cmd) do_dbg_log = false; this.data_allocate(12); this.data_end = 12; - this.sector_count_reg = 0x01; // 0x01: indicates transfer of a command packet (C/D) - this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; // 0x10: another command is ready to be serviced (SERV) + this.sector_count_reg = 0x01; // 0x01: indicates transfer of a command packet (C/D) + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; this.push_irq(); } else @@ -1553,7 +1543,7 @@ IDEInterface.prototype.atapi_check_condition_response = function(sense_key, addi // https://github.com/qemu/qemu/blob/757a34115e7491744a63dfc3d291fd1de5297ee2/hw/ide/atapi.c#L186 this.data_allocate(0); this.data_end = this.data_length; - this.status_reg = ATA_SR_DRDY|ATA_SR_ERR; + this.status_reg = ATA_SR_DRDY|ATA_SR_COND; this.error_reg = sense_key << 4; this.sector_count_reg = (this.sector_count_reg & ~7) | 2 | 1; this.atapi_sense_key = sense_key; @@ -1634,7 +1624,7 @@ IDEInterface.prototype.atapi_read = function(cmd) else { byte_count = Math.min(byte_count, this.buffer.byteLength - start); - this.status_reg = ATA_SR_DRDY|ATA_SR_DSC | 0x80; // TODO: meaning of 0x80? + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_BSY; this.report_read_start(); this.read_buffer(start, byte_count, (data) => @@ -1688,7 +1678,7 @@ IDEInterface.prototype.atapi_read_dma = function(cmd) } else { - this.status_reg = ATA_SR_DRDY|ATA_SR_DSC | 0x80; // TODO: meaning of 0x80? + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_BSY; this.report_read_start(); this.read_buffer(start, byte_count, (data) => @@ -2056,7 +2046,7 @@ IDEInterface.prototype.ata_read_sectors = function(cmd) } else { - this.status_reg = 0x80 | 0x40; // TODO: meaning? + this.status_reg = ATA_SR_DRDY|ATA_SR_BSY; this.report_read_start(); this.read_buffer(start, byte_count, (data) => @@ -2313,7 +2303,10 @@ IDEInterface.prototype.get_chs = function() var h = this.head; var s = this.lba_low_reg & 0xFF; - dbg_log(this.name + ": get_chs: c=" + c + " h=" + h + " s=" + s, LOG_DISK); + if(LOG_DETAILS & LOG_DETAIL_CHS) + { + dbg_log(this.name + ": get_chs: c=" + c + " h=" + h + " s=" + s, LOG_DISK); + } return (c * this.head_count + h) * this.sectors_per_track + s - 1; }; From f601daf74827edf91b46316e8a4640170f342151 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Mon, 26 May 2025 18:02:09 +0200 Subject: [PATCH 109/301] added ATAPI command flags --- src/ide.js | 57 +++++++++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/src/ide.js b/src/ide.js index 07e204fb..9783a3c1 100644 --- a/src/ide.js +++ b/src/ide.js @@ -166,24 +166,29 @@ const ATAPI_CMD_READ_TRACK_INFORMATION = 0x52; // see [CD-SCSI-2] const ATAPI_CMD_REQUEST_SENSE = 0x03; // see [MMC-2] 9.1.18 const ATAPI_CMD_TEST_UNIT_READY = 0x00; // see [MMC-2] 9.1.20 -const ATAPI_CMD_NAME = +// ATAPI command flags +const ATAPI_CF_NONE = 0x00; // no flags +const ATAPI_CF_NEEDS_DISK = 0x01; // command needs inserted disk +const ATAPI_CF_UNIT_ATTN = 0x02; // bounce command if unit attention condition is active + +const ATAPI_CMD = { - [ATAPI_CMD_GET_CONFIGURATION]: "GET CONFIGURATION", - [ATAPI_CMD_GET_EVENT_STATUS_NOTIFICATION]: "GET EVENT STATUS NOTIFICATION", - [ATAPI_CMD_INQUIRY]: "INQUIRY", - [ATAPI_CMD_MECHANISM_STATUS]: "MECHANISM STATUS", - [ATAPI_CMD_MODE_SENSE_6]: "MODE SENSE (6)", - [ATAPI_CMD_MODE_SENSE_10]: "MODE SENSE (10)", - [ATAPI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL]: "PREVENT ALLOW MEDIUM REMOVAL", - [ATAPI_CMD_READ]: "READ", - [ATAPI_CMD_READ_CAPACITY]: "READ CAPACITY", - [ATAPI_CMD_READ_CD]: "READ CD", - [ATAPI_CMD_READ_DISK_INFORMATION]: "READ DISK INFORMATION", - [ATAPI_CMD_READ_SUBCHANNEL]: "READ SUBCHANNEL", - [ATAPI_CMD_READ_TOC_PMA_ATIP]: "READ TOC PMA ATIP", - [ATAPI_CMD_READ_TRACK_INFORMATION]: "READ TRACK INFORMATION", - [ATAPI_CMD_REQUEST_SENSE]: "REQUEST SENSE", - [ATAPI_CMD_TEST_UNIT_READY]: "TEST UNIT READY", + [ATAPI_CMD_GET_CONFIGURATION]: {name: "GET CONFIGURATION", flags: ATAPI_CF_NONE}, + [ATAPI_CMD_GET_EVENT_STATUS_NOTIFICATION]: {name: "GET EVENT STATUS NOTIFICATION", flags: ATAPI_CF_NONE}, + [ATAPI_CMD_INQUIRY]: {name: "INQUIRY", flags: ATAPI_CF_NONE}, + [ATAPI_CMD_MECHANISM_STATUS]: {name: "MECHANISM STATUS", flags: ATAPI_CF_NONE}, + [ATAPI_CMD_MODE_SENSE_6]: {name: "MODE SENSE (6)", flags: ATAPI_CF_NONE}, + [ATAPI_CMD_MODE_SENSE_10]: {name: "MODE SENSE (10)", flags: ATAPI_CF_NONE}, + [ATAPI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL]: {name: "PREVENT ALLOW MEDIUM REMOVAL", flags: ATAPI_CF_NONE}, + [ATAPI_CMD_READ]: {name: "READ", flags: ATAPI_CF_NEEDS_DISK}, + [ATAPI_CMD_READ_CAPACITY]: {name: "READ CAPACITY", flags: ATAPI_CF_NEEDS_DISK}, + [ATAPI_CMD_READ_CD]: {name: "READ CD", flags: ATAPI_CF_NONE}, + [ATAPI_CMD_READ_DISK_INFORMATION]: {name: "READ DISK INFORMATION", flags: ATAPI_CF_NEEDS_DISK}, + [ATAPI_CMD_READ_SUBCHANNEL]: {name: "READ SUBCHANNEL", flags: ATAPI_CF_NEEDS_DISK}, + [ATAPI_CMD_READ_TOC_PMA_ATIP]: {name: "READ TOC PMA ATIP", flags: ATAPI_CF_NEEDS_DISK}, + [ATAPI_CMD_READ_TRACK_INFORMATION]: {name: "READ TRACK INFORMATION", flags: ATAPI_CF_NONE}, + [ATAPI_CMD_REQUEST_SENSE]: {name: "REQUEST SENSE", flags: ATAPI_CF_NONE}, + [ATAPI_CMD_TEST_UNIT_READY]: {name: "TEST UNIT READY", flags: ATAPI_CF_NONE}, }; // ATAPI 4-bit Sense Keys, see [MMC-2] 9.1.18.3, Table 123 @@ -199,7 +204,9 @@ const ATAPI_SK_BLANK_CHECK = 8; const ATAPI_SK_ABORTED_COMMAND = 11; // ATAPI 8-bit Additional Sense Codes, see [MMC-2] 9.1.18.3, Table 124 +// https://github.com/qemu/qemu/blob/3c5a5e213e5f08fbfe70728237f7799ac70f5b99/hw/ide/ide-internal.h#L288 const ATAPI_ASC_INV_FIELD_IN_CMD_PACKET = 0x24; +const ATAPI_ASC_MEDIUM_MAY_HAVE_CHANGED = 0x28; const ATAPI_ASC_MEDIUM_NOT_PRESENT = 0x3A; // Debug log detail bits (internal to this module) @@ -1273,31 +1280,29 @@ IDEInterface.prototype.ata_command = function(cmd) IDEInterface.prototype.atapi_handle = function() { const cmd = this.data[0]; + const cmd_name = ATAPI_CMD[cmd] ? ATAPI_CMD[cmd].name : ""; + const cmd_flags = ATAPI_CMD[cmd] ? ATAPI_CMD[cmd].flags : ATAPI_CF_NONE; const regs_pre = DEBUG ? this.capture_regs() : undefined; + let do_dbg_log = DEBUG; let dbg_log_extra; this.data_pointer = 0; this.current_atapi_command = cmd; - if(cmd !== ATAPI_CMD_REQUEST_SENSE) + if(cmd !== ATAPI_CMD_REQUEST_SENSE) // TODO { this.atapi_sense_key = 0; this.atapi_add_sense = 0; } - if(!this.buffer && (cmd === ATAPI_CMD_READ_CAPACITY || - cmd === ATAPI_CMD_READ || - cmd === ATAPI_CMD_READ_SUBCHANNEL || - cmd === ATAPI_CMD_READ_TOC_PMA_ATIP || - cmd === ATAPI_CMD_READ_DISK_INFORMATION)) + if(!this.buffer && cmd_flags & ATAPI_CF_NEEDS_DISK) { this.atapi_check_condition_response(ATAPI_SK_NOT_READY, ATAPI_ASC_MEDIUM_NOT_PRESENT); this.push_irq(); if(DEBUG) { - const result = this.status_reg & ATA_SR_ERR ? (this.error_reg & ATA_ER_ABRT ? "ABORT" : "ERROR") : "OK"; - dbg_log(`${this.name}: ATAPI command ${ATAPI_CMD_NAME[cmd]} (${h(cmd)}) without medium: ${result} [${regs_pre}]`, LOG_DISK); + dbg_log(`${this.name}: ATAPI command ${cmd_name} (${h(cmd)}) without medium: ERROR [${regs_pre}]`, LOG_DISK); } return; } @@ -1530,7 +1535,7 @@ IDEInterface.prototype.atapi_handle = function() const regs_msg = `[${regs_pre}] -> [${this.capture_regs()}]`; const result = this.status_reg & ATA_SR_ERR ? (this.error_reg & ATA_ER_ABRT ? "ABORT" : "ERROR") : "OK"; dbg_log_extra = dbg_log_extra ? ` ${dbg_log_extra}:` : ""; - dbg_log(`${this.name}: ATAPI command ${ATAPI_CMD_NAME[cmd]} (${h(cmd)}):${dbg_log_extra} ${result} ${regs_msg}`, LOG_DISK); + dbg_log(`${this.name}: ATAPI command ${cmd_name} (${h(cmd)}):${dbg_log_extra} ${result} ${regs_msg}`, LOG_DISK); } }; From 06d9b9bf92e5da12a2432f03cfbeb30a3b74ddc5 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Wed, 28 May 2025 17:19:55 +0200 Subject: [PATCH 110/301] removed CPU.devices.hda --- src/cpu.js | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/cpu.js b/src/cpu.js index 8eb03bed..14663397 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -537,8 +537,8 @@ CPU.prototype.get_state = function() state[53] = this.devices.ps2; state[54] = this.devices.uart0; state[55] = this.devices.fdc; - state[56] = this.devices.cdrom.channel; - state[57] = this.devices.hda? this.devices.hda.channel : undefined; + state[56] = this.devices.ide.secondary; + state[57] = this.devices.ide.primary ? this.devices.ide.primary : undefined; state[58] = this.devices.pit; state[59] = this.devices.net; state[60] = this.get_state_pic(); @@ -692,8 +692,8 @@ CPU.prototype.set_state = function(state) this.devices.ps2 && this.devices.ps2.set_state(state[53]); this.devices.uart0 && this.devices.uart0.set_state(state[54]); this.devices.fdc && this.devices.fdc.set_state(state[55]); - this.devices.cdrom && this.devices.cdrom.channel.set_state(state[56]); - this.devices.hda && this.devices.hda.channel.set_state(state[57]); + this.devices.ide.secondary.set_state(state[56]); + this.devices.ide.primary && this.devices.ide.primary.set_state(state[57]); this.devices.pit && this.devices.pit.set_state(state[58]); this.devices.net && this.devices.net.set_state(state[59]); this.set_state_pic(state[60]); @@ -1105,19 +1105,14 @@ CPU.prototype.init = function(settings, device_bus) this.devices.fdc = new FloppyController(this, settings.fda, settings.fdb); - let cdrom_channel = 0; const ide_config = [[undefined, undefined], [undefined, undefined]]; if(settings.hda) { ide_config[0][0] = {buffer: settings.hda}; ide_config[0][1] = {buffer: settings.hdb}; - cdrom_channel++; } - ide_config[cdrom_channel][0] = {is_cdrom: true, buffer: settings.cdrom}; + ide_config[1][0] = {is_cdrom: true, buffer: settings.cdrom}; this.devices.ide = new IDEController(this, device_bus, ide_config); - if(settings.hda) { - this.devices.hda = this.devices.ide.primary.master; - } - this.devices.cdrom = this.devices.ide.channels[cdrom_channel].master; + this.devices.cdrom = this.devices.ide.secondary.master; this.devices.pit = new PIT(this, device_bus); From 5a7d8948d862eff382c661c705781b2cfeb4d60f Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Wed, 28 May 2025 18:36:01 +0200 Subject: [PATCH 111/301] added references to ACS-3 from ATA/ATAPI-8 --- src/ide.js | 91 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 32 deletions(-) diff --git a/src/ide.js b/src/ide.js index 9783a3c1..062c8aa9 100644 --- a/src/ide.js +++ b/src/ide.js @@ -7,9 +7,12 @@ import { CMOS_BIOS_DISKTRANSFLAG, CMOS_DISK_DATA, CMOS_DISK_DRIVE1_CYL, CMOS_DIS import { CPU } from "./cpu.js"; import { BusConnector } from "./bus.js"; -// ATA/ATAPI-6 IDE Controller +// ATA/ATAPI-6/8 IDE Controller // // References +// - [ATA8-ACS] +// ATA/ATAPI Command Set - 3 (ACS-3) (Rev. 5, Oct. 28, 2013) +// https://read.seas.harvard.edu/cs161/2019/pdf/ata-atapi-8.pdf // - [ATA-6] // AT Attachment with Packet Interface - 6 (ATA/ATAPI-6) (Rev. 3a; Dec. 14, 2001) // https://technion-csl.github.io/ose/readings/hardware/ATA-d1410r3a.pdf @@ -17,6 +20,15 @@ import { BusConnector } from "./bus.js"; // PROPOSAL FOR CD-ROM IN SCSI-2 (X3T9.2/87) (Rev. 0, Jun. 30, 1987) // https://www.t10.org/ftp/x3t9.2/document.87/87-106r0.txt // https://www.t10.org/ftp/x3t9.2/document.87/87-106r1.txt (errata to r0) +// - [SAM-3] +// SCSI Architecture Model - 3 (SAM-3) (Sep. 21, 2004) +// https://dn790004.ca.archive.org/0/items/SCSISpecificationDocumentsSCSIDocuments/SCSI%20Architecture%20Model/SCSI%20Architecture%20Model%203%20rev%2014.pdf +// - [SPC-3] +// SCSI Primary Commands - 3 (SPC-3) (July 20, 2008) +// https://www.t10.org/ftp/t10/document.08/08-309r0.pdf +// - [MMC-3] +// SCSI Multimedia Commands - 3 (MMC-3) (Rev. 10g, Nov. 12, 2001) +// https://ia902808.us.archive.org/33/items/mmc3r10g/mmc3r10g.pdf // - [MMC-2] // Packet Commands for C/DVD Devices (1997) // https://www.t10.org/ftp/t10/document.97/97-108r0.pdf @@ -68,47 +80,47 @@ const ATA_SR_BSY = 0x80; // Busy // Device register bits: // Bits 0x20/0x80 are obsolete and 0x01/0x02/0x04/0x08/0x40 are command dependent. -const ATA_DR_DEV = 0x10; // Device select; slave device if set, else master device +const ATA_DR_DEV = 0x10; // Device select; slave device if set, else master device // Device Control register bits: // Bits 0x08/0x10/0x20/0x40 are reserved and bit 0x01 is always zero. const ATA_CR_nIEN = 0x02; // Interrupt disable (not Interrupt ENable) const ATA_CR_SRST = 0x04; // Software reset -const ATA_CR_HOB = 0x80; // 48-bit Address feature set +const ATA_CR_HOB = 0x80; // 48-bit Address feature set // ATA commands -const ATA_CMD_DEVICE_RESET = 0x08; // see [ATA-6] 8.10 and 9.11 -const ATA_CMD_EXECUTE_DEVICE_DIAGNOSTIC = 0x90; // see [ATA-6] 8.11 -const ATA_CMD_FLUSH_CACHE = 0xE7; // see [ATA-6] 8.12 -const ATA_CMD_FLUSH_CACHE_EXT = 0xEA; // see [ATA-6] 8.13 +const ATA_CMD_DEVICE_RESET = 0x08; // see [ATA8-ACS] 7.6 +const ATA_CMD_EXECUTE_DEVICE_DIAGNOSTIC = 0x90; // see [ATA8-ACS] 7.9 +const ATA_CMD_FLUSH_CACHE = 0xE7; // see [ATA8-ACS] 7.10 +const ATA_CMD_FLUSH_CACHE_EXT = 0xEA; // see [ATA8-ACS] 7.11 const ATA_CMD_GET_MEDIA_STATUS = 0xDA; // see [ATA-6] 8.14 -const ATA_CMD_IDENTIFY_DEVICE = 0xEC; // see [ATA-6] 8.15 -const ATA_CMD_IDENTIFY_PACKET_DEVICE = 0xA1; // see [ATA-6] 8.16 -const ATA_CMD_IDLE_IMMEDIATE = 0xE1; // see [ATA-6] 8.18 -const ATA_CMD_INITIALIZE_DEVICE_PARAMETERS = 0x91; // not mentioned in [ATA-6] +const ATA_CMD_IDENTIFY_DEVICE = 0xEC; // see [ATA8-ACS] 7.12 +const ATA_CMD_IDENTIFY_PACKET_DEVICE = 0xA1; // see [ATA8-ACS] 7.13 +const ATA_CMD_IDLE_IMMEDIATE = 0xE1; // see [ATA8-ACS] 7.15 +const ATA_CMD_INITIALIZE_DEVICE_PARAMETERS = 0x91; // not mentioned in [ATA-6] or [ATA8-ACS] const ATA_CMD_MEDIA_LOCK = 0xDE; // see [ATA-6] 8.20 -const ATA_CMD_NOP = 0x00; // see [ATA-6] 8.22 -const ATA_CMD_PACKET = 0xA0; // see [ATA-6] 8.23 -const ATA_CMD_READ_DMA = 0xC8; // see [ATA-6] 8.26 -const ATA_CMD_READ_DMA_EXT = 0x25; // see [ATA-6] 8.25 -const ATA_CMD_READ_MULTIPLE = 0x29; // see [ATA-6] 8.30 -const ATA_CMD_READ_MULTIPLE_EXT = 0xC4; // see [ATA-6] 8.31 +const ATA_CMD_NOP = 0x00; // see [ATA8-ACS] 7.17 +const ATA_CMD_PACKET = 0xA0; // see [ATA8-ACS] 7.18 +const ATA_CMD_READ_DMA = 0xC8; // see [ATA8-ACS] 7.21 +const ATA_CMD_READ_DMA_EXT = 0x25; // see [ATA8-ACS] 7.22 +const ATA_CMD_READ_MULTIPLE = 0x29; // see [ATA8-ACS] 7.26 +const ATA_CMD_READ_MULTIPLE_EXT = 0xC4; // see [ATA8-ACS] 7.27 const ATA_CMD_READ_NATIVE_MAX_ADDRESS = 0xF8; // see [ATA-6] 8.32 const ATA_CMD_READ_NATIVE_MAX_ADDRESS_EXT = 0x27; // see [ATA-6] 8.33 -const ATA_CMD_READ_SECTORS = 0x20; // see [ATA-6] 8.34 -const ATA_CMD_READ_SECTORS_EXT = 0x24; // see [ATA-6] 8.35 -const ATA_CMD_READ_VERIFY_SECTORS = 0x40; // see [ATA-6] 8.36 -const ATA_CMD_SECURITY_FREEZE_LOCK = 0xF5; // see [ATA-6] 8.41 -const ATA_CMD_SET_FEATURES = 0xEF; // see [ATA-6] 8.46 +const ATA_CMD_READ_SECTORS = 0x20; // see [ATA8-ACS] 7.28 +const ATA_CMD_READ_SECTORS_EXT = 0x24; // see [ATA8-ACS] 7.29 +const ATA_CMD_READ_VERIFY_SECTORS = 0x40; // see [ATA8-ACS] 7.32 +const ATA_CMD_SECURITY_FREEZE_LOCK = 0xF5; // see [ATA8-ACS] 7.40 +const ATA_CMD_SET_FEATURES = 0xEF; // see [ATA8-ACS] 7.45 const ATA_CMD_SET_MAX = 0xF9; // see [ATA-6] 8.47 -const ATA_CMD_SET_MULTIPLE_MODE = 0xC6; // see [ATA-6] 8.49 -const ATA_CMD_STANDBY_IMMEDIATE = 0xE0; // see [ATA-6] 8.53 -const ATA_CMD_WRITE_DMA = 0xCA; // see [ATA-6] 8.55 -const ATA_CMD_WRITE_DMA_EXT = 0x35; // see [ATA-6] 8.56 -const ATA_CMD_WRITE_MULTIPLE = 0x39; // see [ATA-6] 8.60 -const ATA_CMD_WRITE_MULTIPLE_EXT = 0xC5; // see [ATA-6] 8.61 -const ATA_CMD_WRITE_SECTORS = 0x30; // see [ATA-6] 8.62 -const ATA_CMD_WRITE_SECTORS_EXT = 0x34; // see [ATA-6] 8.63 +const ATA_CMD_SET_MULTIPLE_MODE = 0xC6; // see [ATA8-ACS] 7.46 +const ATA_CMD_STANDBY_IMMEDIATE = 0xE0; // see [ATA8-ACS] 7.50 +const ATA_CMD_WRITE_DMA = 0xCA; // see [ATA8-ACS] 7.58 +const ATA_CMD_WRITE_DMA_EXT = 0x35; // see [ATA8-ACS] 7.59 +const ATA_CMD_WRITE_MULTIPLE = 0x39; // see [ATA8-ACS] 7.64 +const ATA_CMD_WRITE_MULTIPLE_EXT = 0xC5; // see [ATA8-ACS] 7.65 +const ATA_CMD_WRITE_SECTORS = 0x30; // see [ATA8-ACS] 7.67 +const ATA_CMD_WRITE_SECTORS_EXT = 0x34; // see [ATA8-ACS] 7.68 const ATA_CMD_10h = 0x10; // command obsolete/unknown, see [ATA-6] Table E.2 const ATA_CMD_NAME = @@ -191,6 +203,10 @@ const ATAPI_CMD = [ATAPI_CMD_TEST_UNIT_READY]: {name: "TEST UNIT READY", flags: ATAPI_CF_NONE}, }; +// ATAPI device signature +const ATAPI_SIGNATURE_LO = 0x14; +const ATAPI_SIGNATURE_HI = 0xEB; + // ATAPI 4-bit Sense Keys, see [MMC-2] 9.1.18.3, Table 123 const ATAPI_SK_NO_SENSE = 0; const ATAPI_SK_RECOVERED_ERROR = 1; @@ -1001,8 +1017,8 @@ IDEInterface.prototype.device_reset = function() this.sector_count_reg = 1; this.error_reg = 1; this.lba_low_reg = 1; - this.lba_mid_reg = 0x14; - this.lba_high_reg = 0xEB; + this.lba_mid_reg = ATAPI_SIGNATURE_LO; // TODO: missing documentation + this.lba_high_reg = ATAPI_SIGNATURE_HI; } else { @@ -1087,6 +1103,17 @@ IDEInterface.prototype.ata_command = function(cmd) break; case ATA_CMD_READ_SECTORS: + do_dbg_log = false; + if(this.is_atapi) + { + this.ata_abort_command(); + } + else + { + this.ata_read_sectors(cmd); + } + break; + case ATA_CMD_READ_SECTORS_EXT: case ATA_CMD_READ_MULTIPLE: case ATA_CMD_READ_MULTIPLE_EXT: From 28cfa1501b16bce85d5c18beab966b9478262c31 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Wed, 28 May 2025 18:37:33 +0200 Subject: [PATCH 112/301] added ATAPI signature in ATA commands "READ SECTORS" and "IDENTIFY DEVICE" --- src/ide.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ide.js b/src/ide.js index 062c8aa9..ab199c08 100644 --- a/src/ide.js +++ b/src/ide.js @@ -1106,6 +1106,8 @@ IDEInterface.prototype.ata_command = function(cmd) do_dbg_log = false; if(this.is_atapi) { + this.lba_mid_reg = ATAPI_SIGNATURE_LO; // see [ATA8-ACS] 4.3 + this.lba_high_reg = ATAPI_SIGNATURE_HI; this.ata_abort_command(); } else @@ -1257,6 +1259,8 @@ IDEInterface.prototype.ata_command = function(cmd) case ATA_CMD_IDENTIFY_DEVICE: if(this.is_atapi) { + this.lba_mid_reg = ATAPI_SIGNATURE_LO; // see [ATA8-ACS] 4.3 + this.lba_high_reg = ATAPI_SIGNATURE_HI; this.ata_abort_command(); } else From ef0b306732cfe311046e31b94cdbfd7d5df851ef Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Thu, 29 May 2025 13:11:06 +0200 Subject: [PATCH 113/301] added ATAPI command "PAUSE" (W95) The minor change from yesterday changed Win95's behaviour such that it now issues an ATAPI command ("PAUSE") that wasn't implemented yet which led to Win95 crashing in v86 Debug mode. Added that command and now it works again. --- src/ide.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ide.js b/src/ide.js index ab199c08..588ebc8b 100644 --- a/src/ide.js +++ b/src/ide.js @@ -167,6 +167,7 @@ const ATAPI_CMD_INQUIRY = 0x12; // see [MMC-2] 9.1.3 const ATAPI_CMD_MECHANISM_STATUS = 0xBD; // see [MMC-2] 9.1.5 const ATAPI_CMD_MODE_SENSE_6 = 0x1A; // see [CD-SCSI-2] const ATAPI_CMD_MODE_SENSE_10 = 0x5A; // see [MMC-2] 9.1.7 +const ATAPI_CMD_PAUSE = 0x45; // see [CD-SCSI-2] const ATAPI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1E; // see [MMC-2] 9.1.9 const ATAPI_CMD_READ = 0x28; // see [CD-SCSI-2] const ATAPI_CMD_READ_CAPACITY = 0x25; // see [MMC-2] 9.1.12 @@ -191,6 +192,7 @@ const ATAPI_CMD = [ATAPI_CMD_MECHANISM_STATUS]: {name: "MECHANISM STATUS", flags: ATAPI_CF_NONE}, [ATAPI_CMD_MODE_SENSE_6]: {name: "MODE SENSE (6)", flags: ATAPI_CF_NONE}, [ATAPI_CMD_MODE_SENSE_10]: {name: "MODE SENSE (10)", flags: ATAPI_CF_NONE}, + [ATAPI_CMD_PAUSE]: {name: "PAUSE", flags: ATAPI_CF_NONE}, [ATAPI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL]: {name: "PREVENT ALLOW MEDIUM REMOVAL", flags: ATAPI_CF_NONE}, [ATAPI_CMD_READ]: {name: "READ", flags: ATAPI_CF_NEEDS_DISK}, [ATAPI_CMD_READ_CAPACITY]: {name: "READ CAPACITY", flags: ATAPI_CF_NEEDS_DISK}, @@ -1530,6 +1532,7 @@ IDEInterface.prototype.atapi_handle = function() this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; break; + case ATAPI_CMD_PAUSE: case ATAPI_CMD_GET_EVENT_STATUS_NOTIFICATION: dbg_log_extra = "unimplemented"; this.atapi_check_condition_response(ATAPI_SK_ILLEGAL_REQUEST, ATAPI_ASC_INV_FIELD_IN_CMD_PACKET); From 50945b3d16240267de444be65b4715cc4c430cee Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Thu, 29 May 2025 14:15:52 +0200 Subject: [PATCH 114/301] set "CD Not Ready" condition in ATAPI commands as specified in MMC-3 A few ATAPI commands did not return an error (CD Not Ready condition) with ejected medium as specified in MMC-3 (https://www.t10.org/ftp/t10/document.97/97-108r0.pdf). Adding these to our subset of ATAPI commands improved the FreeDOS 1.4 situation around eject/insert a bit (the problem really is eject, insert always works given the medium is ejected). Waiting around 20-30 sec after eject seems to work reliably. --- src/ide.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ide.js b/src/ide.js index 588ebc8b..00ead569 100644 --- a/src/ide.js +++ b/src/ide.js @@ -184,6 +184,7 @@ const ATAPI_CF_NONE = 0x00; // no flags const ATAPI_CF_NEEDS_DISK = 0x01; // command needs inserted disk const ATAPI_CF_UNIT_ATTN = 0x02; // bounce command if unit attention condition is active +// ATAPI commands, for flags see [MMC-3] 4.2.6 const ATAPI_CMD = { [ATAPI_CMD_GET_CONFIGURATION]: {name: "GET CONFIGURATION", flags: ATAPI_CF_NONE}, @@ -192,17 +193,17 @@ const ATAPI_CMD = [ATAPI_CMD_MECHANISM_STATUS]: {name: "MECHANISM STATUS", flags: ATAPI_CF_NONE}, [ATAPI_CMD_MODE_SENSE_6]: {name: "MODE SENSE (6)", flags: ATAPI_CF_NONE}, [ATAPI_CMD_MODE_SENSE_10]: {name: "MODE SENSE (10)", flags: ATAPI_CF_NONE}, - [ATAPI_CMD_PAUSE]: {name: "PAUSE", flags: ATAPI_CF_NONE}, + [ATAPI_CMD_PAUSE]: {name: "PAUSE", flags: ATAPI_CF_NEEDS_DISK}, [ATAPI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL]: {name: "PREVENT ALLOW MEDIUM REMOVAL", flags: ATAPI_CF_NONE}, [ATAPI_CMD_READ]: {name: "READ", flags: ATAPI_CF_NEEDS_DISK}, [ATAPI_CMD_READ_CAPACITY]: {name: "READ CAPACITY", flags: ATAPI_CF_NEEDS_DISK}, - [ATAPI_CMD_READ_CD]: {name: "READ CD", flags: ATAPI_CF_NONE}, + [ATAPI_CMD_READ_CD]: {name: "READ CD", flags: ATAPI_CF_NEEDS_DISK}, [ATAPI_CMD_READ_DISK_INFORMATION]: {name: "READ DISK INFORMATION", flags: ATAPI_CF_NEEDS_DISK}, [ATAPI_CMD_READ_SUBCHANNEL]: {name: "READ SUBCHANNEL", flags: ATAPI_CF_NEEDS_DISK}, [ATAPI_CMD_READ_TOC_PMA_ATIP]: {name: "READ TOC PMA ATIP", flags: ATAPI_CF_NEEDS_DISK}, - [ATAPI_CMD_READ_TRACK_INFORMATION]: {name: "READ TRACK INFORMATION", flags: ATAPI_CF_NONE}, + [ATAPI_CMD_READ_TRACK_INFORMATION]: {name: "READ TRACK INFORMATION", flags: ATAPI_CF_NEEDS_DISK}, [ATAPI_CMD_REQUEST_SENSE]: {name: "REQUEST SENSE", flags: ATAPI_CF_NONE}, - [ATAPI_CMD_TEST_UNIT_READY]: {name: "TEST UNIT READY", flags: ATAPI_CF_NONE}, + [ATAPI_CMD_TEST_UNIT_READY]: {name: "TEST UNIT READY", flags: ATAPI_CF_NEEDS_DISK}, }; // ATAPI device signature From 20521979e743da6d3a30a3bca93ba5e94643cda5 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Thu, 29 May 2025 17:04:58 +0200 Subject: [PATCH 115/301] added ATAPI command "START STOP UNIT" (W95) Adds support to eject a CD from within Windows 95 through the context menu of the CD icon. Before this commit eject was only supported externally through the v86 API. --- src/ide.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/ide.js b/src/ide.js index 00ead569..15eb9297 100644 --- a/src/ide.js +++ b/src/ide.js @@ -177,6 +177,7 @@ const ATAPI_CMD_READ_SUBCHANNEL = 0x42; // see [CD-SCSI-2] const ATAPI_CMD_READ_TOC_PMA_ATIP = 0x43; // see [CD-SCSI-2] const ATAPI_CMD_READ_TRACK_INFORMATION = 0x52; // see [CD-SCSI-2] const ATAPI_CMD_REQUEST_SENSE = 0x03; // see [MMC-2] 9.1.18 +const ATAPI_CMD_START_STOP_UNIT = 0x1B; // see [CD-SCSI-2] const ATAPI_CMD_TEST_UNIT_READY = 0x00; // see [MMC-2] 9.1.20 // ATAPI command flags @@ -203,6 +204,7 @@ const ATAPI_CMD = [ATAPI_CMD_READ_TOC_PMA_ATIP]: {name: "READ TOC PMA ATIP", flags: ATAPI_CF_NEEDS_DISK}, [ATAPI_CMD_READ_TRACK_INFORMATION]: {name: "READ TRACK INFORMATION", flags: ATAPI_CF_NEEDS_DISK}, [ATAPI_CMD_REQUEST_SENSE]: {name: "REQUEST SENSE", flags: ATAPI_CF_NONE}, + [ATAPI_CMD_START_STOP_UNIT]: {name: "START STOP UNIT", flags: ATAPI_CF_NONE}, [ATAPI_CMD_TEST_UNIT_READY]: {name: "TEST UNIT READY", flags: ATAPI_CF_NEEDS_DISK}, }; @@ -1533,6 +1535,20 @@ IDEInterface.prototype.atapi_handle = function() this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; break; + case ATAPI_CMD_START_STOP_UNIT: + var loej_start = this.data[4] & 0x3; + dbg_log_extra = `Immed=${h(this.data[1] & 1)} LoEj/Start=${h(loej_start)}`; + if(this.buffer && loej_start === 0x2) + { + dbg_log_extra += ": disk ejected"; + this.medium_changed = true; + this.buffer = null; + } + this.status_reg = ATA_SR_DRDY|ATA_SR_DSC; + this.data_allocate(0); + this.data_end = this.data_length; + break; + case ATAPI_CMD_PAUSE: case ATAPI_CMD_GET_EVENT_STATUS_NOTIFICATION: dbg_log_extra = "unimplemented"; From ba2186ee4946b165fd81e14defd04a1d6a68a171 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Fri, 30 May 2025 15:50:23 +0200 Subject: [PATCH 116/301] improved identify packet --- src/ide.js | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/ide.js b/src/ide.js index 15eb9297..73e65096 100644 --- a/src/ide.js +++ b/src/ide.js @@ -1051,10 +1051,10 @@ IDEInterface.prototype.ata_abort_command = function() IDEInterface.prototype.capture_regs = function() { - return `ST=${h(this.status_reg)} ER=${h(this.error_reg)} ` + - `SC=${h(this.sector_count_reg)} LL=${h(this.lba_low_reg)} ` + - `LM=${h(this.lba_mid_reg)} LH=${h(this.lba_high_reg)} ` + - `FE=${h(this.features_reg)} DE=${h(this.device_reg)}`; + return `ST=${h(this.status_reg & 0xFF)} ER=${h(this.error_reg & 0xFF)} ` + + `SC=${h(this.sector_count_reg & 0xFF)} LL=${h(this.lba_low_reg & 0xFF)} ` + + `LM=${h(this.lba_mid_reg & 0xFF)} LH=${h(this.lba_high_reg & 0xFF)} ` + + `FE=${h(this.features_reg & 0xFF)}`; }; IDEInterface.prototype.ata_command = function(cmd) @@ -2440,7 +2440,7 @@ IDEInterface.prototype.create_identify_packet = function() // - [ATA-retro] // AT Attachment Interface for Disk Drives, Revision 4c // https://dn790009.ca.archive.org/0/items/SCSISpecificationDocumentsATAATAPI/ATA_ATAPI/AT%20Attachment%20Interface%20for%20Disk%20Drives%20Revision%204c.pdf - // For the words above 64 see [ATA-6] Table 27. + // For the words above 64 see [ATA-6] Table 27, [ATA8-ACS] 7.12 and 7.13. // // dead link: http://bochs.sourceforge.net/cgi-bin/lxr/source/iodev/harddrv.cc#L2821 @@ -2451,7 +2451,12 @@ IDEInterface.prototype.create_identify_packet = function() // - 0x0400: Multiword DMA mode 2 is selected const multiword_dma_mode = this.current_command === ATA_CMD_PACKET ? 0 : 0x0407; // Major version number: bits 3/4/5/6 indicate support for ATA/ATAPI-3/4/5/6 (bits 0/1/2 are obsolete in [ATA-6]) - const major_version = 0b01111110; + const major_version = 0x0000; // device does not report version + // supported ATA: NOP, FLUSH CACHE, FLUSH CACHE EXT, 48-bit addr + // supported ATAPI: NOP, DEVICE RESET, PACKET and FLUSH CACHE + const feat_82 = this.is_atapi ? 1 << 14 | 1 << 9 | 1 << 5 : 1 << 14; + const feat_83 = this.is_atapi ? 1 << 14 | 1 << 12 : 1 << 14 | 1 << 13 | 1 << 12 | 1 << 10; + const feat_84 = this.is_atapi ? 1 << 14 : 1 << 14; this.data.fill(0, 0, 512); this.data_set([ @@ -2471,7 +2476,7 @@ IDEInterface.prototype.create_identify_packet = function() this.sectors_per_track & 0xFF, this.sectors_per_track >> 8 & 0xFF, // 7-9: Vendor-unique 0, 0, 0, 0, 0, 0, - // 10-19: Serial number (20 ASCII characters) + // 10-19: Serial number (20 ASCII characters, filled below) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -2481,9 +2486,9 @@ IDEInterface.prototype.create_identify_packet = function() 0, 2, // 22: Number of ECC bytes avail on read/write long cmds 4, 0, - // 23-26: Firmware revision (8 ASCII characters) + // 23-26: Firmware revision (8 ASCII characters, filled below) 0, 0, 0, 0, 0, 0, 0, 0, - // 27-46: Model number (40 ASCII characters) + // 27-46: Model number (40 ASCII characters, filled below) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -2538,17 +2543,17 @@ IDEInterface.prototype.create_identify_packet = function() // 81: Minor version number 0, 0, // 82: Command set supported - 0, 0, + feat_82 & 0xFF, feat_82 >> 8 & 0xFF, // 83: Command set supported - 0, 0x74, + feat_83 & 0xFF, feat_83 >> 8 & 0xFF, // 84: Command set/feature supported extension - 0, 0x40, - // 85: Command set/feature enabled - 0, 0x40, - // 86: Command set/feature enabled - 0, 0x74, - // 87: Command set/feature default - 0, 0x40, + feat_84 & 0xFF, feat_84 >> 8 & 0xFF, + // 85: Command set/feature enabled (copy of 82) + feat_82 & 0xFF, feat_82 >> 8 & 0xFF, + // 86: Command set/feature enabled (copy of 83) + feat_83 & 0xFF, feat_83 >> 8 & 0xFF, + // 87: Command set/feature default (copy of 84) + feat_84 & 0xFF, feat_84 >> 8 & 0xFF, // 88: DMA related field 0, 0, // 89: Time required for security erase unit completion @@ -2572,7 +2577,7 @@ IDEInterface.prototype.create_identify_packet = function() ]); // 10-19 serial number - strcpy_be16(this.data, 10, 10, "86" + this.channel_nr.toString() + this.interface_nr.toString()); + strcpy_be16(this.data, 10, 10, `8086-86${this.channel_nr}${this.interface_nr}`); // 23-26 firmware revision strcpy_be16(this.data, 23, 4, "1.00"); // 27-46 model number From 1f855f85ee5add139539172b9b565c982d772b0c Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sat, 31 May 2025 11:35:03 +0200 Subject: [PATCH 117/301] write RTC CMOS data only for master drives --- src/ide.js | 49 ++++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/ide.js b/src/ide.js index 73e65096..5a9e1e71 100644 --- a/src/ide.js +++ b/src/ide.js @@ -981,32 +981,35 @@ IDEInterface.prototype.set_disk_buffer = function(buffer) this.cylinder_count = Math.floor(this.cylinder_count); } - // for CMOS see: - // https://github.com/copy/v86/blob/master/src/rtc.js - // https://github.com/coreboot/seabios/blob/master/src/hw/rtc.h - // https://web.archive.org/web/20240119203005/http://www.bioscentral.com/misc/cmosmap.htm - const rtc = this.cpu.devices.rtc; + if(this.interface_nr === 0) + { + // for CMOS see: + // https://github.com/copy/v86/blob/master/src/rtc.js + // https://github.com/coreboot/seabios/blob/master/src/hw/rtc.h + // https://web.archive.org/web/20240119203005/http://www.bioscentral.com/misc/cmosmap.htm + const rtc = this.cpu.devices.rtc; - // master - rtc.cmos_write(CMOS_BIOS_DISKTRANSFLAG, // TODO: what is this doing, setting LBA translation? - rtc.cmos_read(CMOS_BIOS_DISKTRANSFLAG) | 1 << this.channel_nr * 4); + // master + rtc.cmos_write(CMOS_BIOS_DISKTRANSFLAG, // TODO: what is this doing, setting LBA translation? + rtc.cmos_read(CMOS_BIOS_DISKTRANSFLAG) | 1 << this.channel_nr * 4); - // set hard disk type (CMOS_DISK_DATA = 0x12) of C: to 0b1111, keep type of D: - // bits 0-3: hard disk type of D: - // bits 4-7: hard disk type of C: - // TODO: should this not also set CMOS_DISK_DRIVE1_TYPE to a hard disk type (see SeaBIOS rtc.h)? - rtc.cmos_write(CMOS_DISK_DATA, rtc.cmos_read(CMOS_DISK_DATA) & 0x0F | 0xF0); + // set hard disk type (CMOS_DISK_DATA = 0x12) of C: to 0b1111, keep type of D: + // bits 0-3: hard disk type of D: + // bits 4-7: hard disk type of C: + // TODO: should this not also set CMOS_DISK_DRIVE1_TYPE to a hard disk type (see SeaBIOS rtc.h)? + rtc.cmos_write(CMOS_DISK_DATA, rtc.cmos_read(CMOS_DISK_DATA) & 0x0F | 0xF0); - const drive_reg = this.channel_nr === 0 ? CMOS_DISK_DRIVE1_CYL : CMOS_DISK_DRIVE2_CYL; // 0x1B : 0x24 (drive C: or D:) - rtc.cmos_write(drive_reg + 0, this.cylinder_count & 0xFF); // number of cylinders least significant byte - rtc.cmos_write(drive_reg + 1, this.cylinder_count >> 8 & 0xFF); // number of cylinders most significant byte - rtc.cmos_write(drive_reg + 2, this.head_count & 0xFF); // number of heads - rtc.cmos_write(drive_reg + 3, 0xFF); // write precomp cylinder least significant byte - rtc.cmos_write(drive_reg + 4, 0xFF); // write precomp cylinder most significant byte - rtc.cmos_write(drive_reg + 5, 0xC8); // control byte - rtc.cmos_write(drive_reg + 6, this.cylinder_count & 0xFF); // landing zone least significant byte - rtc.cmos_write(drive_reg + 7, this.cylinder_count >> 8 & 0xFF); // landing zone most significant byte - rtc.cmos_write(drive_reg + 8, this.sectors_per_track & 0xFF); // number of sectors + const drive_reg = this.channel_nr === 0 ? CMOS_DISK_DRIVE1_CYL : CMOS_DISK_DRIVE2_CYL; // 0x1B : 0x24 (drive C: or D:) + rtc.cmos_write(drive_reg + 0, this.cylinder_count & 0xFF); // number of cylinders least significant byte + rtc.cmos_write(drive_reg + 1, this.cylinder_count >> 8 & 0xFF); // number of cylinders most significant byte + rtc.cmos_write(drive_reg + 2, this.head_count & 0xFF); // number of heads + rtc.cmos_write(drive_reg + 3, 0xFF); // write precomp cylinder least significant byte + rtc.cmos_write(drive_reg + 4, 0xFF); // write precomp cylinder most significant byte + rtc.cmos_write(drive_reg + 5, 0xC8); // control byte + rtc.cmos_write(drive_reg + 6, this.cylinder_count & 0xFF); // landing zone least significant byte + rtc.cmos_write(drive_reg + 7, this.cylinder_count >> 8 & 0xFF); // landing zone most significant byte + rtc.cmos_write(drive_reg + 8, this.sectors_per_track & 0xFF); // number of sectors + } if(this.channel.cpu) { From cb3274c7ee45160d9ee406d009550f17029292c9 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sun, 1 Jun 2025 13:39:07 +0200 Subject: [PATCH 118/301] fixed misdeclared register locations of BAR1 and BAR3 There was an overlap in the PCI space register declarations of the Floppy and the IDE controller. Since the conflicting registers were used exclusively by the two controllers this was fine, but 9front prints out this boot warning message: ioalloc: 3f0 - 3f5 floppy: clashes with: 3f4 - 3f5 PCI.0.31.0 Note that "PCI.0.31.0" is our IDE controller (with pci_id 0x1f). Problem: IDE actually uses only register address 0x3f6, but declared BAR1 for address range 0x3f4-0x3f7 which overlaps with the Floppy controller (the same with BAR3 and its address range of 0x374-0x377, but there was no overlap with other devices here). Fix: - changed BAR1 base address from 0x3f4 to 0x3f6 and its size from 4 to 1 - changed BAR3 base address from 0x374 to 0x376 and its size from 4 to 1 This commit fixes that problem and the 9front boot message from above. --- src/ide.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ide.js b/src/ide.js index 5a9e1e71..4240d67e 100644 --- a/src/ide.js +++ b/src/ide.js @@ -44,7 +44,7 @@ const HD_SECTOR_SIZE = 512; // Read-only registers: const ATA_REG_ERROR = 0x01; // Error register, see [ATA-6] 7.9 const ATA_REG_STATUS = 0x07; // Status register, see [ATA-6] 7.15 -const ATA_REG_ALT_STATUS = 0x02; // (*1*) Alternate Status register, see [ATA-6] 7.3 +const ATA_REG_ALT_STATUS = 0x00; // (*1*) Alternate Status register, see [ATA-6] 7.3 // Read-/writable registers: const ATA_REG_DATA = 0x00; // Data register, see [ATA-6] 7.6 const ATA_REG_SECTOR = 0x02; // Sector Count register, see [ATA-6] 7.14 @@ -55,7 +55,7 @@ const ATA_REG_DEVICE = 0x06; // Device register, see [ATA-6] 7.7 // Write-only registers: const ATA_REG_FEATURES = 0x01; // Features register, see [ATA-6] 7.10 const ATA_REG_COMMAND = 0x07; // Command register, see [ATA-6] 7.4 -const ATA_REG_CONTROL = 0x02; // (*1*) Device Control register, see [ATA-6] 7.8 +const ATA_REG_CONTROL = 0x00; // (*1*) Device Control register, see [ATA-6] 7.8 // Per-channel Bus Master IDE register offsets (BAR4), see [BMI-1] 2.0 // these are the primary channel's offsets, add 8 for secondary @@ -278,14 +278,14 @@ export function IDEController(cpu, bus, ide_config) if(has_primary) { this.primary = new IDEChannel(this, 0, ide_config[0], { - command_base: 0x1f0, control_base: 0x3f4, bus_master_base: bus_master_base, irq: 14 + command_base: 0x1f0, control_base: 0x3f6, bus_master_base: bus_master_base, irq: 14 }); this.channels[0] = this.primary; } if(has_secondary) { this.secondary = new IDEChannel(this, 1, ide_config[1], { - command_base: 0x170, control_base: 0x374, bus_master_base: bus_master_base, irq: 15 + command_base: 0x170, control_base: 0x376, bus_master_base: bus_master_base, irq: 15 }); this.channels[1] = this.secondary; } @@ -332,9 +332,9 @@ export function IDEController(cpu, bus, ide_config) ]; this.pci_bars = [ has_primary ? { size: 8 } : undefined, // BAR0: Command block register address of primary channel - has_primary ? { size: 4 } : undefined, // BAR1: Control block register address of primary channel + has_primary ? { size: 1 } : undefined, // BAR1: Control block register address of primary channel has_secondary ? { size: 8 } : undefined, // BAR2: Command block register address of secondary channel - has_secondary ? { size: 4 } : undefined, // BAR3: Control block register address of secondary channel + has_secondary ? { size: 1 } : undefined, // BAR3: Control block register address of secondary channel { size: 16 } // BAR4: Bus Master I/O register address of both channels (8+8) ]; cpu.devices.pci.register_device(this); @@ -565,7 +565,7 @@ function IDEChannel(controller, channel_nr, channel_config, hw_settings) }); // - // Control Block Register: control_base + 0...3 (BAR1: 3F4h, BAR3: 374h) + // Control Block Register: control_base (BAR1: 3F6h, BAR3: 376h) // // read Alternate Status register From 2ea8ff0baf1d8d63f7a23ddfcc7dec0a54651d5e Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 7 Jun 2025 17:15:00 +0700 Subject: [PATCH 119/301] make ide changes compatible with old state images --- src/cpu.js | 57 +++++++++++++++++++++++++++++++++++++++++++++--------- src/ide.js | 17 +++++++++++++++- src/pci.js | 5 ++++- 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/src/cpu.js b/src/cpu.js index 14663397..c57fcf69 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -537,8 +537,23 @@ CPU.prototype.get_state = function() state[53] = this.devices.ps2; state[54] = this.devices.uart0; state[55] = this.devices.fdc; - state[56] = this.devices.ide.secondary; - state[57] = this.devices.ide.primary ? this.devices.ide.primary : undefined; + + if(!this.devices.ide.secondary) + { + if(this.devices.ide.primary?.master.is_atapi) + { + state[56] = this.devices.ide.primary; + } + else + { + state[57] = this.devices.ide.primary; + } + } + else + { + state[85] = this.devices.ide; + } + state[58] = this.devices.pit; state[59] = this.devices.net; state[60] = this.get_state_pic(); @@ -684,7 +699,6 @@ CPU.prototype.set_state = function(state) this.devices.virtio_9p && this.devices.virtio_9p.set_state(state[45]); this.devices.apic && this.devices.apic.set_state(state[46]); this.devices.rtc && this.devices.rtc.set_state(state[47]); - this.devices.pci && this.devices.pci.set_state(state[48]); this.devices.dma && this.devices.dma.set_state(state[49]); this.devices.acpi && this.devices.acpi.set_state(state[50]); // 51 (formerly hpet) @@ -692,8 +706,32 @@ CPU.prototype.set_state = function(state) this.devices.ps2 && this.devices.ps2.set_state(state[53]); this.devices.uart0 && this.devices.uart0.set_state(state[54]); this.devices.fdc && this.devices.fdc.set_state(state[55]); - this.devices.ide.secondary.set_state(state[56]); - this.devices.ide.primary && this.devices.ide.primary.set_state(state[57]); + + if(state[56] || state[57]) + { + // ide device from older version of v86, only primary: state[56] contains cdrom, state[57] contains hard drive + + const ide_config = [[undefined, undefined], [undefined, undefined]]; + if(state[56]) + { + ide_config[0][0] = { is_cdrom: true, buffer: this.devices.cdrom.buffer }; + } + else + { + ide_config[0][0] = { is_cdrom: false, buffer: this.devices.ide.primary.master.buffer }; + + } + this.devices.ide = new IDEController(this, this.devices.ide.bus, ide_config); + this.devices.cdrom = state[56] ? this.devices.ide.primary.master : undefined; + this.devices.ide.primary.set_state(state[56] || state[57]); + } + else if(state[85]) + { + this.devices.ide.set_state(state[85]); + } + + this.devices.pci && this.devices.pci.set_state(state[48]); + this.devices.pit && this.devices.pit.set_state(state[58]); this.devices.net && this.devices.net.set_state(state[59]); this.set_state_pic(state[60]); @@ -1106,11 +1144,12 @@ CPU.prototype.init = function(settings, device_bus) this.devices.fdc = new FloppyController(this, settings.fda, settings.fdb); const ide_config = [[undefined, undefined], [undefined, undefined]]; - if(settings.hda) { - ide_config[0][0] = {buffer: settings.hda}; - ide_config[0][1] = {buffer: settings.hdb}; + if(settings.hda) + { + ide_config[0][0] = { buffer: settings.hda }; + ide_config[0][1] = { buffer: settings.hdb }; } - ide_config[1][0] = {is_cdrom: true, buffer: settings.cdrom}; + ide_config[1][0] = { is_cdrom: true, buffer: settings.cdrom }; this.devices.ide = new IDEController(this, device_bus, ide_config); this.devices.cdrom = this.devices.ide.secondary.master; diff --git a/src/ide.js b/src/ide.js index 4240d67e..23192770 100644 --- a/src/ide.js +++ b/src/ide.js @@ -301,7 +301,8 @@ export function IDEController(cpu, bus, ide_config) const command_base1 = has_secondary ? this.secondary.command_base : 0; const control_base1 = has_secondary ? this.secondary.control_base : 0; - this.pci_id = 0x1F << 3; + this.name = "ide"; + this.pci_id = 0x1E << 3; this.pci_space = [ vendor_id & 0xFF, vendor_id >> 8, device_id & 0xFF, device_id >> 8, 0x05, 0x00, 0xA0, 0x02, 0x00, prog_if, subclass, class_code, 0x00, 0x00, 0x00, 0x00, @@ -343,6 +344,20 @@ export function IDEController(cpu, bus, ide_config) Object.seal(this); } +IDEController.prototype.get_state = function() +{ + const state = []; + state[0] = this.primary; + state[1] = this.secondary; + return state; +}; + +IDEController.prototype.set_state = function(state) +{ + this.primary && this.primary.set_state(state[0]); + this.secondary && this.secondary.set_state(state[1]); +}; + /** * @constructor * @param {IDEController} controller diff --git a/src/pci.js b/src/pci.js index 6c775fe7..b3534f98 100644 --- a/src/pci.js +++ b/src/pci.js @@ -495,7 +495,10 @@ PCI.prototype.register_device = function(device) dbg_log("PCI register bdf=" + h(device_id) + " (" + device.name + ")", LOG_PCI); - dbg_assert(!this.devices[device_id]); + if(this.devices[device_id]) + { + dbg_log("warning: overwriting device " + this.devices[device_id].name + " with " + device.name, LOG_PCI); + } dbg_assert(device.pci_space.length >= 64); dbg_assert(device_id < this.devices.length); From d603a779db0170452a476542536de2863df68084 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 7 Jun 2025 17:17:56 +0700 Subject: [PATCH 120/301] minor cleanup --- src/ide.js | 58 +++++++++++++++++++++--------------------------------- 1 file changed, 22 insertions(+), 36 deletions(-) diff --git a/src/ide.js b/src/ide.js index 23192770..3661e08a 100644 --- a/src/ide.js +++ b/src/ide.js @@ -39,6 +39,8 @@ import { BusConnector } from "./bus.js"; const CDROM_SECTOR_SIZE = 2048; const HD_SECTOR_SIZE = 512; +const BUS_MASTER_BASE = 0xB400; + // Per-channel ATA register offsets, legend: // (*1*) Control block register (BAR1/3), else: Command block register (BAR0/2) // Read-only registers: @@ -84,7 +86,7 @@ const ATA_DR_DEV = 0x10; // Device select; slave device if set, else master de // Device Control register bits: // Bits 0x08/0x10/0x20/0x40 are reserved and bit 0x01 is always zero. -const ATA_CR_nIEN = 0x02; // Interrupt disable (not Interrupt ENable) +const ATA_CR_NIEN = 0x02; // Interrupt disable (not Interrupt ENable) const ATA_CR_SRST = 0x04; // Software reset const ATA_CR_HOB = 0x80; // 48-bit Address feature set @@ -266,28 +268,21 @@ export function IDEController(cpu, bus, ide_config) { this.cpu = cpu; this.bus = bus; + this.primary = undefined; this.secondary = undefined; - this.channels = [undefined, undefined]; const has_primary = ide_config && ide_config[0][0]; const has_secondary = ide_config && ide_config[1][0]; if(has_primary || has_secondary) { - const bus_master_base = 0xB400; if(has_primary) { - this.primary = new IDEChannel(this, 0, ide_config[0], { - command_base: 0x1f0, control_base: 0x3f6, bus_master_base: bus_master_base, irq: 14 - }); - this.channels[0] = this.primary; + this.primary = new IDEChannel(this, 0, ide_config[0], 0x1F0, 0x3F6, 14); } if(has_secondary) { - this.secondary = new IDEChannel(this, 1, ide_config[1], { - command_base: 0x170, control_base: 0x376, bus_master_base: bus_master_base, irq: 15 - }); - this.channels[1] = this.secondary; + this.secondary = new IDEChannel(this, 1, ide_config[1], 0x170, 0x376, 15); } const vendor_id = 0x8086; // Intel Corporation @@ -310,7 +305,7 @@ export function IDEController(cpu, bus, ide_config) control_base0 & 0xFF | 1, control_base0 >> 8, 0x00, 0x00, command_base1 & 0xFF | 1, command_base1 >> 8, 0x00, 0x00, control_base1 & 0xFF | 1, control_base1 >> 8, 0x00, 0x00, - bus_master_base & 0xFF | 1, bus_master_base >> 8, 0x00, 0x00, + BUS_MASTER_BASE & 0xFF | 1, BUS_MASTER_BASE >> 8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x10, 0xD4, 0x82, @@ -363,29 +358,26 @@ IDEController.prototype.set_state = function(state) * @param {IDEController} controller * @param {number} channel_nr * */ -function IDEChannel(controller, channel_nr, channel_config, hw_settings) +function IDEChannel(controller, channel_nr, channel_config, command_base, control_base, irq) { this.controller = controller; this.channel_nr = channel_nr; this.cpu = controller.cpu; this.bus = controller.bus; - this.command_base = hw_settings.command_base; - this.control_base = hw_settings.control_base; - this.irq = hw_settings.irq; + this.command_base = command_base; + this.control_base = control_base; + this.irq = irq; this.name = "ide" + channel_nr; const master_cfg = channel_config ? channel_config[0] : undefined; const slave_cfg = channel_config ? channel_config[1] : undefined; - this.master = new IDEInterface(this, 0, master_cfg ? master_cfg.buffer : undefined, master_cfg ? !!master_cfg.is_cdrom : false); - this.slave = new IDEInterface(this, 1, slave_cfg ? slave_cfg.buffer : undefined, slave_cfg ? !!slave_cfg.is_cdrom : false); + this.master = new IDEInterface(this, 0, master_cfg?.buffer, master_cfg?.is_cdrom); + this.slave = new IDEInterface(this, 1, slave_cfg?.buffer, slave_cfg?.is_cdrom); - this.interfaces = [this.master, this.slave]; this.current_interface = this.master; - this.master.init_interface(); - this.slave.init_interface(); /** @type {number} */ - this.device_control_reg = ATA_CR_nIEN; + this.device_control_reg = ATA_CR_NIEN; /** @type {number} */ this.prdt_addr = 0; @@ -594,7 +586,7 @@ function IDEChannel(controller, channel_nr, channel_config, hw_settings) // primary channel: bus_master_base + 0...7, secondary: bus_master_base + 8...15 // - const bus_master_base = hw_settings.bus_master_base + channel_nr * 8; + const bus_master_base = BUS_MASTER_BASE + channel_nr * 8; // read/write Bus Master IDE Command register cpu.io.register_read(bus_master_base | BMI_REG_COMMAND, @@ -627,7 +619,7 @@ IDEChannel.prototype.write_control = function(data) if(LOG_DETAILS & (LOG_DETAIL_REG_IO | LOG_DETAIL_IRQ)) { dbg_log(this.current_interface.name + ": write Device Control register: " + - h(data, 2) + " interrupts " + ((data & ATA_CR_nIEN) ? "disabled" : "enabled"), LOG_DISK); + h(data, 2) + " interrupts " + ((data & ATA_CR_NIEN) ? "disabled" : "enabled"), LOG_DISK); } if(data & ATA_CR_SRST) { @@ -748,7 +740,7 @@ IDEChannel.prototype.dma_write_command8 = function(value) IDEChannel.prototype.push_irq = function() { - if((this.device_control_reg & ATA_CR_nIEN) === 0) + if((this.device_control_reg & ATA_CR_NIEN) === 0) { if(LOG_DETAILS & LOG_DETAIL_IRQ) { @@ -821,7 +813,7 @@ function IDEInterface(channel, interface_nr, buffer, is_cd) this.buffer = null; /** @type {boolean} */ - this.drive_connected = is_cd || buffer; + this.drive_connected = is_cd || !!buffer; /** @type {number} */ this.sector_size = is_cd ? CDROM_SECTOR_SIZE : HD_SECTOR_SIZE; @@ -911,21 +903,15 @@ function IDEInterface(channel, interface_nr, buffer, is_cd) /** @type {boolean} */ this.medium_changed = false; - // caller must call this.init_interface() to complete object initialization - this.inital_buffer = buffer; -} - -IDEInterface.prototype.init_interface = function() -{ - this.set_disk_buffer(this.inital_buffer); - delete this.inital_buffer; - Object.seal(this); + this.set_disk_buffer(buffer); if(this.drive_connected) { dbg_log(`${this.name}: ${this.is_atapi ? "ATAPI CD-ROM" : "ATA HD"} device ready`, LOG_DISK); } -}; + + Object.seal(this); +} IDEInterface.prototype.has_disk = function() { From 10bb5bc0fa7380b8421133fe28a2bfab1e07435f Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Sun, 8 Jun 2025 22:00:08 +0200 Subject: [PATCH 121/301] raise IRQ only if IRQ bit in DOR is enabled The floppy controller's Digital Output Register (DOR) is a read/writeable 8 bit register. Amongst other things, this register allows the guest to reset the floppy controller ("nRESET" bit 0x8) and to enable/disable the use of the controller's IRQ line ("IRQ" bit 0x4). Note that the "nRESET" bit is negated ("not RESET"). The floppy controller is expected to raise an IRQ when exiting the RESET state, meaning when the guest changes the nRESET bit from 0 to 1. That is what is currently done in the DOR write handler in floppy.js. But the floppy controller is also expected to raise IRQs only if the IRQ bit in DOR is set, and this was not considered in the DOR write handler. This commit changes the DOR write handler to raise the IRQ only if the IRQ bit is also set in the new DOR value received from the guest. --- src/floppy.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/floppy.js b/src/floppy.js index 437e8b66..13fdaed0 100644 --- a/src/floppy.js +++ b/src/floppy.js @@ -343,16 +343,23 @@ FloppyController.prototype.port3F2_read = function() FloppyController.prototype.port3F2_write = function(value) { - if((value & 4) === 4 && (this.dor & 4) === 0) + // write Digital Output Register (DOR), relevant bits here: + // 0x4 (nRESET): 0: RESET mode, 1: NORMAL mode + // 0x8 (IRQ_DMA): 0: disable IRQ/DMA, 1: enable IRQ/DMA + if(!(this.dor & 0x4) && (value & 0x4)) { - // clear reset mode + // nRESET bit transition from 0 to 1: exit from RESET state this.status_reg0 = 0xC0; - this.cpu.device_raise_irq(6); + if(value & 0x8) + { + // raise IRQ only if IRQ_DMA bit is 1 (in new DOR value) + this.cpu.device_raise_irq(6); + } } dbg_log("start motors: " + h(value >> 4), LOG_FLOPPY); dbg_log("enable dma/irq: " + !!(value & 8), LOG_FLOPPY); - dbg_log("reset fdc: " + !!(value & 4), LOG_FLOPPY); + dbg_log("reset fdc: " + !(value & 4), LOG_FLOPPY); dbg_log("drive select: " + (value & 3), LOG_FLOPPY); if((value & 3) !== 0) { From 197d43cb76d7263a0f0805452a10c79aa052feab Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 28 Apr 2025 15:31:54 +0800 Subject: [PATCH 122/301] fix profile name --- src/browser/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/main.js b/src/browser/main.js index 7f71e74b..4240f7ca 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1419,7 +1419,7 @@ function onload() }, { id: "netboot.xyz", - name: "iPXE", + name: "netboot.xyz", cdrom: { url: host + "netboot.xyz.iso", size: 2398208, From f81470a762563f3f1521e05237adf4105aaa31d5 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sun, 1 Jun 2025 22:03:17 +0700 Subject: [PATCH 123/301] minor --- tests/benchmark/fetch-download.js | 8 ++++---- tests/devices/fetch_network.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/benchmark/fetch-download.js b/tests/benchmark/fetch-download.js index 61264e0c..06ca72c0 100755 --- a/tests/benchmark/fetch-download.js +++ b/tests/benchmark/fetch-download.js @@ -36,9 +36,9 @@ if(isMainThread) emulator.run(); }); - var SERVER_PORT = 0; - var serial_text = ""; - var booted = false; + let SERVER_PORT = 0; + let serial_text = ""; + let booted = false; emulator.bus.register("emulator-started", function() { @@ -73,7 +73,7 @@ else const benchsize = workerData; const benchfile = Buffer.alloc(benchsize); - var server = createServer(function(_, response) { + const server = createServer(function(_, response) { response.setHeader("content-type", "application/octet-stream"); response.setHeader("content-length", benchsize.toString(10)); response.write(benchfile); diff --git a/tests/devices/fetch_network.js b/tests/devices/fetch_network.js index 7e0d6bb7..8f7d94ce 100755 --- a/tests/devices/fetch_network.js +++ b/tests/devices/fetch_network.js @@ -15,14 +15,14 @@ const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); const SHOW_LOGS = false; -var SERVER_PORT = 0; - function wait(time) { return new Promise((res) => setTimeout(res, time)); } if(isMainThread) { + let SERVER_PORT = 0; + const tests = [ { From 1a0f31e34f86ffa2dc3f0ceb321a8237633dd07e Mon Sep 17 00:00:00 2001 From: Fabian Date: Sun, 1 Jun 2025 22:14:51 +0700 Subject: [PATCH 124/301] add SqueakNOS --- src/browser/main.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/browser/main.js b/src/browser/main.js index 4240f7ca..6b58e8d5 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1428,6 +1428,17 @@ function onload() homepage: "https://netboot.xyz/", net_device_type: "virtio", }, + { + id: "squeaknos", + name: "SqueakNOS", + cdrom: { + url: host + "SqueakNOS.iso", + size: 61171712, + async: false, + }, + memory_size: 512 * 1024 * 1024, + homepage: "https://squeaknos.blogspot.com/" + }, ]; if(DEBUG) From ed34b063393cc4aae2999065d35141bd10a17034 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sun, 1 Jun 2025 23:57:02 +0700 Subject: [PATCH 125/301] add Chokanji 4 --- src/browser/main.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/browser/main.js b/src/browser/main.js index 6b58e8d5..7f7bc05d 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1439,6 +1439,19 @@ function onload() memory_size: 512 * 1024 * 1024, homepage: "https://squeaknos.blogspot.com/" }, + { + id: "chokanji4", + name: "Chokanji 4", + hda: { + url: host + "chokanji4/.img.zst", + size: 10737418240, + async: true, + fixed_chunk_size: 256 * 1024, + use_parts: true, + }, + memory_size: 512 * 1024 * 1024, + homepage: "https://archive.org/details/brightv4000" + }, ]; if(DEBUG) From 45c4a7d0e14b6f0881620b40452a1cc4ee85f966 Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 2 Jun 2025 00:09:51 +0700 Subject: [PATCH 126/301] make exit button preserve settings --- src/browser/main.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/browser/main.js b/src/browser/main.js index 7f7bc05d..31b63ef0 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1536,15 +1536,7 @@ function onload() if(query_args.has("hda.url") || query_args.has("cdrom.url") || query_args.has("fda.url")) { start_emulation(null, query_args); - } - else - { - if(query_args.has("m")) $("memory_size").value = query_args.get("m"); - if(query_args.has("vram")) $("vga_memory_size").value = query_args.get("vram"); - if(query_args.has("relay_url")) $("relay_url").value = query_args.get("relay_url"); - if(query_args.has("mute")) $("disable_audio").checked = bool_arg(query_args.get("mute")); - if(query_args.has("acpi")) $("acpi").checked = bool_arg(query_args.get("acpi")); - if(query_args.has("boot_order")) $("boot_order").value = query_args.get("boot_order"); + return; } } else if(/^[a-zA-Z0-9\-_]+\/[a-zA-Z0-9\-_]+$/g.test(profile)) @@ -1581,6 +1573,13 @@ function onload() }); } + if(query_args.has("m")) $("memory_size").value = query_args.get("m"); + if(query_args.has("vram")) $("vga_memory_size").value = query_args.get("vram"); + if(query_args.has("relay_url")) $("relay_url").value = query_args.get("relay_url"); + if(query_args.has("mute")) $("disable_audio").checked = bool_arg(query_args.get("mute")); + if(query_args.has("acpi")) $("acpi").checked = bool_arg(query_args.get("acpi")); + if(query_args.has("boot_order")) $("boot_order").value = query_args.get("boot_order"); + const os_info = Array.from(document.querySelectorAll("#oses tbody tr")).map(element => { const [_, size_raw, unit] = element.children[1].textContent.match(/([\d\.]+)\+? (\w+)/); @@ -2158,7 +2157,9 @@ function init_ui(profile, settings, emulator) $("exit").onclick = function() { emulator.destroy(); - location.href = location.pathname; + const url = new URL(location.href); + url.searchParams.delete("profile"); + location.href = url.pathname + url.search; }; $("lock_mouse").onclick = function() From 44fb0d57ea664b948d969f25c611596da9585add Mon Sep 17 00:00:00 2001 From: Fabian Date: Wed, 4 Jun 2025 12:13:41 +0700 Subject: [PATCH 127/301] reduce verbosity of cpu logging --- src/rust/cpu/cpu.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/rust/cpu/cpu.rs b/src/rust/cpu/cpu.rs index 097002e0..7b25d796 100644 --- a/src/rust/cpu/cpu.rs +++ b/src/rust/cpu/cpu.rs @@ -713,13 +713,13 @@ pub unsafe fn iret(is_16: bool) { let executable = access & 8 == 8; let conforming = access & 4 == 4; if dpl < *cpl && !(executable && conforming) { - dbg_log!( - "set segment to null sreg={} dpl={} executable={} conforming={}", - reg, - dpl, - executable, - conforming - ); + //dbg_log!( + // "set segment to null sreg={} dpl={} executable={} conforming={}", + // reg, + // dpl, + // executable, + // conforming + //); *segment_is_null.offset(reg as isize) = true; *sreg.offset(reg as isize) = 0; } From c8facf37f505a91ba752382e16d9d74e8f230933 Mon Sep 17 00:00:00 2001 From: Fabian Date: Wed, 4 Jun 2025 16:03:30 +0700 Subject: [PATCH 128/301] add note on chromium bug --- src/browser/mouse.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/browser/mouse.js b/src/browser/mouse.js index 7147162b..12ad6fbd 100644 --- a/src/browser/mouse.js +++ b/src/browser/mouse.js @@ -220,6 +220,9 @@ export function MouseAdapter(bus, screen_container) delta_y = -delta_y; + // NOTE: affected by https://issues.chromium.org/issues/40737979 + // Causes cursor jumps on multi-monitor and/or 120+ HZ monitors + mouse.bus.send("mouse-delta", [delta_x, delta_y]); if(screen_container) From 258f98560b044ee3ea8c0f4980c47e204403d896 Mon Sep 17 00:00:00 2001 From: Fabian Date: Wed, 4 Jun 2025 16:03:52 +0700 Subject: [PATCH 129/301] don't error on floppy command 0x14 (fiwix) --- src/floppy.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/floppy.js b/src/floppy.js index 13fdaed0..ed738688 100644 --- a/src/floppy.js +++ b/src/floppy.js @@ -286,6 +286,7 @@ FloppyController.prototype.port3F5_write = function(reg_byte) this.bytes_expecting = 3; break; case 0x04: + case 0x14: this.next_command = this.check_drive_status; this.bytes_expecting = 1; break; From aa6bfcfb32ad56881fb1e027f8996a2041d6a482 Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 10 Jun 2025 14:36:03 +0700 Subject: [PATCH 130/301] fix boot order url parameter --- src/browser/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/main.js b/src/browser/main.js index 31b63ef0..7ec183a0 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1978,7 +1978,7 @@ function start_emulation(profile, query_args) { settings.boot_order = boot_order; } - if(settings.boot_order !== DEFAULT_BOOT_ORDER) new_query_args.set("boot_order", String(settings.boot_order)); + if(settings.boot_order !== DEFAULT_BOOT_ORDER) new_query_args.set("boot_order", settings.boot_order.toString(16)); if(settings.acpi === undefined) { From 6d54013976f0233e0d075ae90bdc983549046094 Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 10 Jun 2025 15:39:58 +0700 Subject: [PATCH 131/301] minor: add missing LOG_DISK --- src/ide.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ide.js b/src/ide.js index 3661e08a..25d62c73 100644 --- a/src/ide.js +++ b/src/ide.js @@ -1743,7 +1743,7 @@ IDEInterface.prototype.atapi_read_dma = function(cmd) this.read_buffer(start, byte_count, (data) => { - dbg_log(this.name + ": atapi_read_dma: Data arrived"); + dbg_log(this.name + ": atapi_read_dma: Data arrived", LOG_DISK); this.report_read_end(byte_count); this.status_reg = ATA_SR_DRDY|ATA_SR_DSC|ATA_SR_DRQ; this.sector_count_reg = this.sector_count_reg & ~7 | 2; From a4e496768307701e69652657adf1ebdd0e132506 Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 10 Jun 2025 16:45:37 +0700 Subject: [PATCH 132/301] less bold --- v86.css | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/v86.css b/v86.css index 5088ef52..383f647d 100644 --- a/v86.css +++ b/v86.css @@ -102,10 +102,8 @@ h4 { margin-bottom: 10px; outline: 1px solid #555; } -#boot_options a { - text-decoration: none; +#oses a { font-weight: bold; - font-size: 16px; } #boot_options td, #boot_options th { padding: 1px 7px; From 05acfb12eba0377cb61800964feee325a04587e9 Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 10 Jun 2025 17:41:45 +0700 Subject: [PATCH 133/301] add cdrom insert/eject test --- Makefile | 1 + tests/api/cdrom-insert-eject.js | 52 +++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100755 tests/api/cdrom-insert-eject.js diff --git a/Makefile b/Makefile index 2718c47a..aff899ce 100644 --- a/Makefile +++ b/Makefile @@ -361,6 +361,7 @@ api-tests: build/v86-debug.wasm ./tests/api/state.js ./tests/api/reset.js #./tests/api/floppy-insert-eject.js # disabled for now, sometimes hangs + ./tests/api/cdrom-insert-eject.js ./tests/api/serial.js ./tests/api/reboot.js ./tests/api/pic.js diff --git a/tests/api/cdrom-insert-eject.js b/tests/api/cdrom-insert-eject.js new file mode 100755 index 00000000..4e9f8974 --- /dev/null +++ b/tests/api/cdrom-insert-eject.js @@ -0,0 +1,52 @@ +#!/usr/bin/env node + +import { setTimeout as pause } from "timers/promises"; +import url from "node:url"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + +const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; +const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); + +process.on("unhandledRejection", exn => { throw exn; }); + +const emulator = new V86({ + bios: { url: __dirname + "/../../bios/seabios.bin" }, + vga_bios: { url: __dirname + "/../../bios/vgabios.bin" }, + hda: { url: __dirname + "/../../images/msdos622.img" }, + network_relay_url: "", + autostart: true, + memory_size: 32 * 1024 * 1024, + filesystem: {}, + log_level: 0, + disable_jit: +process.env.DISABLE_JIT, +}); + +//const interval = setInterval(() => { +// console.warn(emulator.screen_adapter.get_text_screen()); +//}, 1000); + +const timeout = setTimeout(() => { + console.warn(emulator.screen_adapter.get_text_screen()); + throw new Error("Timeout"); +}, 60 * 1000); + +setTimeout(async () => +{ + await emulator.wait_until_vga_screen_contains("C:\\> "); + console.log("Got C:\\>"); + await pause(1000); + emulator.keyboard_send_text("dir D:\n"); + await emulator.wait_until_vga_screen_contains("Abort, Retry, Fail?"); + console.log("Got Abort, Retry, Fail?"); + await pause(1000); + emulator.keyboard_send_text("a"); + emulator.set_cdrom({ url: __dirname + "/../../images/linux4.iso" }); + await pause(1000); + emulator.keyboard_send_text("dir D:\n"); + await emulator.wait_until_vga_screen_contains("BOOT "); + console.log("Got BOOT"); + emulator.destroy(); + clearTimeout(timeout); + //clearInterval(interval); +}, 1000); From f28837785b76088176f9815f38c6471b0be892e4 Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 10 Jun 2025 17:43:20 +0700 Subject: [PATCH 134/301] add tests for cdrom+hda+hdb --- tests/full/run.js | 54 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/tests/full/run.js b/tests/full/run.js index 57e5746c..1efbb22a 100755 --- a/tests/full/run.js +++ b/tests/full/run.js @@ -546,6 +546,38 @@ if(cluster.isPrimary) expect_graphical_size: [1024, 768], expect_mouse_registered: true, }, + { + name: "Arch Linux (with fda, cdrom, hda and hdb)", + skip_if_disk_image_missing: true, + timeout: 5 * 60, + bzimage_initrd_from_filesystem: true, + memory_size: 512 * 1024 * 1024, + cmdline: [ + "rw apm=off vga=0x344 video=vesafb:ypan,vremap:8", + "root=host9p rootfstype=9p rootflags=trans=virtio,cache=loose mitigations=off", + "audit=0 init=/usr/bin/init-openrc net.ifnames=0 biosdevname=0", + ].join(" "), + filesystem: { + basefs: "images/fs.json", + baseurl: "images/arch/", + }, + hda: root_path + "/images/w95.img", + hdb: root_path + "/images/FiwixOS-3.4-i386.img", + cdrom: root_path + "/images/dsl-4.11.rc2.iso", + fda: root_path + "/images/freedos722.img", + actions: [ + { + on_text: "root@localhost", + run: "modprobe floppy && mkdir /mnt/{a,b,c,f} && mount /dev/sda1 /mnt/a && mount /dev/sdb2 /mnt/b && mount /dev/sr0 /mnt/c && mount /dev/fd0 /mnt/f && ls /mnt/*\n", + }, + ], + expected_texts: [ + "bin dev home", // fiwix + " AUTOEXEC.BAT CONFIG.WIN MSDOS.SYS", // w95 + "KNOPPIX boot index.html", // DSL + "FDOS README debug.com", // freedos + ], + }, { name: "FreeGEM", skip_if_disk_image_missing: true, @@ -871,18 +903,31 @@ if(cluster.isPrimary) actions: [{ on_text: " BIOS default device boot in", run: "\n", after: 5000 }], }, { - name: "Core 9 (with floppy disk)", + name: "Core 9 (with hard disk)", skip_if_disk_image_missing: 1, timeout: 5 * 60, cdrom: root_path + "/images/experimental/os/Core-9.0.iso", fda: root_path + "/images/freedos722.img", - boot_order: 0x132, + boot_order: 0x213, actions: [ { on_text: "boot:", run: "\n" }, { on_text: "tc@box", run: "sudo mount /dev/fd0 /mnt && ls /mnt\n" }, ], expected_texts: ["AUTOEXEC.BAT"], }, + { + name: "Core 9 (with hard disk)", + skip_if_disk_image_missing: 1, + timeout: 5 * 60, + cdrom: root_path + "/images/experimental/os/Core-9.0.iso", + hda: root_path + "/images/TinyCore-11.0.iso", + boot_order: 0x213, + actions: [ + { on_text: "boot:", run: "\n" }, + { on_text: "tc@box", run: "sudo mount /dev/sda1 /mnt && ls /mnt\n" }, + ], + expected_texts: ["boot/ cde/"], + }, { name: "Core 8", skip_if_disk_image_missing: 1, @@ -1049,7 +1094,6 @@ function run_test(test, done) autostart: true, memory_size: test.memory_size || 128 * 1024 * 1024, log_level: +process.env.LOG_LEVEL || 0, - cmdline: test.cmdline, }; if(test.cdrom) @@ -1064,6 +1108,10 @@ function run_test(test, done) { settings.hda = { url: test.hda, async: true }; } + if(test.hdb) + { + settings.hdb = { url: test.hdb, async: true }; + } if(test.bzimage) { settings.bzimage = { url: test.bzimage }; From a4b86d85e23df75cc455d4996ca204ed311b8bf6 Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 10 Jun 2025 17:45:00 +0700 Subject: [PATCH 135/301] create empty disks and second hard drive --- debug.html | 4 +-- index.html | 7 ++++- src/browser/main.js | 69 +++++++++++++++++++++++++++++++++++++++++++-- v86.css | 3 ++ 4 files changed, 77 insertions(+), 6 deletions(-) diff --git a/debug.html b/debug.html index f37feb70..fa942236 100644 --- a/debug.html +++ b/debug.html @@ -92,12 +92,12 @@ -
+ or create empty disk
Second hard disk image -
+ or create empty disk
diff --git a/index.html b/index.html index 35591075..f787b0f2 100644 --- a/index.html +++ b/index.html @@ -132,7 +132,12 @@ -
+ or create empty disk
+ + + + + or create empty disk
- +

diff --git a/src/browser/main.js b/src/browser/main.js index b41c464b..4a4bf27e 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1667,7 +1667,7 @@ function onload() }; } - const os_info = Array.from(document.querySelectorAll("#oses tbody tr")).map(element => + const os_info = Array.from(document.querySelectorAll("#oses a.tr")).map(element => { const [_, size_raw, unit] = element.children[1].textContent.match(/([\d\.]+)\+? (\w+)/); let size = +size_raw; @@ -1764,6 +1764,15 @@ function onload() } } + $("reset_filters").onclick = function() + { + for(const element of document.querySelectorAll("#filter input[type=checkbox]")) + { + element.checked = false; + } + update_filters(); + }; + function set_proxy_value(id, value) { const elem = $(id); From b0d20cee00412064dfe11370778a4fb664541b1b Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 8 Sep 2025 15:24:47 -0300 Subject: [PATCH 255/301] add BSD/OS, Mojo OS, XENUS, Vanadium OS, Asuro and PrettyOS thanks @SuperMaxusa --- src/browser/main.js | 77 +++++++++++++++++++++++++++++++++++++++++++++ tests/full/run.js | 43 +++++++++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/src/browser/main.js b/src/browser/main.js index 4a4bf27e..31912dfb 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1481,6 +1481,83 @@ function onload() memory_size: 512 * 1024 * 1024, homepage: "https://archhurd.org/", }, + { + id: "prettyos", + name: "PrettyOS", + fda: { + url: host + "prettyos.img", + size: 1474560, + async: false, + }, + homepage: "https://www.prettyos.de/Image.html", + }, + { + id: "vanadium", + name: "Vanadium OS", + cdrom: { + url: host + "vanadiumos.iso", + size: 8388608, + async: false, + }, + homepage: "https://www.durlej.net/software.html", + }, + { + id: "xenus", + name: "XENUS", + hda: { + url: host + "xenushdd.img", + size: 52428800, + async: false, + }, + homepage: "https://www.durlej.net/xenus/", + }, + { + id: "mojo", + name: "Mojo OS", + cdrom: { + url: host + "mojo-0.2.2.iso", + size: 4048896, + async: false, + }, + homepage: "https://archiveos.org/mojoos/", + }, + { + id: "bsdos", + memory_size: 128 * 1024 * 1024, + name: "BSD/OS", + hda: { + url: host + "bsdos43/.img.zst", + size: 1024 * 1024 * 1024, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + state: { url: host + "bsdos43_state.bin" }, + homepage: "https://en.wikipedia.org/wiki/BSD/OS", + }, + { + id: "bsdos-boot", + memory_size: 128 * 1024 * 1024, + name: "BSD/OS", + hda: { + url: host + "bsdos43/.img.zst", + size: 1024 * 1024 * 1024, + async: true, + fixed_chunk_size: 1024 * 1024, + use_parts: true, + }, + homepage: "https://en.wikipedia.org/wiki/BSD/OS", + }, + { + id: "asuro", + name: "Asuro", + cdrom: { + url: host + "asuro.iso", + size: 5361664, + async: false, + }, + homepage: "https://asuro.xyz/", + }, ]; if(DEBUG) diff --git a/tests/full/run.js b/tests/full/run.js index f8f57c76..bd2a5cc6 100755 --- a/tests/full/run.js +++ b/tests/full/run.js @@ -794,6 +794,49 @@ if(cluster.isPrimary) ], expected_texts: ["login:", "We'd like your feedback", "# "], }, + { + name: "Mojo OS", + skip_if_disk_image_missing: true, + timeout: 60, + cdrom: root_path + "/images/mojo-0.2.2.iso", + actions: [ + { + on_text: "/> ", + run: "help\n", + }, + ], + expected_texts: ["Mojo test shell", "See manual pages for more information"], + expected_serial_text: [" ===> Shell loaded"], + expect_mouse_registered: true, + }, + { + name: "Vanadium OS", + skip_if_disk_image_missing: true, + timeout: 60, + cdrom: root_path + "/images/vanadiumos.iso", + actions: [ + { after: 2000, run: " " }, + { after: 2100, run: " " }, + { after: 2200, run: " " }, + { after: 2300, run: " " }, + { after: 2400, run: " " }, + { after: 2500, run: " " }, + { after: 2600, run: " " }, + { after: 2700, run: " " }, + { after: 2800, run: "c" }, + ], + expect_mouse_registered: true, + expect_graphical_mode: true, + }, + { + name: "Asuro", + skip_if_disk_image_missing: true, + timeout: 60, + cdrom: root_path + "/images/asuro.iso", + expect_mouse_registered: true, + expect_graphical_mode: true, + expected_serial_text: ["Asuro Booted Correctly!"], + }, { name: "Mobius", skip_if_disk_image_missing: true, From 2f65f9d6d1dd3e59ca6d0e0887b73e0d5cfa62a8 Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 8 Sep 2025 16:59:56 -0300 Subject: [PATCH 256/301] use new task scheduling api if available about 2% faster than the worker-based yield function --- src/main.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main.js b/src/main.js index 81cc6103..9f78b165 100644 --- a/src/main.js +++ b/src/main.js @@ -114,6 +114,17 @@ if(typeof process !== "undefined") v86.prototype.register_yield = function() {}; v86.prototype.unregister_yield = function() {}; } +else if(window["scheduler"] && typeof window["scheduler"]["postTask"] === "function" && !location.href.includes("dont-use-scheduling-api")) +{ + v86.prototype.yield = function(t, tick) + { + t = Math.max(0, t); + window["scheduler"]["postTask"](() => this.yield_callback(tick), { delay: t }); + }; + + v86.prototype.register_yield = function() {}; + v86.prototype.unregister_yield = function() {}; +} else if(typeof Worker !== "undefined") { // XXX: This has a slightly lower throughput compared to window.postMessage From 7c4cbd0b04961554dc86662a7c4eb6660b012cdb Mon Sep 17 00:00:00 2001 From: Fabian Date: Wed, 10 Sep 2025 16:04:02 -0300 Subject: [PATCH 257/301] fix: correct field indices for restoring old state images with apic --- src/cpu.js | 18 +++++++++--------- src/rust/cpu/apic.rs | 6 +++++- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/cpu.js b/src/cpu.js index 6e572543..67659731 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -822,15 +822,15 @@ CPU.prototype.set_state_apic = function(state) apic[2] = state[2]; // timer_divider_shift apic[3] = state[3]; // timer_initial_count apic[4] = state[4]; // timer_current_count - // skip next_tick (in js: state[4]; in rust: apic[5] and apic[6]) - apic[7] = state[6]; // lvt_timer - apic[8] = state[7]; // lvt_perf_counter - apic[9] = state[8]; // lvt_int0 - apic[10] = state[9]; // lvt_int1 - apic[11] = state[10]; // lvt_error - apic[12] = state[11]; // tpr - apic[13] = state[12]; // icr0 - apic[14] = state[13]; // icr1 + // skip next_tick (in js: state[4]; in rust: apic[6] and apic[7]) + apic[8] = state[6]; // lvt_timer + apic[9] = state[7]; // lvt_perf_counter + apic[10] = state[8]; // lvt_int0 + apic[11] = state[9]; // lvt_int1 + apic[12] = state[10]; // lvt_error + apic[13] = state[11]; // tpr + apic[14] = state[12]; // icr0 + apic[15] = state[13]; // icr1 apic.set(state[15], 16); // irr apic.set(state[15], 24); // isr apic.set(state[16], 32); // tmr diff --git a/src/rust/cpu/apic.rs b/src/rust/cpu/apic.rs index 1ef1dd75..e9da04df 100644 --- a/src/rust/cpu/apic.rs +++ b/src/rust/cpu/apic.rs @@ -39,7 +39,11 @@ const IOAPIC_DELIVERY_FIXED: u8 = 0; const APIC_STRUCT_SIZE: usize = 4 * 46; // Note: JavaScript (cpu.get_state_apic) depens on this layout +const _: () = assert!(std::mem::offset_of!(Apic, timer_last_tick) == 6 * 4); +const _: () = assert!(std::mem::offset_of!(Apic, lvt_timer) == 8 * 4); +const _: () = assert!(std::mem::offset_of!(Apic, lvt_perf_counter) == 9 * 4); const _: () = assert!(std::mem::offset_of!(Apic, icr0) == 14 * 4); +const _: () = assert!(std::mem::offset_of!(Apic, icr1) == 15 * 4); const _: () = assert!(std::mem::offset_of!(Apic, irr) == 16 * 4); const _: () = assert!(std::mem::offset_of!(Apic, isr) == 24 * 4); const _: () = assert!(std::mem::offset_of!(Apic, tmr) == 32 * 4); @@ -552,7 +556,7 @@ fn deliver(apic: &mut Apic, vector: u8, mode: u8, is_level: bool) { } if vector < 0x10 || vector == 0xFF { - dbg_assert!(false, "TODO: Invalid vector"); + dbg_assert!(false, "TODO: Invalid vector: {:x}", vector); } if register_get_bit(&apic.irr, vector) { From be7a8aa8747c95690eb92c6757462b738daa748b Mon Sep 17 00:00:00 2001 From: Fabian Date: Wed, 10 Sep 2025 16:15:44 -0300 Subject: [PATCH 258/301] apic: reduce verbosity of logging --- src/rust/cpu/apic.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/rust/cpu/apic.rs b/src/rust/cpu/apic.rs index e9da04df..3f845db7 100644 --- a/src/rust/cpu/apic.rs +++ b/src/rust/cpu/apic.rs @@ -191,7 +191,9 @@ fn read32_internal(apic: &mut Apic, addr: u32) -> u32 { }, 0x320 => { - dbg_log!("read timer lvt"); + if APIC_LOG_VERBOSE { + dbg_log!("read timer lvt"); + } apic.lvt_timer }, @@ -391,7 +393,9 @@ fn write32_internal(apic: &mut Apic, addr: u32, value: u32) { }, 0x320 => { - dbg_log!("timer lvt: {:08x}", value); + if APIC_LOG_VERBOSE { + dbg_log!("timer lvt: {:08x}", value); + } // TODO: check if unmasking and if this should trigger an interrupt immediately apic.lvt_timer = value; }, From f6083ac6b0d10a0e0e7155f45da808525799eccb Mon Sep 17 00:00:00 2001 From: Fabian Date: Wed, 10 Sep 2025 16:24:18 -0300 Subject: [PATCH 259/301] upgrade haiku to beta5 fixes rare crash thanks @SuperMaxusa --- src/browser/main.js | 12 +++++++----- tests/full/run.js | 5 +++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/browser/main.js b/src/browser/main.js index 31912dfb..7788ad16 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -294,28 +294,30 @@ function onload() id: "haiku", memory_size: 512 * 1024 * 1024, hda: { - url: host + "haiku-v4/.img", - size: 1 * 1024 * 1024 * 1024, + url: host + "haiku-v5/.img", + size: 1342177280, async: true, fixed_chunk_size: 1024 * 1024, use_parts: true, }, - state: { url: host + "haiku_state-v4.bin.zst" }, + state: { url: host + "haiku_state-v5.bin.zst" }, name: "Haiku", homepage: "https://www.haiku-os.org/", + acpi: true, }, { id: "haiku-boot", memory_size: 512 * 1024 * 1024, hda: { - url: host + "haiku-v4/.img", - size: 1 * 1024 * 1024 * 1024, + url: host + "haiku-v5/.img", + size: 1342177280, async: true, fixed_chunk_size: 1024 * 1024, use_parts: true, }, name: "Haiku", homepage: "https://www.haiku-os.org/", + acpi: true, }, { id: "beos", diff --git a/tests/full/run.js b/tests/full/run.js index bd2a5cc6..4d88b76b 100755 --- a/tests/full/run.js +++ b/tests/full/run.js @@ -665,8 +665,8 @@ if(cluster.isPrimary) skip_if_disk_image_missing: true, timeout: 60, memory_size: 512 * 1024 * 1024, - hda: root_path + "/images/haiku-v4.img", - state: root_path + "/images/haiku_state-v4.bin.zst", + hda: root_path + "/images/haiku-v5.img", + state: root_path + "/images/haiku_state-v5.bin.zst", actions: [ { after: 2 * 1000, @@ -676,6 +676,7 @@ if(cluster.isPrimary) expected_serial_text: [ "121393", ], + acpi: true, }, { name: "9front", From 6e3afa67ab08c1ac693c414490d7695f6d50e935 Mon Sep 17 00:00:00 2001 From: Fabian Date: Wed, 10 Sep 2025 17:17:48 -0300 Subject: [PATCH 260/301] remove trailing spaces --- docs/windows-9x.md | 6 +++--- docs/windows-nt.md | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/windows-9x.md b/docs/windows-9x.md index 118e7961..eed34754 100644 --- a/docs/windows-9x.md +++ b/docs/windows-9x.md @@ -6,7 +6,7 @@ Recommended versions: ------------- -1. Create a disk image (up to 2 GB): +1. Create a disk image (up to 2 GB): ```sh qemu-img create -f raw hdd.img M ``` @@ -32,7 +32,7 @@ qemu-system-i386 -m 128 -M pc,acpi=off -hda hdd.img Currently, the floppy drive in v86 works only with MS-DOS compatibility mode. -To check this: open the Start menu, click on "Control Panel" and "System", select "Performance" tab. +To check this: open the Start menu, click on "Control Panel" and "System", select "Performance" tab. If it says *"Drive A is using MS-DOS compatibility mode file system"*, the floppy drive should work properly in v86. If not, try this solution: 1. Click on "Device Manager" in "System Properties". @@ -44,7 +44,7 @@ If it says *"Drive A is using MS-DOS compatibility mode file system"*, the flopp The default VGA display driver only supports 640x480x8 video mode, to fix this, install **Universal VBE9x Video Display Driver**. > [!WARNING] -> After installing, DOS Mode (and other programs and games that require it) may not work properly. +> After installing, DOS Mode (and other programs and games that require it) may not work properly. > This is a problem in VBE9x, not v86, see [#110](https://github.com/copy/v86/issues/110). > Also, this driver doesn't support DirectX, DirectDraw and OpenGL. diff --git a/docs/windows-nt.md b/docs/windows-nt.md index e3474880..8ee83a8d 100644 --- a/docs/windows-nt.md +++ b/docs/windows-nt.md @@ -21,7 +21,7 @@ qemu-system-i386 -m 64 -hda hdd.img -cpu pentium -M pc,acpi=off -cdrom InstallCD ``` 4. Run `xcopy /v :\I386\ C:\install\` in a VM to copy all files, disable the CD-ROM driver. -5. Run QEMU with the following settings: +5. Run QEMU with the following settings: ```sh qemu-system-i386 -m 64 -hda hdd.img -cpu pentium -M pc,acpi=off @@ -33,7 +33,7 @@ qemu-system-i386 -m 64 -hda hdd.img -cpu pentium -M pc,acpi=off ## Windows NT 3.51 -### Installing +### Installing > [!NOTE] > In newer versions of QEMU, the Windows Setup may not work, you can use an older version of QEMU, PCem, 86Box or PCBox instead. @@ -64,7 +64,7 @@ Recommended version: Windows NT 4.0 SP1 ### Installing using QEMU -1. Run QEMU with the following settings for installation: +1. Run QEMU with the following settings for installation: ```sh qemu-system-i386 -m 64 -hda hdd.img -cdrom InstallCD.iso -cpu pentium -M pc,acpi=off @@ -90,7 +90,7 @@ var emulator = new V86({ ### Installing using QEMU -1. Run QEMU with the following settings for installation: +1. Run QEMU with the following settings for installation: ```sh qemu-system-i386 -m 512 -hda hdd.img -cdrom InstallCD.iso @@ -107,7 +107,7 @@ After installation, change the computer type to "Standard PC" as described [here 1. Open Start menu, right-click on "My Computer", select "Manage" 2. Open Device Manager, open Computer, right-click on "ACPI Uniprocessor PC" 3. Select "Update Driver..." > "No, not this time" -4. Select "Install from a list or specific location (Advanced)" > Next > "Don't search. I will choose the driver to install." +4. Select "Install from a list or specific location (Advanced)" > Next > "Don't search. I will choose the driver to install." 5. Choose "Standard PC", press Next > Finish. 6. Restart the VM, follow multiple "Found New Hardware Wizard" dialogs with default options. @@ -145,7 +145,7 @@ Models: Sound Blaster 16 or AWE32 or compatible (WDM) ### Installing using QEMU -1. Run QEMU with the following settings for installation: +1. Run QEMU with the following settings for installation: ```sh qemu-system-i386 -m 1024 -hda hdd.img -cdrom InstallCD.iso From 4e15afcd777ddf7b3cb9c152a87521a5d666651a Mon Sep 17 00:00:00 2001 From: Fabian Date: Wed, 10 Sep 2025 17:20:13 -0300 Subject: [PATCH 261/301] update docs with proper qemu invocation --- docs/windows-9x.md | 2 +- docs/windows-nt.md | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/windows-9x.md b/docs/windows-9x.md index eed34754..3d2867ee 100644 --- a/docs/windows-9x.md +++ b/docs/windows-9x.md @@ -12,7 +12,7 @@ qemu-img create -f raw hdd.img M ``` 2. Run QEMU with the following settings: ```sh -qemu-system-i386 -m 128 -M pc,acpi=off -hda hdd.img +qemu-system-i386 -m 128 -M pc,acpi=off -drive file=hdd.img,format=raw ``` - add `-cdrom /path/to/installCD.iso`, if you use a CD version. - add `-fda /path/to/boot_floppy.img -boot a`, if you use a floppy version or your install CD is non-bootable. diff --git a/docs/windows-nt.md b/docs/windows-nt.md index 8ee83a8d..c0f21920 100644 --- a/docs/windows-nt.md +++ b/docs/windows-nt.md @@ -17,14 +17,14 @@ 3. Run QEMU with the following settings for installation: ```sh -qemu-system-i386 -m 64 -hda hdd.img -cpu pentium -M pc,acpi=off -cdrom InstallCD.iso +qemu-system-i386 -m 64 -drive file=hdd.img,format=raw -cpu pentium -M pc,acpi=off -cdrom InstallCD.iso ``` 4. Run `xcopy /v :\I386\ C:\install\` in a VM to copy all files, disable the CD-ROM driver. 5. Run QEMU with the following settings: ```sh -qemu-system-i386 -m 64 -hda hdd.img -cpu pentium -M pc,acpi=off +qemu-system-i386 -m 64 -drive file=hdd.img,format=raw -cpu pentium -M pc,acpi=off ``` 6. Run `C:\install\winnt /F /C` in a VM. @@ -67,7 +67,7 @@ Recommended version: Windows NT 4.0 SP1 1. Run QEMU with the following settings for installation: ```sh -qemu-system-i386 -m 64 -hda hdd.img -cdrom InstallCD.iso -cpu pentium -M pc,acpi=off +qemu-system-i386 -m 64 -drive file=hdd.img,format=raw -cdrom InstallCD.iso -cpu pentium -M pc,acpi=off ``` 2. On setup startup, press F5 and select "Standard PC". @@ -93,7 +93,7 @@ var emulator = new V86({ 1. Run QEMU with the following settings for installation: ```sh -qemu-system-i386 -m 512 -hda hdd.img -cdrom InstallCD.iso +qemu-system-i386 -m 512 -drive file=hdd.img,format=raw -cdrom InstallCD.iso ``` Optional: @@ -148,7 +148,7 @@ Models: Sound Blaster 16 or AWE32 or compatible (WDM) 1. Run QEMU with the following settings for installation: ```sh -qemu-system-i386 -m 1024 -hda hdd.img -cdrom InstallCD.iso +qemu-system-i386 -m 1024 -drive file=hdd.img,format=raw -cdrom InstallCD.iso ``` Optionally add `-accel kvm` (for Linux host), `-accel whpx` (for Windows host) or `-accel hvf` (for MacOS host) to use hypervisor acceleration. From 0669f7a4774f9d575392ef36aacddd69f3345425 Mon Sep 17 00:00:00 2001 From: Fabian Date: Thu, 11 Sep 2025 20:01:44 -0300 Subject: [PATCH 262/301] make tests a more reliable --- tests/full/run.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/full/run.js b/tests/full/run.js index 4d88b76b..23e366c2 100755 --- a/tests/full/run.js +++ b/tests/full/run.js @@ -553,9 +553,9 @@ if(cluster.isPrimary) on_text: "Compress okay", run: RUN_SLOW_TESTS ? - "./v86-in-v86.js | tee /dev/stderr | grep -m1 'Files send via emulator appear in' ; sleep 2; echo v86-in-v86 okay\n" + "./v86-in-v86.js | tee /dev/stderr | grep -m1 'Files send via emulator appear in' ; sleep 2; echo; echo v86-in-v86 okay\n" : - "./v86-in-v86.js | tee /dev/stderr | grep -m1 'Kernel command line:' ; sleep 2; echo v86-in-v86 okay\n", + "./v86-in-v86.js | tee /dev/stderr | grep -m1 'Kernel command line:' ; sleep 2; echo; echo v86-in-v86 okay\n", }, { on_text: "v86-in-v86 okay", @@ -1533,8 +1533,8 @@ function run_test(test, done) timeouts.push( setTimeout(() => { if(VERBOSE) console.error("Sending '%s'", action.run); - if(typeof action.run[0] === "string") emulator.keyboard_send_text(action.run, 5); - else emulator.keyboard_send_scancodes(action.run, 5); + if(typeof action.run[0] === "string") emulator.keyboard_send_text(action.run, 10); + else emulator.keyboard_send_scancodes(action.run, 10); }, action.after || 0) ); } @@ -1589,8 +1589,8 @@ function run_test(test, done) timeouts.push( setTimeout(() => { if(VERBOSE) console.error("Sending '%s'", action.run); - if(typeof action.run[0] === "string") emulator.keyboard_send_text(action.run, 5); - else emulator.keyboard_send_scancodes(action.run, 5); + if(typeof action.run[0] === "string") emulator.keyboard_send_text(action.run, 10); + else emulator.keyboard_send_scancodes(action.run, 10); }, action.after || 0) ); } From 772f0a1faaee5faf41db89552a8ac5dc44c227cf Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 19 Sep 2025 15:51:12 -0300 Subject: [PATCH 263/301] fix the mobile css --- v86.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/v86.css b/v86.css index 5d18da39..21558482 100644 --- a/v86.css +++ b/v86.css @@ -154,10 +154,10 @@ h4 { } } @media (max-width: 750px) { - #oses span:nth-child(2), #oses span:nth-child(3), #oses span:nth-child(10) { + #oses .th span:nth-child(10) { display: none; } - #oses span:nth-child(1), #oses span:nth-child(2), #oses span:nth-child(3) { + #oses span:nth-child(1), #oses span:nth-child(2), #oses span:nth-child(3), #oses span:nth-child(3) div { display: inline; } #oses span:nth-child(2), #oses span:nth-child(3) { From ccb5e1ce5d38d8bbe0cd44cadbefd09404bd3cad Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 20 Sep 2025 11:22:34 -0300 Subject: [PATCH 264/301] disable use of scheduling api for now see https://github.com/copy/v86/issues/789#issuecomment-3314001637 --- src/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.js b/src/main.js index 9f78b165..30a85db9 100644 --- a/src/main.js +++ b/src/main.js @@ -114,7 +114,7 @@ if(typeof process !== "undefined") v86.prototype.register_yield = function() {}; v86.prototype.unregister_yield = function() {}; } -else if(window["scheduler"] && typeof window["scheduler"]["postTask"] === "function" && !location.href.includes("dont-use-scheduling-api")) +else if(window["scheduler"] && typeof window["scheduler"]["postTask"] === "function" && location.href.includes("use-scheduling-api")) { v86.prototype.yield = function(t, tick) { From 71f4a416abfaf6cbf4de77a935259f5ccd60e077 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 20 Sep 2025 11:37:40 -0300 Subject: [PATCH 265/301] fix warning --- src/rust/jit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rust/jit.rs b/src/rust/jit.rs index 9d1c8a1d..09f06eec 100644 --- a/src/rust/jit.rs +++ b/src/rust/jit.rs @@ -707,7 +707,7 @@ fn jit_find_basic_blocks( let previous_block = basic_blocks .range(..current_block.addr) .next_back() - .filter(|(_, previous_block)| (!previous_block.has_sti)) + .filter(|(_, previous_block)| !previous_block.has_sti) .map(|(_, previous_block)| previous_block); if let Some(previous_block) = previous_block { From ac5e657e5a9fcbfc4da53301c59397ead57e52dc Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 20 Sep 2025 14:40:00 -0300 Subject: [PATCH 266/301] fix minor error in debug.html --- src/browser/main.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/browser/main.js b/src/browser/main.js index 7788ad16..a4720b77 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1843,14 +1843,17 @@ function onload() } } - $("reset_filters").onclick = function() + if($("reset_filters")) { - for(const element of document.querySelectorAll("#filter input[type=checkbox]")) + $("reset_filters").onclick = function() { - element.checked = false; - } - update_filters(); - }; + for(const element of document.querySelectorAll("#filter input[type=checkbox]")) + { + element.checked = false; + } + update_filters(); + }; + } function set_proxy_value(id, value) { From 9735a0eed83a426b3b14cf98f9043a5a9241b142 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 20 Sep 2025 15:06:02 -0300 Subject: [PATCH 267/301] fix fetching wasm progress message --- src/browser/main.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/browser/main.js b/src/browser/main.js index a4720b77..079a1094 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -68,9 +68,11 @@ function show_progress(e) const el = $("loading"); el.style.display = "block"; - if(e.file_name.endsWith(".wasm")) + const file_name = e.file_name.split("?", 1)[0]; + + if(file_name.endsWith(".wasm")) { - const parts = e.file_name.split("/"); + const parts = file_name.split("/"); el.textContent = "Fetching " + parts[parts.length - 1] + " ..."; return; } From 667313401d072bb7966893fa460875085207d2b7 Mon Sep 17 00:00:00 2001 From: Christian Schnell Date: Thu, 30 Oct 2025 18:35:05 +0100 Subject: [PATCH 268/301] Fix UTF-8 encoding in SerialAdapterXtermJS (#1439) * avoid possibly creating unused SerialAdapter instance * use UTF-8 encoding when exchanging strings between XTerm and serial port Use TextEncoder.encode() instead of String.charCodeAt() in Terminal.onData(data_str) to encode user input strings (keyboard or paste). --- src/browser/serial.js | 11 ++++++----- src/browser/starter.js | 11 +++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/browser/serial.js b/src/browser/serial.js index 0f049b5b..f0371b91 100644 --- a/src/browser/serial.js +++ b/src/browser/serial.js @@ -234,16 +234,17 @@ export function SerialAdapterXtermJS(element, bus) }); term.write("This is the serial console. Whatever you type or paste here will be sent to COM1"); - const on_data_disposable = term["onData"](function(data) { - for(let i = 0; i < data.length; i++) + const utf8_encoder = new TextEncoder(); + const on_data_disposable = term["onData"](function(data_str) { + for(const utf8_byte of utf8_encoder.encode(data_str)) { - bus.send("serial0-input", data.charCodeAt(i)); + bus.send("serial0-input", utf8_byte); } }); - bus.register("serial0-output-byte", function(byte) + bus.register("serial0-output-byte", function(utf8_byte) { - term.write(Uint8Array.of(byte)); + term.write(Uint8Array.of(utf8_byte)); }, this); this.destroy = function() { diff --git a/src/browser/starter.js b/src/browser/starter.js index 8aec8fad..87b66859 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -289,16 +289,15 @@ V86.prototype.continue_init = async function(emulator, options) settings.screen = this.screen_adapter; settings.screen_options = screen_options; - if(options.serial_container) - { - this.serial_adapter = new SerialAdapter(options.serial_container, this.bus); - //this.recording_adapter = new SerialRecordingAdapter(this.bus); - } - if(options.serial_container_xtermjs) { this.serial_adapter = new SerialAdapterXtermJS(options.serial_container_xtermjs, this.bus); } + else if(options.serial_container) + { + this.serial_adapter = new SerialAdapter(options.serial_container, this.bus); + //this.recording_adapter = new SerialRecordingAdapter(this.bus); + } if(!options.disable_speaker) { From 777b8c397ceeeee15a3f20bf0d6b165b4018a4f1 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Thu, 30 Oct 2025 19:36:00 +0200 Subject: [PATCH 269/301] fix iso file extension detection (#1426) --- src/browser/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/main.js b/src/browser/main.js index 079a1094..b60c2135 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -2837,7 +2837,7 @@ function init_ui(profile, settings, emulator) const files = file_input.files; let buffer; - if(files.length === 1 && files[0].name.endsWith(".iso")) + if(files.length === 1 && /\.(iso(9660|img)?|cdr)$/i.test(files[0].name)) { buffer = files[0]; } From a4c9943c3d980a47f871727dc1479dd34e23e015 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Tue, 7 Oct 2025 12:25:28 +0300 Subject: [PATCH 270/301] docs: add the "Troubleshooting" section for `windows-9x.md` --- docs/windows-9x.md | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/docs/windows-9x.md b/docs/windows-9x.md index 3d2867ee..9aea4880 100644 --- a/docs/windows-9x.md +++ b/docs/windows-9x.md @@ -23,11 +23,41 @@ qemu-system-i386 -m 128 -M pc,acpi=off -drive file=hdd.img,format=raw 4. To change floppy disk, press *Ctrl+Alt+2* to switch to the QEMU Monitor, run `change floppy0 /path/to/new_floppy_image` and press *Ctrl+Alt+1* to switch to VGA. 5. Follow the installation guide on the screen. -6. (optionally) If "Windows protection" errors appears on startup, apply [FIX95CPU](http://lonecrusader.x10host.com/fix95cpu.html) or [patcher9x](https://github.com/JHRobotics/patcher9x#installation). > [!TIP] > For transfer files from host to guest, use [genisoimage](https://wiki.debian.org/genisoimage) ([UltraISO](https://www.ultraiso.com/) and [PowerISO](https://www.poweriso.com/) for Windows and Mac) for creating CD-ISO image or [dosfstools](https://github.com/dosfstools/dosfstools) ([WinImage](https://www.winimage.com/download.htm) for Windows) for creating floppy disk images, then mount the created image to QEMU. +## Troubleshooting + +### "Windows protection" errors during startup + +Apply [FIX95CPU](http://lonecrusader.x10host.com/fix95cpu.html) or [patcher9x](https://github.com/JHRobotics/patcher9x#installation). + +### "VFBACKUP could no load VFD.VXD" on startup (Windows 95) + +**Workaround #1**: +*Source: [#1185](https://github.com/copy/v86/issues/1185)* + +1. Mount the installation CD (or `Disk 3` for the RTM version on floppy disks). +2. Open the "MS-DOS prompt" and run: + +For the CD version: +```bat +extract /a /l C:\Windows\System :\WIN95\WIN95_02.CAB vfd.vxd +``` + +For the floppy version: +```bat +extract /a /l C:\Windows\System :\WIN95_03.CAB vfd.vxd +``` + +**Workaround #2**: +*Source: [#289](https://github.com/copy/v86/issues/289)* + +1. Open the Start menu, click on "Run" and run `sysedit`. +2. Find `C:\AUTOEXEC.BAT` and add `smartdrv` to the top of the file. +3. Press File -> Save. + ## Floppy disk support Currently, the floppy drive in v86 works only with MS-DOS compatibility mode. From 81eb6a1d417eb25c008971acfe6ce701c43f4ee6 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Tue, 7 Oct 2025 12:40:30 +0300 Subject: [PATCH 271/301] docs: use tables in `windows-9x.md` --- docs/windows-9x.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/windows-9x.md b/docs/windows-9x.md index 9aea4880..40631ac2 100644 --- a/docs/windows-9x.md +++ b/docs/windows-9x.md @@ -94,28 +94,28 @@ See about [installing AmnHLT](cpu-idling.md#windows-9x-using-amnhlt). 1. Open the Start menu, click on "Control Panel" and "Add New Hardware". 2. Press "Next", select "No" and select next options: -``` -Hardware type: Network adapters -Manufacturers: Novell -Models: NE2000 Compatible -``` +| Option | Value | +|:--------------|:------------------| +| Hardware type | Network adapters | +| Manufacturers | Novell | +| Models | NE2000 Compatible | 3. Press "Next" and restart Windows. 4. After restarting, right-click on "My computer", select "Propeties". 5. Open "Device Manager" tab, select "NE2000 Compatible" (in "Network adapters") and press "Properties" 6. Open "Resources", change values by selecting the properties and click on "Change Setting": -``` -Interrupt Request: 10 -Input/Output Range: 0300 - 031F -``` +| Option | Value | +|:-------------------|:------------| +| Interrupt Request | 10 | +| Input/Output Range | 0300 - 031F | 7. In "Control Panel", open "Network", click on "Add", choose "Protocol" and select the following options: -``` -Manufacturers: Microsoft -Network Protocols: TCP/IP -``` +| Option | Value | +|:------------------|:----------| +| Manufacturers | Microsoft | +| Network Protocols | TCP/IP | 8. (optionally) Set "Primary Network Logon" to `Windows Logon`. @@ -127,10 +127,10 @@ Network Protocols: TCP/IP 1. Open "Start" menu, click on "Control Panel" and "Add New Hardware". 2. Press "Next", select "No" and select the following options: -``` -Hardware type: Sound, video and game cotrollers -Manufacturers: Creative Labs -Models: Creative Labs Sound Blaster 16 or AWE-32 -``` +| Option | Value | +|:--------------|:-----------------------------------------| +| Hardware type | Sound, video and game cotrollers | +| Manufacturers | Creative Labs | +| Models | Creative Labs Sound Blaster 16 or AWE-32 | 3. Restart Windows. From c70dafd411a596194fd2937e04d0253f2135fc45 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Tue, 7 Oct 2025 13:29:18 +0300 Subject: [PATCH 272/301] docs: fix sb16 driver url --- docs/windows-9x.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/windows-9x.md b/docs/windows-9x.md index 40631ac2..43eda901 100644 --- a/docs/windows-9x.md +++ b/docs/windows-9x.md @@ -122,7 +122,7 @@ See about [installing AmnHLT](cpu-idling.md#windows-9x-using-amnhlt). ## Enabling sound manually > [!NOTE] -> If you don't have an install CD, use the Sound Blaster 16 driver from https://www.claunia.com/qemu/drivers/index.html. +> If you don't have an install CD, use the Sound Blaster 16 driver from https://web.archive.org/web/20210814023225/https://www.claunia.com/qemu/drivers/index.html (unpack `sbw9xup.exe` as a zip archive). 1. Open "Start" menu, click on "Control Panel" and "Add New Hardware". 2. Press "Next", select "No" and select the following options: From 54ec8e8daa42d407ccb80b7d925290f114f20815 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Tue, 7 Oct 2025 17:09:49 +0300 Subject: [PATCH 273/301] docs: add vmdisp9x instruction in `windows-9x.md` --- docs/windows-9x.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/windows-9x.md b/docs/windows-9x.md index 43eda901..d72f080e 100644 --- a/docs/windows-9x.md +++ b/docs/windows-9x.md @@ -71,7 +71,9 @@ If it says *"Drive A is using MS-DOS compatibility mode file system"*, the flopp ## Enabling True Color (32 bpp) -The default VGA display driver only supports 640x480x8 video mode, to fix this, install **Universal VBE9x Video Display Driver**. +The default VGA display driver only supports 640x480x4 video mode, to fix this, you can install **Universal VBE9x Video Display Driver** or **VMDisp9x**. + +### Universal VBE9x Video Display Driver > [!WARNING] > After installing, DOS Mode (and other programs and games that require it) may not work properly. @@ -86,6 +88,19 @@ The default VGA display driver only supports 640x480x8 video mode, to fix this, 6. Select "VBE Miniport" adapter, press "OK" and "Next". 7. After installing, restart Windows. +### VMDisp9x (Windows 95) + +> [!WARNING] +> This driver can run DOS Mode with some graphical glitches. However, DirectX and/or DirectDraw may not work properly with this driver. +> Also, this driver doesn't support OpenGL. + +1. Download `vmdisp9x-<...>-driver-2d.img` from https://github.com/JHRobotics/vmdisp9x/releases. +2. Mount as floppy image, right-click on the Desktop, click on "Properties". +3. Click "Advanced" > "Adapter" > "Change". +4. Press "Have Disk...", click "Browse" and go to the floppy. +5. Select "VESA ISA" adapter and press "OK". +6. After installing, restart Windows. + ## CPU idling on Windows 95 See about [installing AmnHLT](cpu-idling.md#windows-9x-using-amnhlt). From e65e4f441e663fc4da30a82139d495dca4bf5bdf Mon Sep 17 00:00:00 2001 From: toast Date: Sat, 27 Sep 2025 14:48:42 -0700 Subject: [PATCH 274/301] add loopback to UART --- src/uart.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/uart.js b/src/uart.js index c1f47aa9..771ce86d 100644 --- a/src/uart.js +++ b/src/uart.js @@ -26,6 +26,9 @@ const UART_IIR_RDI = 0x04; /* Receiver data interrupt */ const UART_IIR_RLSI = 0x06; /* Receiver line status interrupt (High p.) */ const UART_IIR_CTI = 0x0c; /* Character timeout */ +// Modem control register +const UART_MCR_LOOPBACK = 0x10; + const UART_LSR_DATA_READY = 0x1; // data available const UART_LSR_TX_EMPTY = 0x20; // TX (THR) buffer is empty const UART_LSR_TRANSMITTER_EMPTY = 0x40; // TX empty and line is idle @@ -399,7 +402,11 @@ UART.prototype.write_data = function(out_byte) this.ThrowInterrupt(UART_IIR_THRI); - this.bus.send("serial" + this.com + "-output-byte", out_byte); + if(this.modem_control & UART_MCR_LOOPBACK) { + this.data_received(out_byte); + } else { + this.bus.send("serial" + this.com + "-output-byte", out_byte); + } if(DEBUG) { From 5a48c7e8677fd947d87ec62cb685d8478cd00bbe Mon Sep 17 00:00:00 2001 From: FunnyCorgi <166766151+FunnyCorgi@users.noreply.github.com> Date: Sun, 12 Oct 2025 14:36:07 -0400 Subject: [PATCH 275/301] Fix HDA and HDB always being set to 1 MB This is towards #1427. --- src/browser/main.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/main.js b/src/browser/main.js index b60c2135..669dd5e2 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -2157,7 +2157,7 @@ function start_emulation(profile, query_args) const hda_empty_size = +$("hda_empty_size")?.value; if(hda_empty_size) { - const size = Math.min(1, Math.max(MAX_ARRAY_BUFFER_SIZE_MB, hda_empty_size)) * 1024 * 1024; + const size = Math.max(1, Math.min(MAX_ARRAY_BUFFER_SIZE_MB, hda_empty_size)) * 1024 * 1024; settings.hda = { buffer: new ArrayBuffer(size) }; new_query_args.set("hda.empty", String(size)); } @@ -2169,7 +2169,7 @@ function start_emulation(profile, query_args) const hdb_empty_size = +$("hdb_empty_size")?.value; if(hdb_empty_size) { - const size = Math.min(1, Math.max(MAX_ARRAY_BUFFER_SIZE_MB, hdb_empty_size)) * 1024 * 1024; + const size = Math.max(1, Math.min(MAX_ARRAY_BUFFER_SIZE_MB, hdb_empty_size)) * 1024 * 1024; settings.hdb = { buffer: new ArrayBuffer(hdb_empty_size) }; new_query_args.set("hdb.empty", String(size)); } From db22e06746d7adfd3b8c01be29fd15dea4b3bd37 Mon Sep 17 00:00:00 2001 From: FunnyCorgi <166766151+FunnyCorgi@users.noreply.github.com> Date: Sun, 12 Oct 2025 15:48:52 -0400 Subject: [PATCH 276/301] Fix HDB buffer creation using raw instead of calculated result size --- src/browser/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/main.js b/src/browser/main.js index 669dd5e2..4c2db9aa 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -2170,7 +2170,7 @@ function start_emulation(profile, query_args) if(hdb_empty_size) { const size = Math.max(1, Math.min(MAX_ARRAY_BUFFER_SIZE_MB, hdb_empty_size)) * 1024 * 1024; - settings.hdb = { buffer: new ArrayBuffer(hdb_empty_size) }; + settings.hdb = { buffer: new ArrayBuffer(size) }; new_query_args.set("hdb.empty", String(size)); } const multiboot = $("multiboot_image")?.files[0]; From c004ebb3ab4993f87e4e86021bf047850aac4ee6 Mon Sep 17 00:00:00 2001 From: Rob Blanckaert Date: Sat, 22 Nov 2025 14:28:35 -0800 Subject: [PATCH 277/301] Use global this for scheduler access This makes libv86 work inside shared workers again. --- src/main.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.js b/src/main.js index 30a85db9..2e45b98e 100644 --- a/src/main.js +++ b/src/main.js @@ -114,12 +114,12 @@ if(typeof process !== "undefined") v86.prototype.register_yield = function() {}; v86.prototype.unregister_yield = function() {}; } -else if(window["scheduler"] && typeof window["scheduler"]["postTask"] === "function" && location.href.includes("use-scheduling-api")) +else if(globalThis["scheduler"] && typeof globalThis["scheduler"]["postTask"] === "function" && location.href.includes("use-scheduling-api")) { v86.prototype.yield = function(t, tick) { t = Math.max(0, t); - window["scheduler"]["postTask"](() => this.yield_callback(tick), { delay: t }); + globalThis["scheduler"]["postTask"](() => this.yield_callback(tick), { delay: t }); }; v86.prototype.register_yield = function() {}; From ec90cede422e13ef4dc4bbd14e72a1f25cb2fa00 Mon Sep 17 00:00:00 2001 From: Rob Blanckaert Date: Sat, 22 Nov 2025 11:10:21 -0800 Subject: [PATCH 278/301] Nuke some old stuff from virtio console thats fixed in the overall virtio impl. now --- src/virtio_console.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/virtio_console.js b/src/virtio_console.js index a8c87c5e..9762ed44 100644 --- a/src/virtio_console.js +++ b/src/virtio_console.js @@ -107,11 +107,9 @@ export function VirtioConsole(cpu, bus) { dbg_assert(false, "VirtioConsole Notified for wrong queue: " + queue_id + " (expected queue_id of 2)"); - return; + } - const queue = this.virtio.queues[queue_id]; - // Full buffer looks like an empty buffer so prevent it from filling - while(queue.count_requests() > queue.size - 2) queue.pop_request(); + }, (queue_id) => { From d093088616514ab6685f8e346d8fcc2a7b198308 Mon Sep 17 00:00:00 2001 From: ading2210 Date: Wed, 5 Nov 2025 13:07:21 -0800 Subject: [PATCH 279/301] add mtu options to network code --- docs/networking.md | 1 + src/browser/fake_network.js | 26 +++++++++++++++----------- src/browser/fetch_network.js | 6 +++--- src/browser/main.js | 4 +++- src/browser/wisp_network.js | 8 ++++---- src/cpu.js | 2 +- src/virtio_net.js | 5 +++-- v86.d.ts | 1 + 8 files changed, 31 insertions(+), 22 deletions(-) diff --git a/docs/networking.md b/docs/networking.md index d9ec124d..08917348 100644 --- a/docs/networking.md +++ b/docs/networking.md @@ -62,6 +62,7 @@ Backends `fetch` and `wisp` support a couple of special settings in `config.net_ | **dns_method** | str | DNS method to use, either `static` or `doh`. `static`: use built-in DNS server, `doh`: use [DNS-over-HTTPS](https://en.wikipedia.org/wiki/DNS_over_HTTPS) (DoH). Defaults to `static` for `fetch` and to `doh` for `wisp` backend. | | **doh_server** | str | Host name or IP address (and optional port number) of the DoH server if `dns_method` is `doh`. The value is expanded to the URL `https://DOH_SERVER/dns-query`. Default: `cloudflare-dns.com`. | | **cors_proxy** | str | CORS proxy server URL, do not use a proxy if undefined. Default: undefined (`fetch` backend only). | +| **mtu** | int | The MTU used for the virtual network. Increasing it can improve performance. This only works if the NIC type is `virtio`. Default: `1500` | #### Example `net_device` settings diff --git a/src/browser/fake_network.js b/src/browser/fake_network.js index e358f921..63923dec 100644 --- a/src/browser/fake_network.js +++ b/src/browser/fake_network.js @@ -45,18 +45,16 @@ const TCP_DYNAMIC_PORT_RANGE = TCP_DYNAMIC_PORT_END - TCP_DYNAMIC_PORT_START; const ETH_HEADER_SIZE = 14; const ETH_PAYLOAD_OFFSET = ETH_HEADER_SIZE; -const ETH_PAYLOAD_SIZE = 1500; +const MTU_DEFAULT = 1500; +//const ETH_PAYLOAD_SIZE = 1500; //mtu const ETH_TRAILER_SIZE = 4; -const ETH_FRAME_SIZE = ETH_HEADER_SIZE + ETH_PAYLOAD_SIZE + ETH_TRAILER_SIZE; const IPV4_HEADER_SIZE = 20; const IPV4_PAYLOAD_OFFSET = ETH_PAYLOAD_OFFSET + IPV4_HEADER_SIZE; -const IPV4_PAYLOAD_SIZE = ETH_PAYLOAD_SIZE - IPV4_HEADER_SIZE; +//const IPV4_PAYLOAD_SIZE = ETH_PAYLOAD_SIZE - IPV4_HEADER_SIZE; const UDP_HEADER_SIZE = 8; const UDP_PAYLOAD_OFFSET = IPV4_PAYLOAD_OFFSET + UDP_HEADER_SIZE; -const UDP_PAYLOAD_SIZE = IPV4_PAYLOAD_SIZE - UDP_HEADER_SIZE; const TCP_HEADER_SIZE = 20; const TCP_PAYLOAD_OFFSET = IPV4_PAYLOAD_OFFSET + TCP_HEADER_SIZE; -const TCP_PAYLOAD_SIZE = IPV4_PAYLOAD_SIZE - TCP_HEADER_SIZE; const ICMP_HEADER_SIZE = 4; const DEFAULT_DOH_SERVER = "cloudflare-dns.com"; @@ -161,15 +159,19 @@ class GrowableRingbuffer } } -export function create_eth_encoder_buf() +export function create_eth_encoder_buf(mtu = MTU_DEFAULT) { + const ETH_FRAME_SIZE = ETH_HEADER_SIZE + mtu + ETH_TRAILER_SIZE; + const IPV4_PAYLOAD_SIZE = mtu - IPV4_HEADER_SIZE; + const UDP_PAYLOAD_SIZE = IPV4_PAYLOAD_SIZE - UDP_HEADER_SIZE; + const eth_frame = new Uint8Array(ETH_FRAME_SIZE); const buffer = eth_frame.buffer; const offset = eth_frame.byteOffset; return { eth_frame: eth_frame, eth_frame_view: new DataView(buffer), - eth_payload_view: new DataView(buffer, offset + ETH_PAYLOAD_OFFSET, ETH_PAYLOAD_SIZE), + eth_payload_view: new DataView(buffer, offset + ETH_PAYLOAD_OFFSET, mtu), ipv4_payload_view: new DataView(buffer, offset + IPV4_PAYLOAD_OFFSET, IPV4_PAYLOAD_SIZE), udp_payload_view: new DataView(buffer, offset + UDP_PAYLOAD_OFFSET, UDP_PAYLOAD_SIZE), text_encoder: new TextEncoder() @@ -991,7 +993,7 @@ export function fake_tcp_connect(dport, adapter) throw new Error("pool of dynamic TCP port numbers exhausted, connection aborted"); } - let conn = new TCPConnection(); + let conn = new TCPConnection(adapter); conn.tuple = tuple; conn.hsrc = adapter.router_mac; @@ -1000,7 +1002,6 @@ export function fake_tcp_connect(dport, adapter) conn.hdest = adapter.vm_mac; conn.dport = dport; conn.pdest = adapter.vm_ip; - conn.net = adapter; adapter.tcp_conn[tuple] = conn; conn.connect(); return conn; @@ -1017,10 +1018,13 @@ export function fake_tcp_probe(dport, adapter) { /** * @constructor */ -export function TCPConnection() +export function TCPConnection(adapter) { + const IPV4_PAYLOAD_SIZE = (adapter.mtu || MTU_DEFAULT) - IPV4_HEADER_SIZE; + const TCP_PAYLOAD_SIZE = IPV4_PAYLOAD_SIZE - TCP_HEADER_SIZE; + this.state = TCP_STATE_CLOSED; - this.net = null; // The adapter is stored here + this.net = adapter; // The adapter is stored here this.send_buffer = new GrowableRingbuffer(2048, 0); this.send_chunk_buf = new Uint8Array(TCP_PAYLOAD_SIZE); this.in_active_close = false; diff --git a/src/browser/fetch_network.js b/src/browser/fetch_network.js index 5d0195a9..74f763f6 100644 --- a/src/browser/fetch_network.js +++ b/src/browser/fetch_network.js @@ -33,7 +33,8 @@ export function FetchNetworkAdapter(bus, config) this.dns_method = config.dns_method || "static"; this.doh_server = config.doh_server; this.tcp_conn = {}; - this.eth_encoder_buf = create_eth_encoder_buf(); + this.mtu = config.mtu; + this.eth_encoder_buf = create_eth_encoder_buf(this.mtu); this.fetch = (...args) => fetch(...args); // Ex: 'https://corsproxy.io/?' @@ -55,9 +56,8 @@ FetchNetworkAdapter.prototype.destroy = function() FetchNetworkAdapter.prototype.on_tcp_connection = function(packet, tuple) { if(packet.tcp.dport === 80) { - let conn = new TCPConnection(); + let conn = new TCPConnection(this); conn.state = TCP_STATE_SYN_RECEIVED; - conn.net = this; conn.on("data", on_data_http); conn.tuple = tuple; conn.accept(packet); diff --git a/src/browser/main.js b/src/browser/main.js index 4c2db9aa..cacc704f 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -2096,6 +2096,7 @@ function start_emulation(profile, query_args) settings.relay_url = query_args.get("relay_url"); settings.disable_jit = bool_arg(query_args.get("disable_jit")); settings.disable_audio = bool_arg(query_args.get("mute")); + settings.mtu = parseInt(query_args.get("mtu"), 10) || undefined; } if(!settings.relay_url) @@ -2255,7 +2256,8 @@ function start_emulation(profile, query_args) net_device: { type: settings.net_device_type || "ne2k", relay_url: settings.relay_url, - cors_proxy: settings.cors_proxy + cors_proxy: settings.cors_proxy, + mtu: settings.mtu }, autostart: true, diff --git a/src/browser/wisp_network.js b/src/browser/wisp_network.js index e0de942f..c3063218 100644 --- a/src/browser/wisp_network.js +++ b/src/browser/wisp_network.js @@ -36,8 +36,9 @@ export function WispNetworkAdapter(wisp_url, bus, config) this.dns_method = config.dns_method || "doh"; this.doh_server = config.doh_server; this.tcp_conn = {}; - this.eth_encoder_buf = create_eth_encoder_buf(); - + this.mtu = config.mtu; + this.eth_encoder_buf = create_eth_encoder_buf(this.mtu); + this.bus.register("net" + this.id + "-mac", function(mac) { this.vm_mac = new Uint8Array(mac.split(":").map(function(x) { return parseInt(x, 16); })); }, this); @@ -190,9 +191,8 @@ WispNetworkAdapter.prototype.destroy = function() */ WispNetworkAdapter.prototype.on_tcp_connection = function(packet, tuple) { - let conn = new TCPConnection(); + let conn = new TCPConnection(this); conn.state = TCP_STATE_SYN_RECEIVED; - conn.net = this; conn.tuple = tuple; conn.stream_id = this.last_stream++; this.tcp_conn[tuple] = conn; diff --git a/src/cpu.js b/src/cpu.js index 67659731..186e571a 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -1210,7 +1210,7 @@ CPU.prototype.init = function(settings, device_bus) } else if(settings.net_device.type === "virtio") { - this.devices.virtio_net = new VirtioNet(this, device_bus, settings.preserve_mac_from_state_image); + this.devices.virtio_net = new VirtioNet(this, device_bus, settings.preserve_mac_from_state_image, settings.net_device.mtu); } if(settings.fs9p) diff --git a/src/virtio_net.js b/src/virtio_net.js index ea6e08bd..ee7ad94c 100644 --- a/src/virtio_net.js +++ b/src/virtio_net.js @@ -24,8 +24,9 @@ const VIRTIO_NET_CTRL_MAC_ADDR_SET = 1; * @param {CPU} cpu * @param {BusConnector} bus * @param {Boolean} preserve_mac_from_state_image + * @param {Number} mtu */ -export function VirtioNet(cpu, bus, preserve_mac_from_state_image) +export function VirtioNet(cpu, bus, preserve_mac_from_state_image, mtu = 1500) { /** @const @type {BusConnector} */ this.bus = bus; @@ -177,7 +178,7 @@ export function VirtioNet(cpu, bus, preserve_mac_from_state_image) { bytes: 2, name: "mtu", - read: () => 1500, + read: () => mtu, write: data => {}, } ]) diff --git a/v86.d.ts b/v86.d.ts index 13c6ecbb..29f762ab 100644 --- a/v86.d.ts +++ b/v86.d.ts @@ -333,6 +333,7 @@ export interface V86Options { dns_method?: "static" | "doh"; doh_server?: string; cors_proxy?: string; + mtu?: number; }; } From 0beafeee0702d8f6dcb3f3191af9bbf83a615627 Mon Sep 17 00:00:00 2001 From: ading2210 Date: Thu, 6 Nov 2025 13:20:00 -0800 Subject: [PATCH 280/301] add mtu and nic type options to the interface --- debug.html | 22 ++++++++++++++++++++++ index.html | 21 +++++++++++++++++++++ src/browser/main.js | 23 +++++++++++++++++++++-- src/virtio_net.js | 6 ++++-- 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/debug.html b/debug.html index 7aac6d96..5749da57 100644 --- a/debug.html +++ b/debug.html @@ -147,6 +147,10 @@ + +
+ + @@ -154,6 +158,24 @@ + + + + + + + + + + + B
+ + + +
diff --git a/index.html b/index.html index 37bfe324..9b2bbfc7 100644 --- a/index.html +++ b/index.html @@ -247,6 +247,10 @@ + +
+ +
Presets: none, inbrowser, public relay, wisp, fetch @@ -255,6 +259,23 @@ + + + + + + + + + + + B
+ + +
diff --git a/src/browser/main.js b/src/browser/main.js index cacc704f..65101c24 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -12,6 +12,8 @@ const DEFAULT_NETWORKING_PROXIES = ["wss://relay.widgetry.org/", "ws://localhost const DEFAULT_MEMORY_SIZE = 128; const DEFAULT_VGA_MEMORY_SIZE = 8; const DEFAULT_BOOT_ORDER = 0; +const DEFAULT_MTU = 1500; +const DEFAULT_NIC_TYPE = "ne2k"; const MAX_ARRAY_BUFFER_SIZE_MB = 2000; @@ -1689,6 +1691,8 @@ function onload() if(query_args.has("mute")) $("disable_audio").checked = bool_arg(query_args.get("mute")); if(query_args.has("acpi")) $("acpi").checked = bool_arg(query_args.get("acpi")); if(query_args.has("boot_order")) $("boot_order").value = query_args.get("boot_order"); + if(query_args.has("net_device_type")) $("net_device_type").value = query_args.get("net_device_type"); + if(query_args.has("mtu")) $("mtu").value = query_args.get("mtu"); for(const dev of ["fda", "fdb"]) { @@ -2091,12 +2095,12 @@ function start_emulation(profile, query_args) settings.acpi = query_args.has("acpi") ? bool_arg(query_args.get("acpi")) : settings.acpi; settings.use_bochs_bios = query_args.get("bios") === "bochs"; settings.net_device_type = query_args.get("net_device_type") || settings.net_device_type; + settings.mtu = parseInt(query_args.get("mtu"), 10) || undefined; } settings.relay_url = query_args.get("relay_url"); settings.disable_jit = bool_arg(query_args.get("disable_jit")); settings.disable_audio = bool_arg(query_args.get("mute")); - settings.mtu = parseInt(query_args.get("mtu"), 10) || undefined; } if(!settings.relay_url) @@ -2240,6 +2244,21 @@ function start_emulation(profile, query_args) settings.bios = { url: BIOSPATH + "bochs-bios.bin" }; settings.vga_bios = { url: BIOSPATH + "bochs-vgabios.bin" }; } + + const nic_type = $("net_device_type").value || DEFAULT_NIC_TYPE; + if(!settings.net_device_type || nic_type !== DEFAULT_NIC_TYPE) + { + settings.net_device_type = nic_type; + } + if(settings.net_device_type !== DEFAULT_NIC_TYPE) new_query_args.set("net_device_type", settings.net_device_type); + + const mtu = parseInt($("mtu").value, 10) || DEFAULT_MTU; + if(!settings.mtu || mtu !== DEFAULT_MTU) + { + settings.mtu = mtu; + } + if(settings.mtu !== DEFAULT_MTU) new_query_args.set("mtu", settings.mtu.toString()); + } if(!query_args) @@ -2254,7 +2273,7 @@ function start_emulation(profile, query_args) use_graphical_text: false, }, net_device: { - type: settings.net_device_type || "ne2k", + type: settings.net_device_type || DEFAULT_NIC_TYPE, relay_url: settings.relay_url, cors_proxy: settings.cors_proxy, mtu: settings.mtu diff --git a/src/virtio_net.js b/src/virtio_net.js index ee7ad94c..f73cc281 100644 --- a/src/virtio_net.js +++ b/src/virtio_net.js @@ -9,6 +9,8 @@ import * as marshall from "../lib/marshall.js"; import { CPU } from "./cpu.js"; import { BusConnector } from "./bus.js"; +const DEFAULT_MTU = 1500; + const VIRTIO_NET_F_MAC = 5; const VIRTIO_NET_F_CTRL_VQ = 17; const VIRTIO_NET_F_STATUS = 16; @@ -24,9 +26,9 @@ const VIRTIO_NET_CTRL_MAC_ADDR_SET = 1; * @param {CPU} cpu * @param {BusConnector} bus * @param {Boolean} preserve_mac_from_state_image - * @param {Number} mtu + * @param {number} mtu */ -export function VirtioNet(cpu, bus, preserve_mac_from_state_image, mtu = 1500) +export function VirtioNet(cpu, bus, preserve_mac_from_state_image, mtu = DEFAULT_MTU) { /** @const @type {BusConnector} */ this.bus = bus; From 1a1afab08d991b11866e27a37eea6c53c0c1c7aa Mon Sep 17 00:00:00 2001 From: ading2210 Date: Thu, 6 Nov 2025 16:33:23 -0800 Subject: [PATCH 281/301] implement tcp mss option and set it based on the mtu --- src/browser/fake_network.js | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/browser/fake_network.js b/src/browser/fake_network.js index 63923dec..ce2af74b 100644 --- a/src/browser/fake_network.js +++ b/src/browser/fake_network.js @@ -192,6 +192,17 @@ function view_set_array(offset, data, view, out) return data.length; } +/** + * Write zeros into the view starting at offset + * @param {number} offset + * @param {number} length + * @param {DataView} view + */ +function view_set_zeros(offset, length, view, out) +{ + out.eth_frame.fill(0, view.byteOffset + offset, view.byteOffset + offset + length); +} + /** * UTF8-encode given string into view starting at offset, return number of bytes written. * @@ -951,19 +962,31 @@ function write_tcp(spec, out) { if(tcp.ece) flags |= 0x40; if(tcp.cwr) flags |= 0x80; - const doff = TCP_HEADER_SIZE >> 2; // header length in 32-bit words + let doff = TCP_HEADER_SIZE; + if (tcp.options) { + if (tcp.options.mss) { + view.setUint8(doff, 0x02) //mss option type + view.setUint8(doff + 1, 0x04) //option length + view.setUint16(doff + 2, tcp.options.mss); + doff += 4; + } + } + + let total_length = Math.ceil(doff / 4) * 4; // needs to a multiple of 4 bytes + if (tcp.options && total_length - doff > 0) { + view_set_zeros(doff, total_length - doff, view, out); //write zeros into remaining space for options + } view.setUint16(0, tcp.sport); view.setUint16(2, tcp.dport); view.setUint32(4, tcp.seq); view.setUint32(8, tcp.ackn); - view.setUint8(12, doff << 4); + view.setUint8(12, (total_length >> 2) << 4); // header length in 32-bit words view.setUint8(13, flags); view.setUint16(14, tcp.winsize); view.setUint16(16, 0); // checksum initially zero before calculation view.setUint16(18, tcp.urgent || 0); - let total_length = TCP_HEADER_SIZE; if(spec.tcp_data) { total_length += view_set_array(TCP_HEADER_SIZE, spec.tcp_data, view, out); } @@ -1020,7 +1043,8 @@ export function fake_tcp_probe(dport, adapter) { */ export function TCPConnection(adapter) { - const IPV4_PAYLOAD_SIZE = (adapter.mtu || MTU_DEFAULT) - IPV4_HEADER_SIZE; + this.mtu = (adapter.mtu || MTU_DEFAULT); + const IPV4_PAYLOAD_SIZE = this.mtu - IPV4_HEADER_SIZE; const TCP_PAYLOAD_SIZE = IPV4_PAYLOAD_SIZE - TCP_HEADER_SIZE; this.state = TCP_STATE_CLOSED; @@ -1123,7 +1147,10 @@ TCPConnection.prototype.accept = function(packet) { ackn: this.ack, winsize: packet.tcp.winsize, syn: true, - ack: true + ack: true, + options: { + mss: (this.mtu - TCP_HEADER_SIZE - IPV4_HEADER_SIZE) + } }; // dbg_log(`TCP[${this.tuple}]: accept(): sending SYN+ACK in state "${this.state}", next "${TCP_STATE_ESTABLISHED}"`, LOG_FETCH); this.state = TCP_STATE_ESTABLISHED; From 081b1eaafdeb64b194324c368a52688a095c1922 Mon Sep 17 00:00:00 2001 From: ading2210 Date: Thu, 6 Nov 2025 21:27:38 -0800 Subject: [PATCH 282/301] fix wisp adapter crash when a stream starts buffering --- src/browser/wisp_network.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/browser/wisp_network.js b/src/browser/wisp_network.js index c3063218..8da4725c 100644 --- a/src/browser/wisp_network.js +++ b/src/browser/wisp_network.js @@ -94,10 +94,12 @@ WispNetworkAdapter.prototype.process_incoming_wisp_frame = function(frame) { } if(this.connections[stream_id].congested) { - for(const packet of this.congested_buffer) { + const buffer = this.congested_buffer.slice(0); + this.congested_buffer.length = 0; + this.connections[stream_id].congested = false; + for(const packet of buffer) { this.send_packet(packet.data, packet.type, stream_id); } - this.connections[stream_id].congested = false; } break; case 4: // CLOSE From 97cd3069ac91f5fed7210b37a11f0d1d5e52bec4 Mon Sep 17 00:00:00 2001 From: ading2210 Date: Fri, 7 Nov 2025 10:23:11 -0800 Subject: [PATCH 283/301] cleanup code style --- src/browser/fake_network.js | 4 +--- src/virtio_net.js | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/browser/fake_network.js b/src/browser/fake_network.js index ce2af74b..8781d374 100644 --- a/src/browser/fake_network.js +++ b/src/browser/fake_network.js @@ -45,12 +45,10 @@ const TCP_DYNAMIC_PORT_RANGE = TCP_DYNAMIC_PORT_END - TCP_DYNAMIC_PORT_START; const ETH_HEADER_SIZE = 14; const ETH_PAYLOAD_OFFSET = ETH_HEADER_SIZE; -const MTU_DEFAULT = 1500; -//const ETH_PAYLOAD_SIZE = 1500; //mtu +const MTU_DEFAULT = 1500; const ETH_TRAILER_SIZE = 4; const IPV4_HEADER_SIZE = 20; const IPV4_PAYLOAD_OFFSET = ETH_PAYLOAD_OFFSET + IPV4_HEADER_SIZE; -//const IPV4_PAYLOAD_SIZE = ETH_PAYLOAD_SIZE - IPV4_HEADER_SIZE; const UDP_HEADER_SIZE = 8; const UDP_PAYLOAD_OFFSET = IPV4_PAYLOAD_OFFSET + UDP_HEADER_SIZE; const TCP_HEADER_SIZE = 20; diff --git a/src/virtio_net.js b/src/virtio_net.js index f73cc281..c34685d7 100644 --- a/src/virtio_net.js +++ b/src/virtio_net.js @@ -9,7 +9,7 @@ import * as marshall from "../lib/marshall.js"; import { CPU } from "./cpu.js"; import { BusConnector } from "./bus.js"; -const DEFAULT_MTU = 1500; +const MTU_DEFAULT = 1500; const VIRTIO_NET_F_MAC = 5; const VIRTIO_NET_F_CTRL_VQ = 17; @@ -28,7 +28,7 @@ const VIRTIO_NET_CTRL_MAC_ADDR_SET = 1; * @param {Boolean} preserve_mac_from_state_image * @param {number} mtu */ -export function VirtioNet(cpu, bus, preserve_mac_from_state_image, mtu = DEFAULT_MTU) +export function VirtioNet(cpu, bus, preserve_mac_from_state_image, mtu = MTU_DEFAULT) { /** @const @type {BusConnector} */ this.bus = bus; From c380cabc4cd8aee23d10d56a204e8a33b3c68695 Mon Sep 17 00:00:00 2001 From: ading2210 Date: Fri, 21 Nov 2025 10:44:23 -0800 Subject: [PATCH 284/301] fix eslint errors --- src/browser/fake_network.js | 16 ++++++++-------- src/browser/wisp_network.js | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/browser/fake_network.js b/src/browser/fake_network.js index 8781d374..0f894ded 100644 --- a/src/browser/fake_network.js +++ b/src/browser/fake_network.js @@ -192,11 +192,11 @@ function view_set_array(offset, data, view, out) /** * Write zeros into the view starting at offset - * @param {number} offset - * @param {number} length + * @param {number} offset + * @param {number} length * @param {DataView} view */ -function view_set_zeros(offset, length, view, out) +function view_set_zeros(offset, length, view, out) { out.eth_frame.fill(0, view.byteOffset + offset, view.byteOffset + offset + length); } @@ -961,17 +961,17 @@ function write_tcp(spec, out) { if(tcp.cwr) flags |= 0x80; let doff = TCP_HEADER_SIZE; - if (tcp.options) { - if (tcp.options.mss) { - view.setUint8(doff, 0x02) //mss option type - view.setUint8(doff + 1, 0x04) //option length + if(tcp.options) { + if(tcp.options.mss) { + view.setUint8(doff, 0x02); //mss option type + view.setUint8(doff + 1, 0x04); //option length view.setUint16(doff + 2, tcp.options.mss); doff += 4; } } let total_length = Math.ceil(doff / 4) * 4; // needs to a multiple of 4 bytes - if (tcp.options && total_length - doff > 0) { + if(tcp.options && total_length - doff > 0) { view_set_zeros(doff, total_length - doff, view, out); //write zeros into remaining space for options } diff --git a/src/browser/wisp_network.js b/src/browser/wisp_network.js index 8da4725c..eb669814 100644 --- a/src/browser/wisp_network.js +++ b/src/browser/wisp_network.js @@ -38,7 +38,7 @@ export function WispNetworkAdapter(wisp_url, bus, config) this.tcp_conn = {}; this.mtu = config.mtu; this.eth_encoder_buf = create_eth_encoder_buf(this.mtu); - + this.bus.register("net" + this.id + "-mac", function(mac) { this.vm_mac = new Uint8Array(mac.split(":").map(function(x) { return parseInt(x, 16); })); }, this); From 56b8c85e3fffb58695f4cd5f4526d40c14afc0f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=A6=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D0=BA=D0=BE=D0=B2?= <49730476+DimaThenekov@users.noreply.github.com> Date: Sat, 15 Nov 2025 19:56:24 +0300 Subject: [PATCH 285/301] Adding zstd_decompress to the filestore --- lib/filesystem.js | 4 ++-- src/browser/filestorage.js | 22 ++++++++++++++++------ src/browser/starter.js | 2 +- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/filesystem.js b/lib/filesystem.js index 6d9262aa..24b5e160 100644 --- a/lib/filesystem.js +++ b/lib/filesystem.js @@ -1068,7 +1068,7 @@ FS.prototype.get_buffer = async function(idx) else if(inode.status === STATUS_ON_STORAGE) { dbg_assert(inode.sha256sum, "Filesystem get_data: found inode on server without sha256sum"); - return await this.storage.read(inode.sha256sum, 0, inode.size); + return await this.storage.read(inode.sha256sum, 0, inode.size, inode.size); } else { @@ -1095,7 +1095,7 @@ FS.prototype.get_data = async function(idx, offset, count) else if(inode.status === STATUS_ON_STORAGE) { dbg_assert(inode.sha256sum, "Filesystem get_data: found inode on server without sha256sum"); - return await this.storage.read(inode.sha256sum, offset, count); + return await this.storage.read(inode.sha256sum, offset, count, inode.size); } else { diff --git a/src/browser/filestorage.js b/src/browser/filestorage.js index c84c7c11..7ce76c80 100644 --- a/src/browser/filestorage.js +++ b/src/browser/filestorage.js @@ -9,9 +9,10 @@ export function FileStorageInterface() {} * @param {string} sha256sum * @param {number} offset * @param {number} count + * @param {number} file_size * @return {!Promise} null if file does not exist. */ -FileStorageInterface.prototype.read = function(sha256sum, offset, count) {}; +FileStorageInterface.prototype.read = function(sha256sum, offset, count, file_size) {}; /** * Add a read-only file to the filestorage. @@ -83,8 +84,9 @@ MemoryFileStorage.prototype.uncache = function(sha256sum) * @implements {FileStorageInterface} * @param {FileStorageInterface} file_storage * @param {string} baseurl + * @param {function(number,Uint8Array):ArrayBuffer} zstd_decompress */ -export function ServerFileStorageWrapper(file_storage, baseurl) +export function ServerFileStorageWrapper(file_storage, baseurl, zstd_decompress) { dbg_assert(baseurl, "ServerMemoryFileStorage: baseurl should not be empty"); @@ -95,19 +97,26 @@ export function ServerFileStorageWrapper(file_storage, baseurl) this.storage = file_storage; this.baseurl = baseurl; + this.zstd_decompress = zstd_decompress; } /** * @param {string} sha256sum + * @param {number} file_size * @return {!Promise} */ -ServerFileStorageWrapper.prototype.load_from_server = function(sha256sum) +ServerFileStorageWrapper.prototype.load_from_server = function(sha256sum, file_size) { return new Promise((resolve, reject) => { load_file(this.baseurl + sha256sum, { done: async buffer => { - const data = new Uint8Array(buffer); + let data = new Uint8Array(buffer); + if (sha256sum.endsWith('.zst')) { + data = new Uint8Array( + this.zstd_decompress(file_size, data) + ); + } await this.cache(sha256sum, data); resolve(data); }}); @@ -118,14 +127,15 @@ ServerFileStorageWrapper.prototype.load_from_server = function(sha256sum) * @param {string} sha256sum * @param {number} offset * @param {number} count + * @param {number} file_size * @return {!Promise} */ -ServerFileStorageWrapper.prototype.read = async function(sha256sum, offset, count) +ServerFileStorageWrapper.prototype.read = async function(sha256sum, offset, count, file_size) { const data = await this.storage.read(sha256sum, offset, count); if(!data) { - const full_file = await this.load_from_server(sha256sum); + const full_file = await this.load_from_server(sha256sum, file_size); return full_file.subarray(offset, offset + count); } return data; diff --git a/src/browser/starter.js b/src/browser/starter.js index 87b66859..103f2199 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -436,7 +436,7 @@ V86.prototype.continue_init = async function(emulator, options) if(base_url) { - file_storage = new ServerFileStorageWrapper(file_storage, base_url); + file_storage = new ServerFileStorageWrapper(file_storage, base_url, this.zstd_decompress); } settings.fs9p = this.fs9p = new FS(file_storage); From 404c1e4ff0e5626e8340ffce32ebea35e9ab08cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=A6=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D0=BA=D0=BE=D0=B2?= <49730476+DimaThenekov@users.noreply.github.com> Date: Sat, 15 Nov 2025 20:54:37 +0300 Subject: [PATCH 286/301] Fixed a bug with parameters --- src/browser/filestorage.js | 2 +- src/browser/starter.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/filestorage.js b/src/browser/filestorage.js index 7ce76c80..e84f3be8 100644 --- a/src/browser/filestorage.js +++ b/src/browser/filestorage.js @@ -132,7 +132,7 @@ ServerFileStorageWrapper.prototype.load_from_server = function(sha256sum, file_s */ ServerFileStorageWrapper.prototype.read = async function(sha256sum, offset, count, file_size) { - const data = await this.storage.read(sha256sum, offset, count); + const data = await this.storage.read(sha256sum, offset, count, file_size); if(!data) { const full_file = await this.load_from_server(sha256sum, file_size); diff --git a/src/browser/starter.js b/src/browser/starter.js index 103f2199..757e75a9 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -436,7 +436,7 @@ V86.prototype.continue_init = async function(emulator, options) if(base_url) { - file_storage = new ServerFileStorageWrapper(file_storage, base_url, this.zstd_decompress); + file_storage = new ServerFileStorageWrapper(file_storage, base_url, this.zstd_decompress.bind(this)); } settings.fs9p = this.fs9p = new FS(file_storage); From 71584c833ccf6834ef7183ca20d31e1df3a9a058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=A6=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D0=BA=D0=BE=D0=B2?= <49730476+DimaThenekov@users.noreply.github.com> Date: Sat, 15 Nov 2025 20:59:20 +0300 Subject: [PATCH 287/301] Add space after "if" --- src/browser/filestorage.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/browser/filestorage.js b/src/browser/filestorage.js index e84f3be8..3f9aa027 100644 --- a/src/browser/filestorage.js +++ b/src/browser/filestorage.js @@ -112,7 +112,8 @@ ServerFileStorageWrapper.prototype.load_from_server = function(sha256sum, file_s load_file(this.baseurl + sha256sum, { done: async buffer => { let data = new Uint8Array(buffer); - if (sha256sum.endsWith('.zst')) { + if (sha256sum.endsWith(".zst")) + { data = new Uint8Array( this.zstd_decompress(file_size, data) ); From ae35964179e335c91704aee46722fe29c3c272b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=A6=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D0=BA=D0=BE=D0=B2?= <49730476+DimaThenekov@users.noreply.github.com> Date: Sat, 15 Nov 2025 21:02:28 +0300 Subject: [PATCH 288/301] Remove space after "if" --- src/browser/filestorage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/filestorage.js b/src/browser/filestorage.js index 3f9aa027..f3c5721d 100644 --- a/src/browser/filestorage.js +++ b/src/browser/filestorage.js @@ -112,7 +112,7 @@ ServerFileStorageWrapper.prototype.load_from_server = function(sha256sum, file_s load_file(this.baseurl + sha256sum, { done: async buffer => { let data = new Uint8Array(buffer); - if (sha256sum.endsWith(".zst")) + if(sha256sum.endsWith(".zst")) { data = new Uint8Array( this.zstd_decompress(file_size, data) From bd493da2dca51849859bd3d331f0cacfdb7371d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=A6=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D0=BA=D0=BE=D0=B2?= <49730476+DimaThenekov@users.noreply.github.com> Date: Thu, 20 Nov 2025 19:40:35 +0300 Subject: [PATCH 289/301] Add zstd compression support at tools --- tools/copy-to-sha256.py | 42 +++++++++++++++++++++++++++++++++-------- tools/fs2json.py | 6 ++++-- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/tools/copy-to-sha256.py b/tools/copy-to-sha256.py index 96e2b9b6..ec12fb22 100755 --- a/tools/copy-to-sha256.py +++ b/tools/copy-to-sha256.py @@ -7,8 +7,22 @@ import argparse import hashlib import shutil import tarfile +import sys +import io HASH_LENGTH = 8 +USE_COMPRESSION = True + +if USE_COMPRESSION: + if sys.version_info >= (3, 14): + from compression import zstd + else: + try: + import zstandard as zstd + except ImportError: + print("Error: zstandard module required when USE_COMPRESSION = True") + print("Install with: pip install zstandard") + sys.exit(1) def hash_file(filename) -> str: with open(filename, "rb", buffering=0) as f: @@ -64,30 +78,42 @@ def handle_dir(logger, from_path: str, to_path: str): continue file_hash = hash_file(absname) - filename = file_hash[0:HASH_LENGTH] + ".bin" + filename = file_hash[0:HASH_LENGTH] + (".bin.zst" if USE_COMPRESSION else ".bin") to_abs = os.path.join(to_path, filename) if os.path.exists(to_abs): logger.info("Exists, skipped {} ({})".format(to_abs, absname)) else: - logger.info("cp {} {}".format(absname, to_abs)) - shutil.copyfile(absname, to_abs) + if USE_COMPRESSION: + logger.info("Compressing {} {}".format(absname, to_abs)) + with open(absname, 'rb') as src_file: + with open(to_abs, 'wb') as dst_file: + zstd.ZstdCompressor(level=3).copy_stream(src_file, dst_file) + else: + logger.info("cp {} {}".format(absname, to_abs)) + shutil.copyfile(absname, to_abs) def handle_tar(logger, tar, to_path: str): for member in tar.getmembers(): if member.isfile() or member.islnk(): f = tar.extractfile(member) file_hash = hash_fileobj(f) - filename = file_hash[0:HASH_LENGTH] + ".bin" + filename = file_hash[0:HASH_LENGTH] + (".bin.zst" if USE_COMPRESSION else ".bin") to_abs = os.path.join(to_path, filename) if os.path.exists(to_abs): logger.info("Exists, skipped {} ({})".format(to_abs, member.name)) else: - logger.info("Extracted {} ({})".format(to_abs, member.name)) - to_file = open(to_abs, "wb") - f.seek(0) - shutil.copyfileobj(f, to_file) + if USE_COMPRESSION: + logger.info("Extracted and compressing {} ({})".format(to_abs, member.name)) + f.seek(0) + with open(to_abs, 'wb') as dst_file: + zstd.ZstdCompressor(level=3).copy_stream(f, dst_file) + else: + logger.info("Extracted {} ({})".format(to_abs, member.name)) + to_file = open(to_abs, "wb") + f.seek(0) + shutil.copyfileobj(f, to_file) if __name__ == "__main__": diff --git a/tools/fs2json.py b/tools/fs2json.py index 37b962c7..59d00bfa 100755 --- a/tools/fs2json.py +++ b/tools/fs2json.py @@ -17,6 +17,8 @@ import tarfile VERSION = 3 +USE_COMPRESSION = True + IDX_NAME = 0 IDX_SIZE = 1 IDX_MTIME = 2 @@ -197,7 +199,7 @@ def handle_dir(logger, path, exclude): obj[IDX_TARGET] = target elif isfile: file_hash = hash_file(absname) - filename = file_hash[0:HASH_LENGTH] + ".bin" + filename = file_hash[0:HASH_LENGTH] + (".bin.zst" if USE_COMPRESSION else ".bin") existing = filename_to_hash.get(filename) assert existing is None or existing == file_hash, "Collision in short hash (%s and %s)" % (existing, file_hash) filename_to_hash[filename] = file_hash @@ -240,7 +242,7 @@ def handle_tar(logger, tar): obj[IDX_MODE] |= S_IFREG f = tar.extractfile(member) file_hash = hash_fileobj(f) - filename = file_hash[0:HASH_LENGTH] + ".bin" + filename = file_hash[0:HASH_LENGTH] + (".bin.zst" if USE_COMPRESSION else ".bin") existing = filename_to_hash.get(filename) assert existing is None or existing == file_hash, "Collision in short hash (%s and %s)" % (existing, file_hash) filename_to_hash[filename] = file_hash From b91ad42fe20fa4ba89b6cb92324f59dd89e8e7ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=A6=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D0=BA=D0=BE=D0=B2?= <49730476+DimaThenekov@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:57:32 +0300 Subject: [PATCH 290/301] Add --zstd flag and use 19 lvl compression --- tools/copy-to-sha256.py | 48 +++++++++++++++++++----------------- tools/docker/alpine/build.sh | 4 +-- tools/fs2json.py | 16 ++++++------ 3 files changed, 36 insertions(+), 32 deletions(-) diff --git a/tools/copy-to-sha256.py b/tools/copy-to-sha256.py index ec12fb22..c82974dd 100755 --- a/tools/copy-to-sha256.py +++ b/tools/copy-to-sha256.py @@ -11,18 +11,6 @@ import sys import io HASH_LENGTH = 8 -USE_COMPRESSION = True - -if USE_COMPRESSION: - if sys.version_info >= (3, 14): - from compression import zstd - else: - try: - import zstandard as zstd - except ImportError: - print("Error: zstandard module required when USE_COMPRESSION = True") - print("Install with: pip install zstandard") - sys.exit(1) def hash_file(filename) -> str: with open(filename, "rb", buffering=0) as f: @@ -43,23 +31,39 @@ def main(): formatter_class=argparse.RawTextHelpFormatter) args.add_argument("from_path", metavar="from", help="from") args.add_argument("to_path", metavar="to", help="to") + args.add_argument("--zstd", action="store_true", help="Use Zstandard compression") args = args.parse_args() from_path = os.path.normpath(args.from_path) to_path = os.path.normpath(args.to_path) + # Import zstd only if compression is requested + zstd_module = None + if args.zstd: + if sys.version_info >= (3, 14): + from compression import zstd + zstd_module = zstd + else: + try: + import zstandard as zstd + zstd_module = zstd + except ImportError: + print("Error: zstandard module required when using --zstd flag") + print("Install with: pip install zstandard") + sys.exit(1) + if os.path.isfile(from_path): tar = tarfile.open(from_path, "r") else: tar = None if tar: - handle_tar(logger, tar, to_path) + handle_tar(logger, tar, to_path, args.zstd, zstd_module) else: - handle_dir(logger, from_path, to_path) + handle_dir(logger, from_path, to_path, args.zstd, zstd_module) -def handle_dir(logger, from_path: str, to_path: str): +def handle_dir(logger, from_path: str, to_path: str, use_compression: bool, zstd_module): def onerror(oserror): logger.warning(oserror) @@ -78,37 +82,37 @@ def handle_dir(logger, from_path: str, to_path: str): continue file_hash = hash_file(absname) - filename = file_hash[0:HASH_LENGTH] + (".bin.zst" if USE_COMPRESSION else ".bin") + filename = file_hash[0:HASH_LENGTH] + (".bin.zst" if use_compression else ".bin") to_abs = os.path.join(to_path, filename) if os.path.exists(to_abs): logger.info("Exists, skipped {} ({})".format(to_abs, absname)) else: - if USE_COMPRESSION: + if use_compression: logger.info("Compressing {} {}".format(absname, to_abs)) with open(absname, 'rb') as src_file: with open(to_abs, 'wb') as dst_file: - zstd.ZstdCompressor(level=3).copy_stream(src_file, dst_file) + zstd_module.ZstdCompressor(level=19).copy_stream(src_file, dst_file) else: logger.info("cp {} {}".format(absname, to_abs)) shutil.copyfile(absname, to_abs) -def handle_tar(logger, tar, to_path: str): +def handle_tar(logger, tar, to_path: str, use_compression: bool, zstd_module): for member in tar.getmembers(): if member.isfile() or member.islnk(): f = tar.extractfile(member) file_hash = hash_fileobj(f) - filename = file_hash[0:HASH_LENGTH] + (".bin.zst" if USE_COMPRESSION else ".bin") + filename = file_hash[0:HASH_LENGTH] + (".bin.zst" if use_compression else ".bin") to_abs = os.path.join(to_path, filename) if os.path.exists(to_abs): logger.info("Exists, skipped {} ({})".format(to_abs, member.name)) else: - if USE_COMPRESSION: + if use_compression: logger.info("Extracted and compressing {} ({})".format(to_abs, member.name)) f.seek(0) with open(to_abs, 'wb') as dst_file: - zstd.ZstdCompressor(level=3).copy_stream(f, dst_file) + zstd_module.ZstdCompressor(level=19).copy_stream(f, dst_file) else: logger.info("Extracted {} ({})".format(to_abs, member.name)) to_file = open(to_abs, "wb") diff --git a/tools/docker/alpine/build.sh b/tools/docker/alpine/build.sh index e6c4c7aa..72edfb8f 100755 --- a/tools/docker/alpine/build.sh +++ b/tools/docker/alpine/build.sh @@ -20,10 +20,10 @@ docker export "$CONTAINER_NAME" -o "$OUT_ROOTFS_TAR" # https://github.com/iximiuz/docker-to-linux/issues/19#issuecomment-1242809707 tar -f "$OUT_ROOTFS_TAR" --delete ".dockerenv" || true -../../../tools/fs2json.py --out "$OUT_FSJSON" "$OUT_ROOTFS_TAR" +../../../tools/fs2json.py --zstd --out "$OUT_FSJSON" "$OUT_ROOTFS_TAR" # Note: Not deleting old files here mkdir -p "$OUT_ROOTFS_FLAT" -../../../tools/copy-to-sha256.py "$OUT_ROOTFS_TAR" "$OUT_ROOTFS_FLAT" +../../../tools/copy-to-sha256.py --zstd "$OUT_ROOTFS_TAR" "$OUT_ROOTFS_FLAT" echo "$OUT_ROOTFS_TAR", "$OUT_ROOTFS_FLAT" and "$OUT_FSJSON" created. diff --git a/tools/fs2json.py b/tools/fs2json.py index 59d00bfa..2bf35aab 100755 --- a/tools/fs2json.py +++ b/tools/fs2json.py @@ -17,8 +17,6 @@ import tarfile VERSION = 3 -USE_COMPRESSION = True - IDX_NAME = 0 IDX_SIZE = 1 IDX_MTIME = 2 @@ -70,6 +68,8 @@ def main(): args.add_argument("path", metavar="path-or-tar", help="Base path or tar file to include in JSON") + args.add_argument("--zstd", action="store_true", + help="Use Zstandard compression") args = args.parse_args() @@ -81,9 +81,9 @@ def main(): tar = None if tar: - (root, total_size) = handle_tar(logger, tar) + (root, total_size) = handle_tar(logger, tar, args.zstd) else: - (root, total_size) = handle_dir(logger, path, args.exclude) + (root, total_size) = handle_dir(logger, path, args.exclude, args.zstd) if False: # normalize the order of children, useful to debug differences between @@ -105,7 +105,7 @@ def main(): logger.info("Creating json ...") json.dump(result, args.out, check_circular=False, separators=(',', ':')) -def handle_dir(logger, path, exclude): +def handle_dir(logger, path, exclude, use_compression): path = path + "/" exclude = exclude or [] exclude = [os.path.join("/", os.path.normpath(p)) for p in exclude] @@ -199,7 +199,7 @@ def handle_dir(logger, path, exclude): obj[IDX_TARGET] = target elif isfile: file_hash = hash_file(absname) - filename = file_hash[0:HASH_LENGTH] + (".bin.zst" if USE_COMPRESSION else ".bin") + filename = file_hash[0:HASH_LENGTH] + (".bin.zst" if use_compression else ".bin") existing = filename_to_hash.get(filename) assert existing is None or existing == file_hash, "Collision in short hash (%s and %s)" % (existing, file_hash) filename_to_hash[filename] = file_hash @@ -214,7 +214,7 @@ def handle_dir(logger, path, exclude): return (mainroot, total_size) -def handle_tar(logger, tar): +def handle_tar(logger, tar, use_compression): mainroot = [] filename_to_hash = {} total_size = 0 @@ -242,7 +242,7 @@ def handle_tar(logger, tar): obj[IDX_MODE] |= S_IFREG f = tar.extractfile(member) file_hash = hash_fileobj(f) - filename = file_hash[0:HASH_LENGTH] + (".bin.zst" if USE_COMPRESSION else ".bin") + filename = file_hash[0:HASH_LENGTH] + (".bin.zst" if use_compression else ".bin") existing = filename_to_hash.get(filename) assert existing is None or existing == file_hash, "Collision in short hash (%s and %s)" % (existing, file_hash) filename_to_hash[filename] = file_hash From f0325850909ef2f5cdb71c811986cd3b0b0fbf07 Mon Sep 17 00:00:00 2001 From: Rob Blanckaert Date: Wed, 26 Nov 2025 14:07:03 -0800 Subject: [PATCH 291/301] Correct sign bit of pointers used with zstd contexts --- src/state.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/state.js b/src/state.js index e34aaa6f..431179e8 100644 --- a/src/state.js +++ b/src/state.js @@ -228,15 +228,15 @@ export function restore_state(cpu, state) { const ctx = cpu.zstd_create_ctx(state.length); - new Uint8Array(cpu.wasm_memory.buffer, cpu.zstd_get_src_ptr(ctx), state.length).set(state); + new Uint8Array(cpu.wasm_memory.buffer, cpu.zstd_get_src_ptr(ctx) >>> 0, state.length).set(state); let ptr = cpu.zstd_read(ctx, 16); - const header_block = new Uint8Array(cpu.wasm_memory.buffer, ptr, 16); + const header_block = new Uint8Array(cpu.wasm_memory.buffer, ptr >>> 0, 16); const info_block_len = read_state_header(header_block, false); cpu.zstd_read_free(ptr, 16); ptr = cpu.zstd_read(ctx, info_block_len); - const info_block_buffer = new Uint8Array(cpu.wasm_memory.buffer, ptr, info_block_len); + const info_block_buffer = new Uint8Array(cpu.wasm_memory.buffer, ptr >>> 0, info_block_len); const info_block_obj = read_info_block(info_block_buffer); cpu.zstd_read_free(ptr, info_block_len); @@ -253,7 +253,7 @@ export function restore_state(cpu, state) if(buffer_info.length > CHUNK_SIZE) { - const ptr = cpu.zstd_read(ctx, front_padding); + const ptr = cpu.zstd_read(ctx, front_padding) >>> 0; cpu.zstd_read_free(ptr, front_padding); const buffer = new Uint8Array(buffer_info.length); @@ -267,7 +267,7 @@ export function restore_state(cpu, state) const to_read = Math.min(remaining, CHUNK_SIZE); const ptr = cpu.zstd_read(ctx, to_read); - buffer.set(new Uint8Array(cpu.wasm_memory.buffer, ptr, to_read), have); + buffer.set(new Uint8Array(cpu.wasm_memory.buffer, ptr >>> 0, to_read), have); cpu.zstd_read_free(ptr, to_read); have += to_read; @@ -276,7 +276,7 @@ export function restore_state(cpu, state) else { const ptr = cpu.zstd_read(ctx, front_padding + buffer_info.length); - const offset = ptr + front_padding; + const offset = (ptr >>> 0) + front_padding; buffers.push(cpu.wasm_memory.buffer.slice(offset, offset + buffer_info.length)); cpu.zstd_read_free(ptr, front_padding + buffer_info.length); } From 524d67326e4af9a7d9c984c6ae0ec820fca38b77 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Fri, 28 Nov 2025 10:55:02 +0200 Subject: [PATCH 292/301] mention RootlessRelay in docs --- docs/networking.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/networking.md b/docs/networking.md index 08917348..ca6067b1 100644 --- a/docs/networking.md +++ b/docs/networking.md @@ -129,6 +129,7 @@ Since this backend (including its proxy server) only forwards unmodified etherne * **[go-websockproxy](https://github.com/gdm85/go-websockproxy)** -- one TAP device for all clients, written in Go, without integraded DHCP but with integrated TLS support * **[node-relay](https://github.com/krishenriksen/node-relay)** -- like websockproxy but written for NodeJS (dnsmasq/no TLS), see [New websocket ethernet switch built using Node.js #777](https://github.com/copy/v86/discussions/777) * **[wsnic](https://github.com/chschnell/wsnic)** -- uses a single bridge and one TAP device per client, integrates dnsmasq for DHCP/DNS and stunnel for TLS +* **[RootlessRelay](https://github.com/obegron/rootlessRelay)** -- uses its own network stack that doesn't require TUN/TAP devices, has a built-in reverse proxy and admin interface, see [RootlessRelay #1442](https://github.com/copy/v86/discussions/1442) [See here](https://github.com/copy/v86/discussions/1199#discussioncomment-12026845) for a benchmark comparing the download performance of these proxy servers. From b6c940d0d481a43de006e40adfd6c081db7bedae Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Fri, 28 Nov 2025 10:37:47 +0200 Subject: [PATCH 293/301] make `libv86-debug.mjs` readable, add missing `ls` --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 3cf8e987..5754fc14 100644 --- a/Makefile +++ b/Makefile @@ -172,6 +172,7 @@ build/libv86-debug.js: $(CLOSURE) src/*.js lib/*.js src/browser/*.js --js $(CORE_FILES)\ --js $(BROWSER_FILES)\ --js $(LIB_FILES) + ls -lh build/libv86-debug.js build/libv86-debug.mjs: $(CLOSURE) src/*.js lib/*.js src/browser/*.js mkdir -p build @@ -179,6 +180,7 @@ build/libv86-debug.mjs: $(CLOSURE) src/*.js lib/*.js src/browser/*.js --js_output_file build/libv86-debug.mjs\ --define=DEBUG=true\ $(CLOSURE_FLAGS)\ + $(CLOSURE_READABLE)\ --compilation_level SIMPLE\ --jscomp_off=missingProperties\ --output_wrapper ';let module = {exports:{}}; %output%; export default module.exports.V86; export let {V86, CPU} = module.exports;'\ From 0898f64b28089e3214ea2afed9eeb980f9b4db56 Mon Sep 17 00:00:00 2001 From: Rob Blanckaert Date: Sat, 15 Mar 2025 23:29:42 -0700 Subject: [PATCH 294/301] Fake networking socket handling cleanup. --- src/browser/fake_network.js | 38 +++++++++++++++++++++++++----------- src/browser/fetch_network.js | 27 ++++++++++--------------- src/browser/wisp_network.js | 12 ++++-------- 3 files changed, 41 insertions(+), 36 deletions(-) diff --git a/src/browser/fake_network.js b/src/browser/fake_network.js index 0f894ded..8ac0ebec 100644 --- a/src/browser/fake_network.js +++ b/src/browser/fake_network.js @@ -254,13 +254,30 @@ function handle_fake_tcp(packet, adapter) { const tuple = `${packet.ipv4.src.join(".")}:${packet.tcp.sport}:${packet.ipv4.dest.join(".")}:${packet.tcp.dport}`; - if(packet.tcp.syn) { + if(packet.tcp.syn && !packet.tcp.ack) { if(adapter.tcp_conn[tuple]) { dbg_log("SYN to already opened port", LOG_FETCH); + delete adapter.tcp_conn[tuple]; } - if(adapter.on_tcp_connection(packet, tuple)) { - return; + + let conn = new TCPConnection(adapter); + conn.state = TCP_STATE_SYN_RECEIVED; + conn.tuple = tuple; + conn.last = packet; + + conn.hsrc = packet.eth.dest; + conn.psrc = packet.ipv4.dest; + conn.sport = packet.tcp.dport; + conn.hdest = packet.eth.src; + conn.dport = packet.tcp.sport; + conn.pdest = packet.ipv4.src; + + adapter.bus.pair.send("tcp-connection", conn); + + if(adapter.on_tcp_connection) { + adapter.on_tcp_connection(conn, packet); } + if(adapter.tcp_conn[tuple]) return; } if(!adapter.tcp_conn[tuple]) { @@ -1109,7 +1126,9 @@ TCPConnection.prototype.connect = function() { this.ack = 1; this.start_seq = 0; this.winsize = 64240; - this.state = TCP_STATE_SYN_SENT; + if(this.state !== TCP_STATE_SYN_PROBE) { + this.state = TCP_STATE_SYN_SENT; + } let reply = this.ipv4_reply(); reply.ipv4.id = 2345; @@ -1125,16 +1144,12 @@ TCPConnection.prototype.connect = function() { }; -TCPConnection.prototype.accept = function(packet) { +TCPConnection.prototype.accept = function(packet=undefined) { + packet = packet || this.last; + this.net.tcp_conn[this.tuple] = this; this.seq = 1338; this.ack = packet.tcp.seq + 1; this.start_seq = packet.tcp.seq; - this.hsrc = this.net.router_mac; - this.psrc = packet.ipv4.dest; - this.sport = packet.tcp.dport; - this.hdest = packet.eth.src; - this.dport = packet.tcp.sport; - this.pdest = packet.ipv4.src; this.winsize = packet.tcp.winsize; let reply = this.ipv4_reply(); @@ -1156,6 +1171,7 @@ TCPConnection.prototype.accept = function(packet) { }; TCPConnection.prototype.process = function(packet) { + this.last = packet; if(this.state === TCP_STATE_CLOSED) { // dbg_log(`TCP[${this.tuple}]: WARNING: connection already closed, packet dropped`, LOG_FETCH); const reply = this.packet_reply(packet, {rst: true}); diff --git a/src/browser/fetch_network.js b/src/browser/fetch_network.js index 74f763f6..f40fc9c3 100644 --- a/src/browser/fetch_network.js +++ b/src/browser/fetch_network.js @@ -1,5 +1,4 @@ import { LOG_FETCH } from "../const.js"; -import { h } from "../lib.js"; import { dbg_log } from "../log.js"; import { @@ -47,26 +46,18 @@ export function FetchNetworkAdapter(bus, config) { this.send(data); }, this); + this.bus.register("tcp-connection", (conn) => { + if(conn.sport === 80) { + conn.on("data", on_data_http); + conn.accept(); + } + }, this); } FetchNetworkAdapter.prototype.destroy = function() { }; -FetchNetworkAdapter.prototype.on_tcp_connection = function(packet, tuple) -{ - if(packet.tcp.dport === 80) { - let conn = new TCPConnection(this); - conn.state = TCP_STATE_SYN_RECEIVED; - conn.on("data", on_data_http); - conn.tuple = tuple; - conn.accept(packet); - this.tcp_conn[tuple] = conn; - return true; - } - return false; -}; - FetchNetworkAdapter.prototype.connect = function(port) { return fake_tcp_connect(port, this); @@ -144,7 +135,7 @@ async function on_data_http(data) const fetch_url = this.net.cors_proxy ? this.net.cors_proxy + encodeURIComponent(target.href) : target.href; const encoder = new TextEncoder(); let response_started = false; - this.net.fetch(fetch_url, opts).then((resp) => { + let handler = (resp) => { let resp_headers = new Headers(resp.headers); resp_headers.delete("content-encoding"); resp_headers.delete("keep-alive"); @@ -177,7 +168,9 @@ async function on_data_http(data) this.close(); }); } - }) + }; + + this.net.fetch(fetch_url, opts).then(handler) .catch((e) => { console.warn("Fetch Failed: " + fetch_url + "\n" + e); if(!response_started) { diff --git a/src/browser/wisp_network.js b/src/browser/wisp_network.js index eb669814..7edb2ca6 100644 --- a/src/browser/wisp_network.js +++ b/src/browser/wisp_network.js @@ -188,16 +188,12 @@ WispNetworkAdapter.prototype.destroy = function() }; /** + * @param {TCPConnection} conn * @param {Uint8Array} packet - * @param {String} tuple */ -WispNetworkAdapter.prototype.on_tcp_connection = function(packet, tuple) +WispNetworkAdapter.prototype.on_tcp_connection = function(conn, packet) { - let conn = new TCPConnection(this); - conn.state = TCP_STATE_SYN_RECEIVED; - conn.tuple = tuple; conn.stream_id = this.last_stream++; - this.tcp_conn[tuple] = conn; conn.on("data", data => { if(data.length !== 0) { @@ -224,7 +220,7 @@ WispNetworkAdapter.prototype.on_tcp_connection = function(packet, tuple) type: "CONNECT", stream_id: conn.stream_id, hostname: packet.ipv4.dest.join("."), - port: packet.tcp.dport, + port: conn.sport, data_callback: (data) => { conn.write(data); }, @@ -233,7 +229,7 @@ WispNetworkAdapter.prototype.on_tcp_connection = function(packet, tuple) } }); - conn.accept(packet); + conn.accept(); return true; }; From dcc1700cef77295a8f16718b3b959cfe3294d8f4 Mon Sep 17 00:00:00 2001 From: Fabian Date: Thu, 25 Dec 2025 16:42:25 +0100 Subject: [PATCH 295/301] add bootOs (#1434) --- src/browser/main.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/browser/main.js b/src/browser/main.js index 65101c24..35ed2929 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -715,6 +715,15 @@ function onload() name: "Invaders", homepage: "https://github.com/nanochess/Invaders", }, + { + id: "bootos", + fda: { + url: host + "bootos-all.img", + size: 368640, + }, + name: "bootOS", + homepage: "https://github.com/nanochess/bootOS", + }, { id: "sectorlisp", fda: { From 40f504e933e389507d17969b6df0e95308836d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Sun, 28 Sep 2025 10:25:05 +0200 Subject: [PATCH 296/301] Add type definitions and update files in package.json --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index fc4415b5..e5c15b8b 100644 --- a/package.json +++ b/package.json @@ -7,12 +7,14 @@ "files": [ "Readme.md", "LICENSE", + "v86.d.ts", "build/libv86*.js", "build/libv86*.js.map", "build/*.mjs", "build/v86*.wasm" ], "main": "build/libv86.mjs", + "types": "v86.d.ts", "repository": "github:copy/v86", "type": "module" } From 9b2e65166f08970e9113493ad5d02599f0290145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Sun, 28 Sep 2025 22:34:49 +0200 Subject: [PATCH 297/301] Change enums to constant enums in v86.d.ts --- v86.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/v86.d.ts b/v86.d.ts index 29f762ab..e5234183 100644 --- a/v86.d.ts +++ b/v86.d.ts @@ -35,7 +35,7 @@ export type V86Image = //| { buffer: File; async?: boolean; }; // only in browsers: https://developer.mozilla.org/en-US/docs/Web/API/File | { buffer: ArrayBuffer }; -export enum LogLevel { +export const enum LogLevel { LOG_ALL = -1, LOG_NONE = 0, LOG_OTHER = 0x000001, @@ -64,7 +64,7 @@ export enum LogLevel { LOG_SB16 = 0x800000, } -export enum BootOrder { +export const enum BootOrder { AUTO = 0, CD_FLOPPY_HARDDISK = 0x213, CD_HARDDISK_FLOPPY = 0x123, @@ -73,7 +73,7 @@ export enum BootOrder { HARDDISK_CD_FLOPPY = 0x132, } -export enum Event { +export const enum Event { FS_ATTACH = "9p-attach", FS_READ_END = "9p-read-end", FS_READ_START = "9p-read-start", From 946819902b7480626c123cfb73a0c038a19a2803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Mon, 29 Sep 2025 10:24:38 +0200 Subject: [PATCH 298/301] Reverts some changes --- v86.d.ts | 49 ++++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/v86.d.ts b/v86.d.ts index e5234183..3fdaafdc 100644 --- a/v86.d.ts +++ b/v86.d.ts @@ -35,7 +35,7 @@ export type V86Image = //| { buffer: File; async?: boolean; }; // only in browsers: https://developer.mozilla.org/en-US/docs/Web/API/File | { buffer: ArrayBuffer }; -export const enum LogLevel { +export enum LogLevel { LOG_ALL = -1, LOG_NONE = 0, LOG_OTHER = 0x000001, @@ -64,7 +64,7 @@ export const enum LogLevel { LOG_SB16 = 0x800000, } -export const enum BootOrder { +export enum BootOrder { AUTO = 0, CD_FLOPPY_HARDDISK = 0x213, CD_HARDDISK_FLOPPY = 0x123, @@ -73,29 +73,28 @@ export const enum BootOrder { HARDDISK_CD_FLOPPY = 0x132, } -export const enum Event { - FS_ATTACH = "9p-attach", - FS_READ_END = "9p-read-end", - FS_READ_START = "9p-read-start", - FS_WRITE_END = "9p-write-end", - DOWNLOAD_ERROR = "download-error", - DOWNLOAD_PROGRESS = "download-progress", - EMULATOR_LOADED = "emulator-loaded", - EMULATOR_READY = "emulator-ready", - EMULATOR_STARTED = "emulator-started", - EMULATOR_STOPPED = "emulator-stopped", - ETH_RECEIVE_END = "eth-receive-end", - ETH_TRANSMIT_END = "eth-transmit-end", - IDE_READ_END = "ide-read-end", - IDE_READ_START = "ide-read-start", - IDE_WRITE_END = "ide-write-end", - MOUSE_ENABLE = "mouse-enable", - NET0_SEND = "net0-send", - SCREEN_PUT_CHAR = "screen-put-char", - SCREEN_SET_SIZE = "screen-set-size", - SERIAL0_OUTPUT_BYTE = "serial0-output-byte", - VIRTIO_CONSOLE0_OUTPUT_BYTES = "virtio-console0-output-bytes", -} +export type Event = + | "9p-attach" + | "9p-read-end" + | "9p-read-start" + | "9p-write-end" + | "download-error" + | "download-progress" + | "emulator-loaded" + | "emulator-ready" + | "emulator-started" + | "emulator-stopped" + | "eth-receive-end" + | "eth-transmit-end" + | "ide-read-end" + | "ide-read-start" + | "ide-write-end" + | "mouse-enable" + | "net0-send" + | "screen-put-char" + | "screen-set-size" + | "serial0-output-byte" + | "virtio-console0-output-bytes"; /** * emulator instance constructor options. From 880fa18e357ee40b4e07a05b70620fab68597a8f Mon Sep 17 00:00:00 2001 From: Fabian Date: Thu, 25 Dec 2025 19:59:06 +0100 Subject: [PATCH 299/301] fix: Handle STI instruction near end of page --- src/rust/jit.rs | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/rust/jit.rs b/src/rust/jit.rs index 09f06eec..da3d80bc 100644 --- a/src/rust/jit.rs +++ b/src/rust/jit.rs @@ -485,7 +485,7 @@ fn jit_find_basic_blocks( let mut pages: HashSet = HashSet::new(); let mut page_blacklist = HashSet::new(); - // 16-bit doesn't not work correctly, most likely due to instruction pointer wrap-around + // 16-bit doesn't work correctly, most likely due to instruction pointer wrap-around let max_pages = if cpu.state_flags.is_32() { unsafe { MAX_PAGES } } else { 1 }; for virt_addr in entry_points { @@ -539,13 +539,22 @@ fn jit_find_basic_blocks( ..cpu }; let analysis = analysis::analyze_step(&mut cpu); - current_block.number_of_instructions += 1; let has_next_instruction = !analysis.no_next_instruction; current_address = cpu.eip; dbg_assert!(Page::page_of(current_address) == Page::page_of(addr_before_instruction)); let current_virt_addr = to_visit & !0xFFF | current_address as i32 & 0xFFF; + if analysis.ty == AnalysisType::STI && is_near_end_of_page(current_address) { + // cut off before the STI so that it is handled by interpreted mode + profiler::stat_increment(stat::COMPILE_CUT_OFF_AT_END_OF_PAGE); + break; + } + + current_block.number_of_instructions += 1; + current_block.last_instruction_addr = addr_before_instruction; + current_block.end_addr = current_address; + match analysis.ty { AnalysisType::Normal | AnalysisType::STI => { dbg_assert!(has_next_instruction); @@ -557,26 +566,22 @@ fn jit_find_basic_blocks( marked_as_entry.insert(current_virt_addr); to_visit_stack.push(current_virt_addr); - current_block.last_instruction_addr = addr_before_instruction; - current_block.end_addr = current_address; break; } if analysis.ty == AnalysisType::STI { - current_block.has_sti = true; - dbg_assert!( !is_near_end_of_page(current_address), - "TODO: Handle STI instruction near end of page" + "should be handled above" ); + + current_block.has_sti = true; } else { // Only split non-STI blocks (one instruction needs to run after STI before // handle_irqs may be called) if basic_blocks.contains_key(¤t_address) { - current_block.last_instruction_addr = addr_before_instruction; - current_block.end_addr = current_address; dbg_assert!(!is_near_end_of_page(current_address)); current_block.ty = BasicBlockType::Normal { next_block_addr: Some(current_address), @@ -629,9 +634,6 @@ fn jit_find_basic_blocks( jump_offset_is_32: is_32, }; - current_block.last_instruction_addr = addr_before_instruction; - current_block.end_addr = current_address; - break; }, AnalysisType::Jump { @@ -669,8 +671,6 @@ fn jit_find_basic_blocks( jump_offset: offset, jump_offset_is_32: is_32, }; - current_block.last_instruction_addr = addr_before_instruction; - current_block.end_addr = current_address; break; }, @@ -690,20 +690,21 @@ fn jit_find_basic_blocks( current_block.ty = BasicBlockType::AbsoluteEip; } - current_block.last_instruction_addr = addr_before_instruction; - current_block.end_addr = current_address; break; }, } if is_near_end_of_page(current_address) { - current_block.last_instruction_addr = addr_before_instruction; - current_block.end_addr = current_address; profiler::stat_increment(stat::COMPILE_CUT_OFF_AT_END_OF_PAGE); break; } } + if current_block.number_of_instructions == 0 { + // Empty basic block, don't insert (only happens when STI is found near end of page) + continue; + } + let previous_block = basic_blocks .range(..current_block.addr) .next_back() From f053c4af7ba4d23030d6477407a5dfddac8ff22f Mon Sep 17 00:00:00 2001 From: Rob Blanckaert Date: Fri, 28 Nov 2025 12:02:12 -0800 Subject: [PATCH 300/301] Allow Maps in state object --- src/state.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/state.js b/src/state.js index 431179e8..af430fdc 100644 --- a/src/state.js +++ b/src/state.js @@ -20,6 +20,7 @@ function StateLoadError(msg) StateLoadError.prototype = new Error; const CONSTRUCTOR_TABLE = { + "Map": Map, "Uint8Array": Uint8Array, "Int8Array": Int8Array, "Uint16Array": Uint16Array, @@ -43,6 +44,17 @@ function save_object(obj, saved_buffers) return obj.map(x => save_object(x, saved_buffers)); } + if(obj instanceof Map) + { + return { + "__state_type__": "Map", + "args": Array.from(obj.entries()).map(([k, v]) => [ + save_object(k, saved_buffers), + save_object(v, saved_buffers), + ]), + }; + } + if(obj.constructor === Object) { console.log(obj); @@ -108,6 +120,10 @@ function restore_buffers(obj, buffers) const constructor = CONSTRUCTOR_TABLE[type]; dbg_assert(constructor, "Unkown type: " + type); + if(obj["args"] !== undefined) { + return new constructor(obj["args"]); + } + const buffer = buffers[obj["buffer_id"]]; return new constructor(buffer); } From 11cf7dd926c5901309e7df0d34905c9a21ad4d89 Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 26 Dec 2025 22:39:16 +0100 Subject: [PATCH 301/301] fix: some properties missing from state images --- src/cpu.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/cpu.js b/src/cpu.js index 186e571a..021164e7 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -573,6 +573,12 @@ CPU.prototype.get_state = function() state[83] = this.devices.virtio_net; state[84] = this.devices.virtio_balloon; + // state[85] new ide set above + + state[86] = this.last_result; + state[87] = this.fpu_status_word; + state[88] = this.mxcsr; + return state; }; @@ -759,6 +765,10 @@ CPU.prototype.set_state = function(state) this.fpu_dp_selector[0] = state[74]; this.fpu_opcode[0] = state[75]; + if(state[86] !== undefined) this.last_result = state[86]; + if(state[87] !== undefined) this.fpu_status_word = state[87]; + if(state[88] !== undefined) this.mxcsr = state[88]; + const bitmap = new Bitmap(state[78].buffer); const packed_memory = state[77]; this.unpack_memory(bitmap, packed_memory);