mirror of
https://github.com/copy/v86.git
synced 2026-01-01 04:53:25 +00:00
Compare commits
301 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11cf7dd926 | ||
|
|
f053c4af7b | ||
|
|
880fa18e35 | ||
|
|
946819902b | ||
|
|
9b2e65166f | ||
|
|
40f504e933 | ||
|
|
dcc1700cef | ||
|
|
0898f64b28 | ||
|
|
b6c940d0d4 | ||
|
|
524d67326e | ||
|
|
f032585090 | ||
|
|
b91ad42fe2 | ||
|
|
bd493da2dc | ||
|
|
ae35964179 | ||
|
|
71584c833c | ||
|
|
404c1e4ff0 | ||
|
|
56b8c85e3f | ||
|
|
c380cabc4c | ||
|
|
97cd3069ac | ||
|
|
081b1eaafd | ||
|
|
1a1afab08d | ||
|
|
0beafeee07 | ||
|
|
d093088616 | ||
|
|
ec90cede42 | ||
|
|
c004ebb3ab | ||
|
|
db22e06746 | ||
|
|
5a48c7e867 | ||
|
|
e65e4f441e | ||
|
|
54ec8e8daa | ||
|
|
c70dafd411 | ||
|
|
81eb6a1d41 | ||
|
|
a4c9943c3d | ||
|
|
777b8c397c | ||
|
|
667313401d | ||
|
|
9735a0eed8 | ||
|
|
ac5e657e5a | ||
|
|
71f4a416ab | ||
|
|
ccb5e1ce5d | ||
|
|
772f0a1faa | ||
|
|
0669f7a477 | ||
|
|
4e15afcd77 | ||
|
|
6e3afa67ab | ||
|
|
f6083ac6b0 | ||
|
|
be7a8aa874 | ||
|
|
7c4cbd0b04 | ||
|
|
2f65f9d6d1 | ||
|
|
b0d20cee00 | ||
|
|
464cddcbed | ||
|
|
4251ac1a9e | ||
|
|
18ff2d85a1 | ||
|
|
07270db5f4 | ||
|
|
dfef28b7e1 | ||
|
|
0ee4138226 | ||
|
|
7bd35defe9 | ||
|
|
b7ad6ee562 | ||
|
|
95ca55d943 | ||
|
|
3830424c4e | ||
|
|
88d4ba8fe5 | ||
|
|
865725bbac | ||
|
|
21a6318a13 | ||
|
|
c1785b724f | ||
|
|
c78885fd84 | ||
|
|
1301eb1196 | ||
|
|
3b0325d899 | ||
|
|
403f7168b2 | ||
|
|
2001db0db3 | ||
|
|
0864656091 | ||
|
|
a480e88594 | ||
|
|
73945e4574 | ||
|
|
da3547a96f | ||
|
|
7a2df17dae | ||
|
|
dfbe60926b | ||
|
|
8f66766f49 | ||
|
|
fe50e6ff6f | ||
|
|
c6c2017ba8 | ||
|
|
244a877989 | ||
|
|
7c6da9d8f2 | ||
|
|
9a2e81b35b | ||
|
|
525fa58719 | ||
|
|
2f040c0502 | ||
|
|
ab4440d28f | ||
|
|
c2dc88dd15 | ||
|
|
af483a9036 | ||
|
|
d074a7f8a4 | ||
|
|
4aa1c16995 | ||
|
|
46fbe8fa40 | ||
|
|
80d77f23a8 | ||
|
|
76595e4612 | ||
|
|
2d3965b53f | ||
|
|
990fecb59f | ||
|
|
afb30c608e | ||
|
|
007b0d9e71 | ||
|
|
5aeac32af3 | ||
|
|
13d4dd0ca9 | ||
|
|
0cc8f0221e | ||
|
|
80a3ab7b3d | ||
|
|
b35621e9aa | ||
|
|
6d2ef6ca41 | ||
|
|
732390abb9 | ||
|
|
a1f160cf32 | ||
|
|
fd80637976 | ||
|
|
c7a3b8b050 | ||
|
|
e27e89877c | ||
|
|
318003f117 | ||
|
|
88aff1091f | ||
|
|
1acdbbe785 | ||
|
|
f020b016f5 | ||
|
|
137ab1f9ad | ||
|
|
a25e7181d5 | ||
|
|
519480d4cd | ||
|
|
92ba111d92 | ||
|
|
47fb4e1dd2 | ||
|
|
e16ce249fe | ||
|
|
00203987f4 | ||
|
|
b91afaa821 | ||
|
|
26ae4dc019 | ||
|
|
200219335b | ||
|
|
e8235c5f3e | ||
|
|
d7e9c64975 | ||
|
|
51998355ed | ||
|
|
ed3f083d50 | ||
|
|
0a3a406f69 | ||
|
|
f2d8ee7334 | ||
|
|
30cd26ca58 | ||
|
|
4a22c44bce | ||
|
|
8b13c70470 | ||
|
|
0541e3bc9b | ||
|
|
57fb799328 | ||
|
|
b909cfe802 | ||
|
|
8f536c090c | ||
|
|
fc1b204d50 | ||
|
|
6f891de57d | ||
|
|
5d94c6bf7b | ||
|
|
be3d6c169b | ||
|
|
46629ecfd2 | ||
|
|
415a65a5c6 | ||
|
|
b1c1b8bc76 | ||
|
|
23c25c0131 | ||
|
|
8b01980b9b | ||
|
|
196001e956 | ||
|
|
90ccbcd9ee | ||
|
|
3cf1e89556 | ||
|
|
5332e7ba6b | ||
|
|
c22a8f1d4c | ||
|
|
2482a9957a | ||
|
|
51bf5a63dc | ||
|
|
6c5fe0f40c | ||
|
|
45d190548a | ||
|
|
ef25dbf95f | ||
|
|
066d5efd23 | ||
|
|
27e6994abb | ||
|
|
ccdc7b6b14 | ||
|
|
be8530cf05 | ||
|
|
9af729f56c | ||
|
|
5831d23fe3 | ||
|
|
fe3757fe5b | ||
|
|
1ab04fe419 | ||
|
|
80917ab8b9 | ||
|
|
8daade0267 | ||
|
|
0eec29652b | ||
|
|
0d627a22f3 | ||
|
|
8c4d38219b | ||
|
|
9a72cb43a6 | ||
|
|
fff0df6665 | ||
|
|
8feebbb5cb | ||
|
|
b8875ff12b | ||
|
|
a4b86d85e2 | ||
|
|
f28837785b | ||
|
|
05acfb12eb | ||
|
|
a4e4967683 | ||
|
|
6d54013976 | ||
|
|
aa6bfcfb32 | ||
|
|
258f98560b | ||
|
|
c8facf37f5 | ||
|
|
44fb0d57ea | ||
|
|
45c4a7d0e1 | ||
|
|
ed34b06339 | ||
|
|
1a0f31e34f | ||
|
|
f81470a762 | ||
|
|
197d43cb76 | ||
|
|
10bb5bc0fa | ||
|
|
d603a779db | ||
|
|
2ea8ff0baf | ||
|
|
cb3274c7ee | ||
|
|
1f855f85ee | ||
|
|
ba2186ee49 | ||
|
|
20521979e7 | ||
|
|
50945b3d16 | ||
|
|
ef0b306732 | ||
|
|
28cfa1501b | ||
|
|
5a7d8948d8 | ||
|
|
06d9b9bf92 | ||
|
|
f601daf748 | ||
|
|
50c153e6c2 | ||
|
|
8e95ee4f64 | ||
|
|
0dff08dfa6 | ||
|
|
b1e45723df | ||
|
|
b71ebced7d | ||
|
|
eb374437e3 | ||
|
|
0cd7ea7a3d | ||
|
|
9e45314ed2 | ||
|
|
e98f30b4e5 | ||
|
|
694537f51e | ||
|
|
e1610ff1a9 | ||
|
|
59a697ca93 | ||
|
|
b63e488844 | ||
|
|
0257cb83c9 | ||
|
|
f3e29baa9c | ||
|
|
add68c3202 | ||
|
|
56cab05d26 | ||
|
|
3f121c831f | ||
|
|
2add432dbf | ||
|
|
40dab74fa5 | ||
|
|
20acdacfcc | ||
|
|
c51a0c1f53 | ||
|
|
a000334266 | ||
|
|
03d0b402fb | ||
|
|
64df9aef35 | ||
|
|
f253f8b67e | ||
|
|
588dcaa876 | ||
|
|
81483e41fa | ||
|
|
4131aa709f | ||
|
|
b9e6445baa | ||
|
|
2564e30950 | ||
|
|
e538b849ce | ||
|
|
9c929e1547 | ||
|
|
17d92c56d6 | ||
|
|
1e645e0f06 | ||
|
|
f5e35644da | ||
|
|
f37e0eb5ad | ||
|
|
a04af85852 | ||
|
|
c64d1d2dbe | ||
|
|
bcaf02af5f | ||
|
|
1b90d2e74e | ||
|
|
3c944a02e0 | ||
|
|
760fff6af8 | ||
|
|
45d045cc44 | ||
|
|
a07e43bc31 | ||
|
|
add1d741c5 | ||
|
|
a1c0f12961 | ||
|
|
f344f0f40c | ||
|
|
6ce3b69be0 | ||
|
|
ad77448248 | ||
|
|
3367bedec8 | ||
|
|
06f33e4a76 | ||
|
|
fc5d381c7f | ||
|
|
5dc5cd249b | ||
|
|
8c0fdb9f9d | ||
|
|
4fd2478d67 | ||
|
|
5f088cc066 | ||
|
|
c8391e5347 | ||
|
|
5e16fa3d7b | ||
|
|
b0b5fbee12 | ||
|
|
6ddb163284 | ||
|
|
577b464aa4 | ||
|
|
9a918b3749 | ||
|
|
22ef9ba14b | ||
|
|
a3840e32b3 | ||
|
|
5d928d4cc7 | ||
|
|
73ac915421 | ||
|
|
121a5170f2 | ||
|
|
611bc7d394 | ||
|
|
58471e809b | ||
|
|
ee9be5a266 | ||
|
|
7936bcd17d | ||
|
|
8559d4d46d | ||
|
|
3bb724fa69 | ||
|
|
51bd604cc5 | ||
|
|
2d69353416 | ||
|
|
2a4fe44a7d | ||
|
|
b44f6ea83b | ||
|
|
862ea200b9 | ||
|
|
16d961e862 | ||
|
|
f02715d2ee | ||
|
|
39c129168e | ||
|
|
a2f6cbb572 | ||
|
|
b3051ebcf9 | ||
|
|
1d232cf262 | ||
|
|
e9d5849afe | ||
|
|
48a71b04a1 | ||
|
|
47f0bf533c | ||
|
|
c11805a70a | ||
|
|
9abf526271 | ||
|
|
1445fdf244 | ||
|
|
1ed0f28218 | ||
|
|
181e1a6442 | ||
|
|
c8f33a91e9 | ||
|
|
f97330d99e | ||
|
|
ed0ed07a18 | ||
|
|
a9219613af | ||
|
|
30be975d6a | ||
|
|
ff56eed36b | ||
|
|
1f9a5fbc68 | ||
|
|
d24565479d | ||
|
|
62ca883ca2 | ||
|
|
78835581ac | ||
|
|
970de79d2b | ||
|
|
bf8a9a9724 | ||
|
|
4990c43930 | ||
|
|
59ffdcb2dd | ||
|
|
573dc8335e |
137 changed files with 14383 additions and 10208 deletions
29
.github/workflows/ci.yml
vendored
29
.github/workflows/ci.yml
vendored
|
|
@ -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
|
||||
|
|
@ -65,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
|
||||
|
|
@ -92,7 +95,7 @@ jobs:
|
|||
|
||||
- name: Download uncached images
|
||||
if: steps.cache-images.outputs.cache-hit != 'true'
|
||||
run: wget -nv -P images/ https://i.copy.sh/{linux.iso,linux3.iso,linux4.iso,buildroot-bzimage68.bin,TinyCore-11.0.iso,oberon.img,msdos.img,openbsd-floppy.img,kolibri.img,windows101.img,os8.img,freedos722.img,mobius-fd-release5.img}
|
||||
run: mkdir -p images && curl --compressed --output-dir images --remote-name-all https://i.copy.sh/{linux.iso,linux3.iso,linux4.iso,buildroot-bzimage68.bin,TinyCore-11.0.iso,oberon.img,msdos.img,openbsd-floppy.img,kolibri.img,windows101.img,os8.img,freedos722.img,mobius-fd-release5.img,msdos622.img}
|
||||
|
||||
- name: Run api-tests
|
||||
run: make api-tests
|
||||
|
|
@ -115,6 +118,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:
|
||||
|
|
@ -134,6 +140,10 @@ jobs:
|
|||
needs: test
|
||||
if: github.ref == 'refs/heads/master'
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Delete old release and tag
|
||||
uses: dev-drprasad/delete-tag-and-release@v1.0.1
|
||||
|
|
@ -146,20 +156,14 @@ jobs:
|
|||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: v86
|
||||
path: build
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R
|
||||
|
||||
- name: Set package.json version
|
||||
uses: actions/github-script@v7
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
result-encoding: string
|
||||
script: |
|
||||
const fs = require("fs");
|
||||
const pkg = {...require("./build/package.json"), version: '0.' + String(process.env.GITHUB_RUN_NUMBER) + '.0'}
|
||||
console.log(pkg);
|
||||
fs.writeFileSync("package.json", JSON.stringify(pkg, null, " "), "utf8");
|
||||
node-version: '20.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Release to GitHub
|
||||
uses: ncipollo/release-action@v1
|
||||
|
|
@ -171,5 +175,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 }}
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -25,3 +25,4 @@ src/rust/gen/analyzer0f.rs
|
|||
src/rust/gen/jit.rs
|
||||
src/rust/gen/jit0f.rs
|
||||
bios/seabios
|
||||
bench-results
|
||||
|
|
|
|||
22
LICENSE.MIT
Normal file
22
LICENSE.MIT
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
QEMU Floppy disk emulator (Intel 82078)
|
||||
|
||||
Copyright (c) 2003, 2007 Jocelyn Mayer
|
||||
Copyright (c) 2008 Hervé Poussineau
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
59
Makefile
59
Makefile
|
|
@ -78,13 +78,13 @@ 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 \
|
||||
memory.js dma.js pit.js vga.js ps2.js rtc.js uart.js \
|
||||
acpi.js apic.js ioapic.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 iso9660.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 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 \
|
||||
|
|
@ -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)\
|
||||
|
|
@ -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;'\
|
||||
|
|
@ -257,6 +259,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
|
||||
|
|
@ -273,8 +276,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='<code>Version: <a href="https://github.com/copy/v86/commits/[a-f0-9]\+">[a-f0-9]\+</a> ([^(]\+)</code>' ;\
|
||||
REPLACE='<code>Version: <a href="https://github.com/copy/v86/commits/'$$COMMIT'">'$$COMMIT'</a> ('$$DATE')</code>' ;\
|
||||
SEARCH='<code>Version: <a id="version" href="https://github.com/copy/v86/commits/[a-f0-9]\+">[a-f0-9]\+</a> ([^(]\+)</code>' ;\
|
||||
REPLACE='<code>Version: <a id="version" href="https://github.com/copy/v86/commits/'$$COMMIT'">'$$COMMIT'</a> ('$$DATE')</code>' ;\
|
||||
sed -i "s@$$SEARCH@$$REPLACE@g" index.html ;\
|
||||
SEARCH='<script src="build/v86_all.js?[a-f0-9]\+"></script>' ;\
|
||||
REPLACE='<script src="build/v86_all.js?'$$COMMIT'"></script>' ;\
|
||||
sed -i "s@$$SEARCH@$$REPLACE@g" index.html ;\
|
||||
grep $$COMMIT index.html
|
||||
|
||||
|
|
@ -293,51 +299,51 @@ 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.js 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.js 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.js 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.js 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
|
||||
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/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/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/v86-debug.wasm
|
||||
./tests/devices/virtio_9p.js
|
||||
./tests/devices/virtio_console.js
|
||||
./tests/devices/fetch_network.js
|
||||
|
|
@ -352,11 +358,12 @@ 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/v86-debug.wasm
|
||||
./tests/api/clean-shutdown.js
|
||||
./tests/api/state.js
|
||||
./tests/api/reset.js
|
||||
#./tests/api/floppy-insert-eject.js # disabled for now, sometimes hangs
|
||||
./tests/api/floppy.js
|
||||
./tests/api/cdrom-insert-eject.js
|
||||
./tests/api/serial.js
|
||||
./tests/api/reboot.js
|
||||
./tests/api/pic.js
|
||||
|
|
@ -375,13 +382,19 @@ 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:
|
||||
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:
|
||||
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
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ list of emulated hardware:
|
|||
- A generic VGA card with SVGA support and Bochs VBE Extensions.
|
||||
- A PCI bus. This one is partly incomplete and not used by every device.
|
||||
- An IDE disk controller.
|
||||
- A built-in ISO 9660 CD-ROM generator.
|
||||
- An NE2000 (RTL8390) PCI network card.
|
||||
- Various virtio devices: Filesystem, network and balloon.
|
||||
- A SoundBlaster 16 sound card.
|
||||
|
|
@ -148,7 +149,7 @@ for a full setup on Debian or
|
|||
The disk images for testing are not included in this repository. You can
|
||||
download them directly from the website using:
|
||||
|
||||
`wget -P images/ https://i.copy.sh/{linux3.iso,linux.iso,linux4.iso,buildroot-bzimage68.bin,openbsd-floppy.img,kolibri.img,windows101.img,os8.img,freedos722.img}`
|
||||
`curl --compressed --output-dir images/ --remote-name-all https://i.copy.sh/{linux.iso,linux3.iso,linux4.iso,buildroot-bzimage68.bin,TinyCore-11.0.iso,oberon.img,msdos.img,openbsd-floppy.img,kolibri.img,windows101.img,os8.img,freedos722.img,mobius-fd-release5.img,msdos622.img}`
|
||||
|
||||
Run integration tests: `make tests`
|
||||
|
||||
|
|
@ -196,6 +197,7 @@ repository under their own licenses:
|
|||
- [`lib/zstd/zstddeclib.c`](lib/zstd/zstddeclib.c)
|
||||
- [`tests/kvm-unit-tests/`](tests/kvm-unit-tests)
|
||||
- [`tests/qemutests/`](tests/qemutests)
|
||||
- [`src/floppy.js/`](src/floppy.js) contains parts ported from qemu under the MIT license, see LICENSE.MIT.
|
||||
|
||||
## Credits
|
||||
|
||||
|
|
|
|||
102
debug.html
102
debug.html
|
|
@ -2,60 +2,11 @@
|
|||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
|
||||
<title>v86 (debug)</title>
|
||||
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
|
||||
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no,interactive-widget=resizes-content">
|
||||
|
||||
<script src="src/const.js"></script>
|
||||
<script src="src/config.js"></script>
|
||||
<script src="src/log.js"></script>
|
||||
<script src="src/lib.js"></script>
|
||||
<script src="src/buffer.js"></script>
|
||||
<script src="src/cpu.js"></script>
|
||||
<script src="src/debug.js"></script>
|
||||
<script src="src/io.js"></script>
|
||||
<script src="src/main.js"></script>
|
||||
<script src="src/ide.js"></script>
|
||||
<script src="src/pci.js"></script>
|
||||
<script src="src/floppy.js"></script>
|
||||
<script src="src/memory.js"></script>
|
||||
<script src="src/dma.js"></script>
|
||||
<script src="src/pit.js"></script>
|
||||
<script src="src/vga.js"></script>
|
||||
<script src="src/ps2.js"></script>
|
||||
<script src="src/rtc.js"></script>
|
||||
<script src="src/uart.js"></script>
|
||||
<script src="src/acpi.js"></script>
|
||||
<script src="src/apic.js"></script>
|
||||
<script src="src/ioapic.js"></script>
|
||||
<script src="src/sb16.js"></script>
|
||||
<script src="src/ne2k.js"></script>
|
||||
<script src="src/state.js"></script>
|
||||
<script src="src/virtio.js"></script>
|
||||
<script src="src/virtio_balloon.js"></script>
|
||||
<script src="src/virtio_console.js"></script>
|
||||
<script src="src/virtio_net.js"></script>
|
||||
<script src="src/bus.js"></script>
|
||||
<script src="src/elf.js"></script>
|
||||
<script src="src/kernel.js"></script>
|
||||
<script src="src/browser/main.js"></script>
|
||||
<script src="src/browser/screen.js"></script>
|
||||
<script src="src/browser/keyboard.js"></script>
|
||||
<script src="src/browser/mouse.js"></script>
|
||||
<script src="src/browser/speaker.js"></script>
|
||||
<script src="src/browser/serial.js"></script>
|
||||
<script src="src/browser/network.js"></script>
|
||||
<script src="src/browser/fake_network.js"></script>
|
||||
<script src="src/browser/fetch_network.js"></script>
|
||||
<script src="src/browser/wisp_network.js"></script>
|
||||
<script src="src/browser/starter.js"></script>
|
||||
<script src="src/browser/worker_bus.js"></script>
|
||||
<script src="src/browser/print_stats.js"></script>
|
||||
<script src="src/browser/filestorage.js"></script>
|
||||
<script src="lib/jor1k.js"></script>
|
||||
<script src="lib/9p.js"></script>
|
||||
<script src="lib/filesystem.js"></script>
|
||||
<script src="lib/marshall.js"></script>
|
||||
<script src="build/capstone-x86.min.js"></script>
|
||||
<script src="build/libwabt.js"></script>
|
||||
<script src="build/libwabt.cjs"></script>
|
||||
<script type="module" src="src/browser/main.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="v86.css">
|
||||
|
||||
|
|
@ -136,17 +87,22 @@
|
|||
|
||||
<tr>
|
||||
<td><label for="floppy_image">Floppy disk image</label></td>
|
||||
<td> <input type="file" id="floppy_image"><br></td>
|
||||
<td><input type="file" id="fda_image"> or <a id="fda_toggle_empty_disk">create empty floppy disk</a><br></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><label for="floppy_image">Second floppy disk image</label></td>
|
||||
<td><input type="file" id="fdb_image"> or <a id="fdb_toggle_empty_disk">create empty floppy disk</a><br></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><label for="hda_image">Hard disk image</label></td>
|
||||
<td><input type="file" id="hda_image"><br></td>
|
||||
<td><input type="file" id="hda_image"> or <a id="hda_toggle_empty_disk">create empty hard disk</a><br></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Second hard disk image</td>
|
||||
<td><input type="file" id="hdb_image"><br></td>
|
||||
<td><input type="file" id="hdb_image"> or <a id="hdb_toggle_empty_disk">create empty hard disk</a><br></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
|
@ -168,6 +124,11 @@
|
|||
<td><input type="file" id="bios"><br></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><label for="vga_bios">VGA BIOS</label></td>
|
||||
<td><input type="file" id="vga_bios"><br></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2"><small>Disk images are not uploaded to the server</small><hr></td>
|
||||
</tr>
|
||||
|
|
@ -186,6 +147,10 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2"><hr></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><label for="relay_url">Networking proxy (leave blank to disable)</label></td>
|
||||
<td>
|
||||
|
|
@ -193,6 +158,24 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><label for="net_device_type">Network Device Type</label></td>
|
||||
<td>
|
||||
<select id="net_device_type">
|
||||
<option value="ne2k">ne2k</option>
|
||||
<option value="virtio">virtio</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><label for="mtu">Network MTU (only used for virtio)</label></td>
|
||||
<td>
|
||||
<input id="mtu" type="number" value="1500" min="68" max="65535" step="1"> B<br>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td colspan="2"><hr></td>
|
||||
</tr>
|
||||
|
|
@ -256,6 +239,8 @@
|
|||
<input type="button" value="Get second hard disk image" id="get_hdb_image">
|
||||
<input type="button" value="Get CD-ROM image" id="get_cdrom_image">
|
||||
<input type="button" value="Insert floppy image" id="change_fda_image">
|
||||
<input type="button" value="Insert second floppy image" id="change_fdb_image">
|
||||
<input type="button" value="Insert CD image" id="change_cdrom_image">
|
||||
<input type="button" value="Save State" id="save_state">
|
||||
<input type="button" value="Load State" id="load_state"> <input type="file" style="display: none" id="load_state_input">
|
||||
<input type="button" value="Memory Dump" id="memory_dump">
|
||||
|
|
@ -265,13 +250,13 @@
|
|||
<input type="button" value="Go fullscreen" id="fullscreen">
|
||||
<input type="button" value="Take screenshot" id="take_screenshot">
|
||||
<input type="button" value="Mute" id="mute">
|
||||
<input type="button" value="Enable theatre mode" id="toggle_theatre">
|
||||
<input type="button" style="display: none" value="Enable zoom to fit" id="toggle_zoom_to_fit">
|
||||
|
||||
<label>
|
||||
Scale:
|
||||
<input type="number" min="0.25" step="0.25" value="1.0" id="scale" style="width: 50px">
|
||||
</label>
|
||||
|
||||
<br>
|
||||
</div>
|
||||
|
||||
<pre style="margin: 0" id="log_levels"></pre>
|
||||
|
|
@ -345,3 +330,6 @@
|
|||
</textarea>
|
||||
|
||||
<div id="terminal"></div>
|
||||
|
||||
<input type="button" value="Hide UI" id="toggle_ui" style="display: none">
|
||||
<div id="theatre_background" style="display: none"></div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,34 @@
|
|||
A 9p filesystem is supported by v86, using a virtio transport. Using
|
||||
it, files can be exchanged with the guest OS, see `create_file` and `read_file`
|
||||
in [`starter.js`](https://github.com/copy/v86/blob/master/src/browser/starter.js).
|
||||
It can be enabled by passing the following options to `V86`:
|
||||
A 9p filesystem is supported by v86, using a virtio transport. There are several
|
||||
ways it can be set up.
|
||||
|
||||
### Guest mount
|
||||
|
||||
In all cases, the filesystem is mounted in the guest system using the `9p`
|
||||
filesystem type and the `host9p` device tag. Typically you want to be specific
|
||||
with the version and transport options:
|
||||
|
||||
```sh
|
||||
mount -t 9p -o trans=virtio,version=9p2000.L host9p /mnt/9p/
|
||||
```
|
||||
|
||||
Here are kernel arguments you can use to boot directly off the 9p filesystem:
|
||||
|
||||
```
|
||||
rw root=host9p rootfstype=9p rootflags=trans=virtio,version=9p2000.L
|
||||
```
|
||||
|
||||
The `aname` option can be used to pick the directory from 9p to mount. The `rw`
|
||||
argument makes this a read-write root filesystem.
|
||||
|
||||
|
||||
### JSON/HTTP Filesystem
|
||||
|
||||
This is the standard way to setup the 9p filesystem. It loads files over
|
||||
HTTP on-demand into an in-memory filesystem in JS. This allows files to be
|
||||
exchanged with the guest OS. See `create_file` and `read_file` in
|
||||
[`starter.js`](https://github.com/copy/v86/blob/master/src/browser/starter.js).
|
||||
|
||||
This mode is enabled by passing the following options to `V86`:
|
||||
|
||||
```javascript
|
||||
filesystem: {
|
||||
|
|
@ -11,15 +38,60 @@ filesystem: {
|
|||
```
|
||||
|
||||
Here, `basefs` is a json file created using
|
||||
[fs2json](https://github.com/copy/fs2json). The base url is the prefix of a url
|
||||
from which the files are available. For instance, if the 9p filesystem has a
|
||||
file `/bin/sh`, that file must be accessible from
|
||||
`http://localhost/9p/base/bin/sh`. If `basefs` and `baseurl` are omitted, an
|
||||
empty 9p filesystem is created.
|
||||
[fs2json.py](tools/fs2json.py) and the `baseurl` directory is created using
|
||||
[copy-to-sha256.py](tools/copy-to-sha256.py).
|
||||
|
||||
The `mount_tag` of the 9p device is `host9p`. In order to mount it in the
|
||||
guest, use:
|
||||
If `basefs` and `baseurl` are omitted, an empty 9p filesystem is created. Unless
|
||||
you configure one of the alternative modes.
|
||||
|
||||
```sh
|
||||
mount -t 9p host9p /mnt/9p/
|
||||
|
||||
### Function Handler
|
||||
|
||||
You can handle 9p messages directly in JavaScript yourself by providing a
|
||||
function as `handle9p` under `filesystem`:
|
||||
|
||||
```javascript
|
||||
filesystem: {
|
||||
handle9p: async (reqBuf, reply) => {
|
||||
// reqBuf is a Uint8Array of the entire request frame.
|
||||
// you can parse these bytes using a library or reading the 9p spec.
|
||||
// once you formulate a response, you send the reply frame as a
|
||||
// Uint8Array by passing it to reply: reply(respBuf)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This allows you to implement a 9p server or custom proxy in JS. However, this
|
||||
filesystem will not be cached (unless cached in the guest OS), functions like
|
||||
`create_file` and `read_file` will not be available, and you will be responsible
|
||||
for keeping its state in sync with any machine save states.
|
||||
|
||||
|
||||
### WebSocket Proxy
|
||||
|
||||
You can also back the 9p virtio filesystem with a 9p server over WebSocket by
|
||||
providing a WS proxy URL:
|
||||
|
||||
```javascript
|
||||
filesystem: {
|
||||
proxy_url: "ws://localhost:8080/"
|
||||
}
|
||||
```
|
||||
|
||||
Simlar to using `handle9p`, this filesystem will not be available in JS and
|
||||
will need to be re-mounted after restoring state.
|
||||
|
||||
The WS proxy just needs to hand off messages with a connection to a normal 9p
|
||||
server. Each binary WebSocket message is the full buffer of a request or a
|
||||
reply.
|
||||
|
||||
To implement, request message bytes can just be sent directly to the 9p
|
||||
connection, but the 9p reply stream needs to be buffered into a single binary
|
||||
WebSocket message. The proxy must at least parse the first 4 bytes to get the
|
||||
message size and use it to buffer a full message before sending over WebSocket.
|
||||
|
||||
The [wanix](https://github.com/tractordev/wanix) CLI has a `serve` command that
|
||||
not only serves a directory over HTTP, but also over 9P via WebSocket. You can
|
||||
see how it [implements a proxy][1] in Go.
|
||||
|
||||
[1]: https://github.com/tractordev/wanix/blob/main/cmd/wanix/serve.go#L117-L177
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -128,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.
|
||||
|
||||
|
|
@ -162,6 +164,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://<port>.external` (e.g. `1234.external` -> `localhost:1234`).
|
||||
|
||||
**CORS proxy server**
|
||||
|
||||
* **[cors-anywhere](https://github.com/Rob--W/cors-anywhere)** -- NodeJS
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ 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 <size in megabytes>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.
|
||||
|
|
@ -23,16 +23,46 @@ qemu-system-i386 -m 128 -M pc,acpi=off -hda hdd.img
|
|||
|
||||
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 <cd-rom letter>:\WIN95\WIN95_02.CAB vfd.vxd
|
||||
```
|
||||
|
||||
For the floppy version:
|
||||
```bat
|
||||
extract /a /l C:\Windows\System <floppy drive letter>:\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.
|
||||
|
||||
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".
|
||||
|
|
@ -41,10 +71,12 @@ 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.
|
||||
> 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.
|
||||
|
||||
|
|
@ -56,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).
|
||||
|
||||
|
|
@ -64,43 +109,43 @@ 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`.
|
||||
|
||||
## 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:
|
||||
|
||||
```
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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 <CD-ROM letter>:\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
|
||||
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.
|
||||
|
|
@ -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,10 +64,10 @@ 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
|
||||
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".
|
||||
|
|
@ -90,10 +90,10 @@ 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
|
||||
qemu-system-i386 -m 512 -drive file=hdd.img,format=raw -cdrom InstallCD.iso
|
||||
```
|
||||
|
||||
Optional:
|
||||
|
|
@ -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,10 +145,10 @@ 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
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,54 @@
|
|||
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",
|
||||
"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 +118,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",
|
||||
|
|
@ -84,7 +133,8 @@ export default [
|
|||
"no-with": "error",
|
||||
"require-yield": "error",
|
||||
"use-isnan": "error",
|
||||
"valid-typeof": "error"
|
||||
"valid-typeof": "error",
|
||||
"strict": "error"
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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/",
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
var fs = require("fs");
|
||||
var V86 = require("../build/libv86.js").V86;
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import url from "node:url";
|
||||
import { V86 } from "../build/libv86.mjs";
|
||||
|
||||
function readfile(path)
|
||||
{
|
||||
return new Uint8Array(fs.readFileSync(path)).buffer;
|
||||
}
|
||||
|
||||
var bios = readfile(__dirname + "/../bios/seabios.bin");
|
||||
var linux = readfile(__dirname + "/../images/linux4.iso");
|
||||
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
||||
|
||||
process.stdin.setRawMode(true);
|
||||
process.stdin.resume();
|
||||
|
|
@ -19,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)
|
||||
|
|
|
|||
|
|
@ -1,19 +1,13 @@
|
|||
#!/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.mjs";
|
||||
|
||||
function readfile(path)
|
||||
{
|
||||
return new Uint8Array(fs.readFileSync(path)).buffer;
|
||||
}
|
||||
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
||||
|
||||
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");
|
||||
|
|
@ -21,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,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
importScripts("../build/libv86.js");
|
||||
|
||||
/* global V86 */
|
||||
|
||||
var emulator = new V86({
|
||||
wasm_path: "../build/v86.wasm",
|
||||
memory_size: 32 * 1024 * 1024,
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
#!/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";
|
||||
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");
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
#!/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";
|
||||
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");
|
||||
|
|
@ -210,8 +213,9 @@ function gen_instruction_body_after_prefix(encodings, size)
|
|||
}),
|
||||
|
||||
default_case: {
|
||||
varname: "x",
|
||||
body: [
|
||||
`if DEBUG { panic!("Bad instruction at {:x}", *instruction_pointer); }`,
|
||||
`dbg_log!("#ud ${encoding.opcode.toString(16).toUpperCase()}/{} at {:x}", x, *instruction_pointer);`,
|
||||
"trigger_ud();",
|
||||
],
|
||||
}
|
||||
|
|
@ -410,7 +414,7 @@ function gen_table()
|
|||
|
||||
"use crate::cpu::cpu::{after_block_boundary, modrm_resolve};",
|
||||
"use crate::cpu::cpu::{read_imm8, read_imm8s, read_imm16, read_imm32s, read_moffs};",
|
||||
"use crate::cpu::cpu::{task_switch_test, trigger_ud, DEBUG};",
|
||||
"use crate::cpu::cpu::{task_switch_test, trigger_ud};",
|
||||
"use crate::cpu::instructions;",
|
||||
"use crate::cpu::global_pointers::{instruction_pointer, prefixes};",
|
||||
"use crate::prefix;",
|
||||
|
|
@ -475,7 +479,6 @@ function gen_table()
|
|||
"use crate::cpu::cpu::{after_block_boundary, modrm_resolve};",
|
||||
"use crate::cpu::cpu::{read_imm8, read_imm16, read_imm32s};",
|
||||
"use crate::cpu::cpu::{task_switch_test, task_switch_test_mmx, trigger_ud};",
|
||||
"use crate::cpu::cpu::DEBUG;",
|
||||
"use crate::cpu::instructions_0f;",
|
||||
"use crate::cpu::global_pointers::{instruction_pointer, prefixes};",
|
||||
"use crate::prefix;",
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
#!/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";
|
||||
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");
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
"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 = [];
|
||||
|
||||
|
|
@ -34,7 +32,8 @@ function print_syntax_tree(statements)
|
|||
|
||||
if(statement.default_case)
|
||||
{
|
||||
cases.push(`_ => {`);
|
||||
const varname = statement.default_case.varname || "_";
|
||||
cases.push(`${varname} => {`);
|
||||
cases.push.apply(cases, indent(print_syntax_tree(statement.default_case.body), 4));
|
||||
cases.push(`}`);
|
||||
}
|
||||
|
|
@ -77,7 +76,3 @@ function print_syntax_tree(statements)
|
|||
|
||||
return code;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
print_syntax_tree,
|
||||
};
|
||||
|
|
|
|||
31
gen/util.js
31
gen/util.js
|
|
@ -1,14 +1,10 @@
|
|||
"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 +12,7 @@ function hex(n, pad)
|
|||
return s;
|
||||
}
|
||||
|
||||
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 +24,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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
"use strict";
|
||||
|
||||
const { hex } = require("./util");
|
||||
|
||||
// http://ref.x86asm.net/coder32.html
|
||||
|
||||
const zf = 1 << 6;
|
||||
|
|
@ -875,4 +871,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;
|
||||
|
|
|
|||
227
index.html
227
index.html
|
|
@ -2,10 +2,13 @@
|
|||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
|
||||
<title>v86</title>
|
||||
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
|
||||
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no,interactive-widget=resizes-content">
|
||||
<meta name="description" content="Run KolibriOS, Linux or Windows 98 in your browser">
|
||||
|
||||
<script src="build/v86_all.js"></script>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="apple-touch-icon" href="192.png">
|
||||
|
||||
<script src="build/v86_all.js?98e7110c2"></script>
|
||||
<link rel="stylesheet" href="v86.css">
|
||||
|
||||
<div>
|
||||
|
|
@ -54,66 +57,121 @@
|
|||
<label><input type=checkbox> contains: demos</label>
|
||||
<label><input type=checkbox> contains: compilers</label>
|
||||
<label><input type=checkbox> contains: web browsers</label>-->
|
||||
<div style="text-align: right; float: right"><a href="#setup">Skip to custom settings</a></div>
|
||||
<div style="text-align: right; float: right">
|
||||
<a id="reset_filters">Reset filters</a> / <a href="#setup">Skip to custom settings</a>
|
||||
</div>
|
||||
<br style="clear: both">
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<table id="oses">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th>UI</th>
|
||||
<th>Family</th>
|
||||
<th>Arch</th>
|
||||
<th>Status</th>
|
||||
<th>Source</th>
|
||||
<th>Lang</th>
|
||||
<th>Medium</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr id="start_archlinux"><td><a href="?profile=archlinux">Arch Linux</a> <td>15+ MB</td>
|
||||
<td><span class=tui_icon></span></td> <td>Linux</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>C</td> <td>9pfs</td> <td>Xorg, Firefox, various compilers and more</td></tr>
|
||||
<tr id="start_dsl"><td><a href="?profile=dsl">Damn Small Linux</a> <td>50 MB</td>
|
||||
<td><span class=gui_icon></span></td> <td>Linux</td> <td>32-bit</td> <td>Historic</td> <td>Open-source</td> <td>C</td> <td>CD</td> <td>4.11.rc2 with Firefox 2.0</td></tr>
|
||||
<tr id="start_buildroot"><td><a href="?profile=buildroot">Buildroot Linux</a> <td>4.9 MB</td>
|
||||
<td><span class=tui_icon></span></td> <td>Linux</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>C</td> <td>bzImage</td> <td>Lua, ping, curl, telnet</td></tr>
|
||||
|
||||
<tr id="start_freebsd"><td><a href="?profile=freebsd">FreeBSD</a> <td>16+ MB</td>
|
||||
<td><span class=tui_icon></span></td> <td>BSD</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>C</td> <td>HD</td> <td>FreeBSD 12.0</td></tr>
|
||||
<tr id="start_openbsd"><td><a href="?profile=openbsd">OpenBSD</a> <td>11+ MB</td>
|
||||
<td><span class=tui_icon></span></td> <td>BSD</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>C</td> <td>HD</td> <td>OpenBSD 6.6</td></tr>
|
||||
|
||||
<tr id="start_fiwix"><td><a href="?profile=fiwix">FiwixOS</a> <td>15+ MB</td>
|
||||
<td><span class=tui_icon></span></td> <td>Unix-like</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>C</td> <td>HD</td> <td>With Doom</td></tr>
|
||||
<tr id="start_serenity"><td><a href="?profile=serenity">SerenityOS</a> <td>16+ MB</td>
|
||||
<td><span class=gui_icon></span></td> <td>Unix-like</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>C++</td> <td>HD</td> <td>Web browser, various games and demos</td></tr>
|
||||
|
||||
<tr id="start_haiku"><td><a href="?profile=haiku">Haiku</a> <td>41+ MB</td>
|
||||
<td><span class=gui_icon></span></td> <td>BeOS</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>C++</td> <td>HD</td> <td>Networking (WebPositive), OCaml, 2048, NetHack</td></tr>
|
||||
<tr id="start_tinyaros"><td><a href="?profile=tinyaros">Tiny Aros</a> <td>17+ MB</td>
|
||||
<td><span class=gui_icon></span></td> <td>AmigaOS</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>C</td> <td>CD</td> <td>AmigaOS-like graphical OS</td></tr>
|
||||
|
||||
<tr id="start_reactos"><td><a href="?profile=reactos">ReactOS</a> <td>17+ MB</td>
|
||||
<td><span class=gui_icon></span></td> <td>Windows-like</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>C++</td> <td>HD</td> <td>QtWeb, LBreakout2, OpenTTD, Bochs, TCC</td></tr>
|
||||
<tr id="start_windows1"><td><a href="?profile=windows1">Windows 1.01</a> <td>0.7 MB</td>
|
||||
<td><span class=gui_icon></span></td> <td>Windows</td> <td>16-bit</td> <td>Historic</td> <td>Proprietary</td> <td>ASM, C</td> <td>Floppy</td> <td>Reversi, Paint</td></tr>
|
||||
<tr id="start_windows95"><td><a href="?profile=windows95">Windows 95</a> <td>19+ MB</td>
|
||||
<td><span class=gui_icon></span></td> <td>Windows</td> <td>32-bit</td> <td>Historic</td> <td>Proprietary</td> <td>ASM, C</td> <td>HD</td> <td>Age of Empires, FASM, POV-Ray, Hover!</td></tr>
|
||||
<tr id="start_windows2000"><td><a href="?profile=windows2000">Windows 2000</a> <td>23+ MB</td>
|
||||
<td><span class=gui_icon></span></td> <td>Windows</td> <td>32-bit</td> <td>Historic</td> <td>Proprietary</td> <td>C++</td> <td>HD</td> <td>IE 5, Pinball</td></tr>
|
||||
<tr id="start_msdos"><td><a href="?profile=msdos">MS-DOS 6.22</a> <td>2.4+ MB</td>
|
||||
<td><span class=tui_icon></span></td> <td>DOS</td> <td>16-bit</td> <td>Historic</td> <td>Proprietary</td> <td>ASM</td> <td>HD</td> <td>Doom, Sim City, OCaml 1.0, Turbo C and more</td></tr>
|
||||
<tr id="start_freedos"><td><a href="?profile=freedos">FreeDOS</a> <td>0.6 MB</td>
|
||||
<td><span class=tui_icon></span></td> <td>DOS</td> <td>16-bit</td> <td>Modern</td> <td>Open-source</td> <td>ASM, C</td> <td>Floppy</td> <td>nasm, vim, debug.com, Rogue, various demos</td></tr>
|
||||
|
||||
<tr id="start_kolibrios"><td><a href="?profile=kolibrios">KolibriOS</a> <td>1.3 MB</td>
|
||||
<td><span class=gui_icon></span></td> <td>Custom</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>ASM</td> <td>Floppy</td> <td>Various apps, games and demos</td></tr>
|
||||
<tr id="start_qnx"><td><a href="?profile=qnx">QNX 4.05</a> <td>1.4 MB</td>
|
||||
<td><span class=gui_icon></span></td> <td>Custom</td> <td>32-bit</td> <td>Historic</td> <td>Proprietary</td> <td>C</td> <td>Floppy</td> <td>1999 demo disk</td></tr>
|
||||
</table>
|
||||
<div class="table" id="oses">
|
||||
<div class="th">
|
||||
<div class="tr">
|
||||
<span>Name</span>
|
||||
<span>Size</span>
|
||||
<span>UI</span>
|
||||
<span>Family</span>
|
||||
<span>Arch</span>
|
||||
<span>Status</span>
|
||||
<span>Source</span>
|
||||
<span>Lang</span>
|
||||
<span>Medium</span>
|
||||
<span>Notes</span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="tr" href="?profile=android" id="start_android"><span>Android</span> <span>54+ MB</span> <span><div class=gui_icon></div></span> <span>Linux</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C++</span> <span>CD</span> <span>Android x86 1.6-r2</span></a>
|
||||
<a class="tr" href="?profile=archlinux" id="start_archlinux"><span>Arch Linux</span> <span>15+ MB</span> <span><div class=tui_icon></div></span> <span>Linux</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>9pfs</span> <span>Various packages, including Xorg, Firefox and more</span></a>
|
||||
<a class="tr" href="?profile=buildroot" id="start_buildroot"><span>Buildroot Linux</span> <span>4.9 MB</span> <span><div class=tui_icon></div></span> <span>Linux</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>bzImage</span> <span>Minimal Linux with Lua, ping, curl, telnet</span></a>
|
||||
<a class="tr" href="?profile=dsl" id="start_dsl"><span>Damn Small Linux</span> <span>50 MB</span> <span><div class=gui_icon></div></span> <span>Linux</span> <span>32-bit</span> <span>Historic</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>4.11.rc2 with Firefox 2.0</span></a>
|
||||
<a class="tr" href="?profile=elks" id="start_elks"><span>ELKS</span> <span>1.2 MB</span> <span><div class=tui_icon></div></span> <span>Linux-like</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>Linux for 8086</span></a>
|
||||
<a class="tr" href="?profile=nodeos" id="start_nodeos"><span>NodeOS</span> <span>14 MB</span> <span><div class=tui_icon></div></span> <span>Linux</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>bzImage</span> <span>Linux with nodejs as /bin/init</span></a>
|
||||
<a class="tr" href="?profile=tilck" id="start_tilck"><span>Tilck</span> <span>16 MB</span> <span><div class=tui_icon></div></span> <span>Linux-like</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>Tiny Linux-Compatible Kernel</span></a>
|
||||
<a class="tr" href="?profile=freebsd" id="start_freebsd"><span>FreeBSD</span> <span>16+ MB</span> <span><div class=tui_icon></div></span> <span>BSD</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>FreeBSD 12.0</span></a>
|
||||
<a class="tr" href="?profile=netbsd" id="start_netbsd"><span>NetBSD</span> <span>23+ MB</span> <span><div class=tui_icon></div></span> <span>BSD</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>NetBSD 4.0 with Xorg</span></a>
|
||||
<a class="tr" href="?profile=openbsd" id="start_openbsd"><span>OpenBSD</span> <span>11+ MB</span> <span><div class=tui_icon></div></span> <span>BSD</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>OpenBSD 6.6</span></a>
|
||||
<a class="tr" href="?profile=fiwix" id="start_fiwix"><span>FiwixOS</span> <span>4.2+ MB</span> <span><div class=tui_icon></div></span> <span>Unix-like</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>With Doom</span></a>
|
||||
<a class="tr" href="?profile=minix" id="start_minix"><span>Minix</span> <span>30+ MB</span> <span><div class=tui_icon></div></span> <span>Unix-like</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>Minix 3.3</span></a>
|
||||
<a class="tr" href="?profile=redox" id="start_redox"><span>Redox</span> <span>31+ MB</span> <span><div class=gui_icon></div></span> <span>Unix-like</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>Rust</span> <span>HD</span> <span>A Unix-like microkernel OS written in Rust</span></a>
|
||||
<a class="tr" href="?profile=serenity" id="start_serenity"><span>SerenityOS</span> <span>16+ MB</span> <span><div class=gui_icon></div></span> <span>Unix-like</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C++</span> <span>HD</span> <span>Web browser, various games and demos</span></a>
|
||||
<a class="tr" href="?profile=sortix" id="start_sortix"><span>Sortix</span> <span>67 MB</span> <span><div class=tui_icon></div></span> <span>Unix-like</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C++</span> <span>CD</span> <span>A small self-hosting Unix-like operating system</span></a>
|
||||
<a class="tr" href="?profile=soso" id="start_soso"><span>Soso</span> <span>7.6 MB</span> <span><div class=gui_icon></div></span> <span>Unix-like</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span> A Simple Unix-like operating system</span></a>
|
||||
<a class="tr" href="?profile=syllable" id="start_syllable"><span>Syllable</span> <span>28+ MB</span> <span><div class=gui_icon></div></span> <span>Unix-like</span> <span>32-bit</span> <span>Historic</span> <span>Open-source</span> <span>C++</span> <span>HD</span> <span>A user friendly, POSIX compatible OS</span></a>
|
||||
<a class="tr" href="?profile=unix-v7" id="start_unix-v7"><span>Unix V7</span> <span>0.5+ MB</span> <span><div class=tui_icon></div></span> <span>Unix</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>C</span> <span>HD</span> <span>Unix V7 port for x86, including Amsterdam Compiler Kit</span></a>
|
||||
<a class="tr" href="?profile=beos" id="start_beos"><span>BeOS 5</span> <span>34+ MB</span> <span><div class=gui_icon></div></span> <span>BeOS</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>C++</span> <span>HD</span> <span>With Opera 3.62, NetPositive</span></a>
|
||||
<a class="tr" href="?profile=haiku" id="start_haiku"><span>Haiku</span> <span>41+ MB</span> <span><div class=gui_icon></div></span> <span>BeOS</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C++</span> <span>HD</span> <span>Networking (WebPositive), OCaml, 2048, NetHack</span></a>
|
||||
<a class="tr" href="?profile=aros-broadway" id="start_aros-broadway"><span>AROS Broadway</span> <span>25+ MB</span> <span><div class=gui_icon></div></span> <span>AmigaOS</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>AmigaOS-like graphical OS</span></a>
|
||||
<a class="tr" href="?profile=icaros" id="start_icaros"><span>Icaros Desktop</span> <span>60+ MB</span> <span><div class=gui_icon></div></span> <span>AmigaOS</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>AmigaOS-like graphical OS</span></a>
|
||||
<a class="tr" href="?profile=tinyaros" id="start_tinyaros"><span>Tiny Aros</span> <span>17+ MB</span> <span><div class=gui_icon></div></span> <span>AmigaOS</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>AmigaOS-like graphical OS</span></a>
|
||||
<a class="tr" href="?profile=9front" id="start_9front"><span>9front</span> <span>5.2+ MB</span> <span><div class=gui_icon></div></span> <span>Plan 9</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>An actively maintained fork of Plan 9</span></a>
|
||||
<a class="tr" href="?profile=9legacy" id="start_9legacy"><span>9legacy</span> <span>13 MB</span> <span><div class=gui_icon></div></span> <span>Plan 9</span> <span>32-bit</span> <span>Historic</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>A set of patches based on the latest release of Plan 9</span></a>
|
||||
<a class="tr" href="?profile=reactos" id="start_reactos"><span>ReactOS</span> <span>17+ MB</span> <span><div class=gui_icon></div></span> <span>Windows-like</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C++</span> <span>HD</span> <span>QtWeb, LBreakout2, OpenTTD, Bochs, TCC</span></a>
|
||||
<a class="tr" href="?profile=windows1" id="start_windows1"><span>Windows 1.01</span> <span>0.7 MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>16-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM, C</span> <span>Floppy</span> <span>The first release version of Windows</span></a>
|
||||
<a class="tr" href="?profile=windows2" id="start_windows2"><span>Windows 2.03</span> <span>1.8 MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>16-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM, C</span> <span>HD</span> <span>Reversi, Paint</span></a>
|
||||
<a class="tr" href="?profile=windows30" id="start_windows30"><span>Windows 3.0</span> <span>6.5 MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>16-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM, C</span> <span>CD</span> <span>CorelDRAW! 2.0, Reversi</span></a>
|
||||
<a class="tr" href="?profile=windows31" id="start_windows31"><span>Windows 3.1</span> <span>17 MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>16-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM, C</span> <span>HD</span> <span>QBasic, Minesweeper, Solitaire</span></a>
|
||||
<a class="tr" href="?profile=windows95" id="start_windows95"><span>Windows 95</span> <span>19+ MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM, C</span> <span>HD</span> <span>Age of Empires, FASM, POV-Ray, Hover!</span></a>
|
||||
<a class="tr" href="?profile=windows98" id="start_windows98"><span>Windows 98</span> <span>13+ MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM, C</span> <span>HD</span> <span>FreeCell, Hearts, sheep.exe, IE 5</span></a>
|
||||
<a class="tr" href="?profile=windows-me" id="start_windows-me"><span>Windows ME</span> <span>14+ MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM, C</span> <span>HD</span> <span>Visual Basic, Office 97</span></a>
|
||||
<a class="tr" href="?profile=windowsnt3" id="start_windowsnt3"><span>Windows NT 3.1</span> <span>18+ MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>C++</span> <span>HD</span> <span>The first retail version of Windows NT</span></a>
|
||||
<a class="tr" href="?profile=windowsnt35" id="start_windowsnt35"><span>Windows NT 3.51</span> <span>28+ MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>C++</span> <span>HD</span> <span>Internet Explorer 3 and Visual FoxPro 3.0</span></a>
|
||||
<a class="tr" href="?profile=windowsnt4" id="start_windowsnt4"><span>Windows NT 4.0</span> <span>16+ MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>C++</span> <span>HD</span> <span>Windows NT 4.0 Service Pack 1</span></a>
|
||||
<a class="tr" href="?profile=windows2000" id="start_windows2000"><span>Windows 2000</span> <span>28+ MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>C++</span> <span>HD</span> <span>IE 6, K-Meleon, Winamp, Delphi, NetHack and more</span></a>
|
||||
<a class="tr" href="?profile=86dos" id="start_86dos"><span>86-DOS</span> <span>0.1 MB</span> <span><div class=tui_icon></div></span> <span>DOS</span> <span>16-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM</span> <span>Floppy</span> <span>86-DOS version 1.0</span></a>
|
||||
<a class="tr" href="?profile=ibm-exploring" id="start_ibm-exploring"><span>Exploring IBM</span> <span>0.1 MB</span> <span><div class=tui_icon></div></span> <span>DOS</span> <span>16-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM</span> <span>Floppy</span> <span>Learning program on how to use a computer</span></a>
|
||||
<a class="tr" href="?profile=freedos" id="start_freedos"><span>FreeDOS</span> <span>0.6 MB</span> <span><div class=tui_icon></div></span> <span>DOS</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM, C</span> <span>Floppy</span> <span>nasm, vim, debug.com, Rogue, various demos</span></a>
|
||||
<a class="tr" href="?profile=freegem" id="start_freegem"><span>FreeGEM</span> <span>1.6+ MB</span> <span><div class=gui_icon></div></span> <span>DOS</span> <span>16-bit</span> <span>Historic</span> <span>Open-source</span> <span>ASM, C</span> <span>HD</span> <span>Graphical desktop for FreeDOS</span></a>
|
||||
<a class="tr" href="?profile=xcom" id="start_xcom"><span>Xcom</span> <span>1.3 MB</span> <span><div class=gui_icon></div></span> <span>DOS</span> <span>16-bit</span> <span>Historic</span> <span>Open-source</span> <span>ASM, C</span> <span>Floppy</span> <span>Graphical desktop for FreeDOS</span></a>
|
||||
<a class="tr" href="?profile=msdos4" id="start_msdos4"><span>MS-DOS 4</span> <span>0.5 MB</span> <span><div class=tui_icon></div></span> <span>DOS</span> <span>16-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM</span> <span>Floppy</span> <span>Contains EDLIN</span></a>
|
||||
<a class="tr" href="?profile=msdos" id="start_msdos"><span>MS-DOS 6.22</span> <span>2.4+ MB</span> <span><div class=tui_icon></div></span> <span>DOS</span> <span>16-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM</span> <span>HD</span> <span>Doom, Sim City, OCaml 1.0, Turbo C and more</span></a>
|
||||
<a class="tr" href="?profile=pcmos" id="start_pcmos"><span>PC-MOS/386</span> <span>0.7 MB</span> <span><div class=tui_icon></div></span> <span>DOS</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM, C</span> <span>Floppy</span> <span>Multi-user and multi-tasking OS</span></a>
|
||||
<a class="tr" href="?profile=psychdos" id="start_psychdos"><span>PsychDOS</span> <span>4.6+ MB</span> <span><div class=gui_icon></div></span> <span>DOS</span> <span>16-bit</span> <span>Historic</span> <span>Open-source</span> <span>ASM</span> <span>HD</span> <span>ANSI-like graphical desktop for FreeDOS</span></a>
|
||||
<a class="tr" href="?profile=leetos" id="start_leetos"><span>lEEt/OS</span> <span>0.5 MB</span> <span><div class=tui_icon></div></span> <span>DOS</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM, C</span> <span>Floppy</span> <span>Graphical desktop for ST-DOS</span></a>
|
||||
<a class="tr" href="?profile=bleskos" id="start_bleskos"><span>BleskOS</span> <span>0.2 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>Alternative OS for older PCs</span></a>
|
||||
<a class="tr" href="?profile=bluejay" id="start_bluejay"><span>Blue Jay</span> <span>83 KB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Floppy</span> <span>Based on MikeOS</span></a>
|
||||
<a class="tr" href="?profile=boneos" id="start_boneos"><span>BoneOS</span> <span>3.0 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>Simple hobby OS</span></a>
|
||||
<a class="tr" href="?profile=bootchess" id="start_bootchess"><span>BootChess</span> <span>4.0 KB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Floppy</span> <span>Chess in a bootsector</span></a>
|
||||
<a class="tr" href="?profile=catk" id="start_catk"><span>CatK</span> <span>3.2 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>Simple Unix-like kernel</span></a>
|
||||
<a class="tr" href="?profile=crazierl" id="start_crazierl"><span>Crazierl</span> <span>11 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C, Erlang</span> <span>Multiboot</span> <span>An Erlang Operating System</span></a>
|
||||
<a class="tr" href="?profile=duskos" id="start_duskos"><span>Dusk OS</span> <span>0.4 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>A 32-bit Forth</span></a>
|
||||
<a class="tr" href="?profile=floppybird" id="start_floppybird"><span>Floppy Bird</span> <span>6.5 KB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>Floppy</span> <span>Flappy Bird game in a bootsector</span></a>
|
||||
<a class="tr" href="?profile=helenos" id="start_helenos"><span>HelenOS</span> <span>7.9 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>A microkernel-based multiserver OS</span></a>
|
||||
<a class="tr" href="?profile=hello-v86" id="start_hello-v86"><span>Hello v86</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>Small bootsector demo for v86</span></a>
|
||||
<a class="tr" href="?profile=house" id="start_house"><span>House</span> <span>1.1 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>Haskell</span> <span>Floppy</span> <span>Haskell User's Operating System and Environment</span></a>
|
||||
<a class="tr" href="?profile=jx" id="start_jx"><span>JX</span> <span>1.3 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>Java</span> <span>Floppy</span> <span>Java-based operating system</span></a>
|
||||
<a class="tr" href="?profile=kolibrios" id="start_kolibrios"><span>KolibriOS</span> <span>1.3 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Floppy</span> <span>Various apps, games and demos</span></a>
|
||||
<a class="tr" href="?profile=littlekernel" id="start_littlekernel"><span>Little Kernel</span> <span>0.4 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>Multiboot</span> <span>An embedded kernel designed for small systems</span></a>
|
||||
<a class="tr" href="?profile=mcp" id="start_mcp"><span>M/CP</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>The Minimal Control Program</span></a>
|
||||
<a class="tr" href="?profile=mikeos" id="start_mikeos"><span>MikeOS</span> <span>0.2 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>CD</span> <span>Contains a FORTH and BASIC interpreter and several games</span></a>
|
||||
<a class="tr" href="?profile=mobius" id="start_mobius"><span>Mobius</span> <span>1.3 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>Floppy</span> <span>POSIX and Win32 compatible OS</span></a>
|
||||
<a class="tr" href="?profile=mu" id="start_mu"><span>Mu</span> <span>0.2 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>Minimal-dependency hobbyist computing stack</span></a>
|
||||
<a class="tr" href="?profile=nanoshell" id="start_nanoshell"><span>NanoShell</span> <span>3.5 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>Multi-tasked 32-bit OS with Win9x-like GUI</span></a>
|
||||
<a class="tr" href="?profile=newos" id="start_newos"><span>NewOS</span> <span>0.6 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C++</span> <span>Floppy</span> <span>Cross-platform portable OS</span></a>
|
||||
<a class="tr" href="?profile=nopeos" id="start_nopeos"><span>Nope OS</span> <span>92 KB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>Simple OS with BASIC interpreter</span></a>
|
||||
<a class="tr" href="?profile=oberon" id="start_oberon"><span>Oberon</span> <span>1.6 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Historic</span> <span>Open-source</span> <span>Oberon</span> <span>HD</span> <span>Native Oberon 2.3.6</span></a>
|
||||
<a class="tr" href="?profile=qnx" id="start_qnx"><span>QNX 4.05</span> <span>1.4 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>C</span> <span>Floppy</span> <span>1999 demo disk</span></a>
|
||||
<a class="tr" href="?profile=sectorlisp" id="start_sectorlisp"><span>SectorLISP</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>Bootstrapping LISP in a Boot Sector</span></a>
|
||||
<a class="tr" href="?profile=skift" id="start_skift"><span>Skift</span> <span>44 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C++</span> <span>CD</span> <span>A hobby OS built from scratch using C/C++</span></a>
|
||||
<a class="tr" href="?profile=snowdrop" id="start_snowdrop"><span>Snowdrop</span> <span>0.4 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Floppy</span> <span>16-bit hobby OS with GUI</span></a>
|
||||
<a class="tr" href="?profile=solos" id="start_solos"><span>Sol OS</span> <span>0.3 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Proprietary</span> <span>ASM</span> <span>Floppy</span> <span>Simple graphical OS</span></a>
|
||||
<a class="tr" href="?profile=stillalive" id="start_stillalive"><span>Still Alive</span> <span>10 KB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>Floppy</span> <span>Bootable demo that plays "Still Alive" from Portal</span></a>
|
||||
<a class="tr" href="?profile=t3xforth" id="start_t3xforth"><span>T3XFORTH</span> <span>59 KB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Historic</span> <span>Open-source</span> <span>ASM</span> <span>Floppy</span> <span>An old-school, plain vanilla FORTH system</span></a>
|
||||
<a class="tr" href="?profile=tetros" id="start_tetros"><span>TetrOS</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>Tetris that fits into the boot sector</span></a>
|
||||
<a class="tr" href="?profile=toaruos" id="start_toaruos"><span>ToaruOS</span> <span>6.3 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>A hobby OS written from scratch</span></a>
|
||||
<a class="tr" href="?profile=bootbasic" id="start_bootbasic"><span>bootBASIC</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>A BASIC in 512 bytes of x86 machine code</span></a>
|
||||
<a class="tr" href="?profile=pillman" id="start_pillman"><span>Pillman</span> <span>512 B</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>A yellow thing eats pills and is chased by monsters</span></a>
|
||||
<a class="tr" href="?profile=bootlogo" id="start_bootlogo"><span>bootLogo</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>Logo language in 508 bytes</span></a>
|
||||
<a class="tr" href="?profile=bootrogue" id="start_bootrogue"><span>bootRogue</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>A roguelike game that fits in a boot sector</span></a>
|
||||
<a class="tr" href="?profile=dino" id="start_dino"><span>dino</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>Chrome's t-rex based bootsector game</span></a>
|
||||
<a class="tr" href="?profile=invaders" id="start_invaders"><span>Invaders</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>Invaders in a bootsector</span></a>
|
||||
<a class="tr" href="?profile=sanos" id="start_sanos"><span>Sanos</span> <span>0.5 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>Minimalistic 32-bit x86 OS</span></a>
|
||||
<a class="tr" href="?profile=sectorforth" id="start_sectorforth"><span>sectorforth</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>16-bit x86 Forth in a bootsector</span></a>
|
||||
<a class="tr" href="?profile=dancy" id="start_dancy"><span>Dancy</span> <span>1.3 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>Dancy Operating System</span></a>
|
||||
<a class="tr" href="?profile=curios" id="start_curios"><span>CuriOS</span> <span>6.6 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>Simple GUI based OS inspired by AmigaOS</span></a>
|
||||
<a class="tr" href="?profile=os64" id="start_os64"><span>OS64</span> <span>2.2 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>Commodore 64 emulator OS for x86 (slow)</span></a>
|
||||
<a class="tr" href="?profile=netboot.xyz" id="start_netboot.xyz"><span>netboot.xyz</span> <span>1.0 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>Netboot into various operating systems (slow)</span></a>
|
||||
<a class="tr" href="?profile=squeaknos" id="start_squeaknos"><span>SqueakNOS</span> <span>20 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C, Smalltalk</span> <span>CD</span> <span>Smalltalk as a Standalone OS</span></a>
|
||||
<a class="tr" href="?profile=chokanji4" id="start_chokanji4"><span>Chokanji 4</span> <span>13+ MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>C</span> <span>HD</span> <span>A Japanese OS based on the TRON project</span></a>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<h4 id="setup">Setup</h4>
|
||||
|
|
@ -127,12 +185,22 @@
|
|||
|
||||
<tr>
|
||||
<td><label for="floppy_image">Floppy disk image</label></td>
|
||||
<td> <input type="file" id="floppy_image"><br></td>
|
||||
<td><input type="file" id="fda_image"> or <a id="fda_toggle_empty_disk">create empty floppy disk</a><br></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><label for="floppy_image">Second floppy disk image</label></td>
|
||||
<td><input type="file" id="fdb_image"> or <a id="fdb_toggle_empty_disk">create empty floppy disk</a><br></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><label for="hda_image">Hard disk image</label></td>
|
||||
<td><input type="file" id="hda_image"><br></td>
|
||||
<td><input type="file" id="hda_image"> or <a id="hda_toggle_empty_disk">create empty hard disk</a><br></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><label for="hdb_image">Second hard disk image</label></td>
|
||||
<td><input type="file" id="hdb_image"> or <a id="hdb_toggle_empty_disk">create empty hard disk</a><br></td>
|
||||
</tr>
|
||||
|
||||
<!--
|
||||
|
|
@ -153,7 +221,12 @@
|
|||
|
||||
<tr>
|
||||
<td><label for="bios">BIOS</label></td>
|
||||
<td> <input type="file" id="bios"><br></td>
|
||||
<td><input type="file" id="bios"><br></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><label for="vga_bios">VGA BIOS</label></td>
|
||||
<td><input type="file" id="vga_bios"><br></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
|
@ -174,6 +247,10 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2"><hr></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><label for="relay_url">Networking proxy</label><br>
|
||||
Presets: <a id="network_none">none</a>, <a id="network_inbrowser">inbrowser</a>, <a id="network_relay">public relay</a>, <a id="network_wisp">wisp</a>, <a id="network_fetch">fetch</a></td>
|
||||
|
|
@ -182,6 +259,23 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><label for="net_device_type">Network Device Type</label></td>
|
||||
<td>
|
||||
<select id="net_device_type">
|
||||
<option value="ne2k">ne2k</option>
|
||||
<option value="virtio">virtio</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><label for="mtu">Network MTU (only used for virtio)</label></td>
|
||||
<td>
|
||||
<input id="mtu" type="number" value="1500" min="68" max="65535" step="1"> B<br>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2"><hr></td>
|
||||
</tr>
|
||||
|
|
@ -236,6 +330,8 @@
|
|||
<input type="button" value="Get second hard disk image" id="get_hdb_image">
|
||||
<input type="button" value="Get CD-ROM image" id="get_cdrom_image">
|
||||
<input type="button" value="Insert floppy image" id="change_fda_image">
|
||||
<input type="button" value="Insert second floppy image" id="change_fdb_image">
|
||||
<input type="button" value="Insert CD image" id="change_cdrom_image" title="Either a single .iso file or multiple files of any type">
|
||||
<input type="button" value="Save State" id="save_state">
|
||||
<input type="button" value="Load State" id="load_state"> <input type="file" style="display: none" id="load_state_input">
|
||||
<input type="button" value="Memory Dump" id="memory_dump">
|
||||
|
|
@ -245,13 +341,13 @@
|
|||
<input type="button" value="Go fullscreen" id="fullscreen">
|
||||
<input type="button" value="Take screenshot" id="take_screenshot">
|
||||
<input type="button" value="Mute" id="mute">
|
||||
<input type="button" value="Enable theatre mode" id="toggle_theatre">
|
||||
<input type="button" style="display: none" value="Enable zoom to fit" id="toggle_zoom_to_fit">
|
||||
|
||||
<label>
|
||||
Scale:
|
||||
<input type="number" min="0.25" step="0.25" value="1.0" id="scale" style="width: 50px">
|
||||
</label>
|
||||
|
||||
<br><br>
|
||||
</div>
|
||||
<pre style="display: none" id="loading"></pre>
|
||||
</div>
|
||||
|
|
@ -318,8 +414,11 @@
|
|||
|
||||
<div id="terminal"></div>
|
||||
|
||||
<input type="button" value="Hide UI" id="toggle_ui" style="display: none">
|
||||
<div id="theatre_background" style="display: none"></div>
|
||||
|
||||
<br style="clear: both">
|
||||
<code>Version: <a href="https://github.com/copy/v86/commits/98e7110c2">98e7110c2</a> (Feb 16, 2021 12:02)</code>
|
||||
<code>Version: <a id="version" href="https://github.com/copy/v86/commits/98e7110c2">98e7110c2</a> (Feb 16, 2021 12:02)</code>
|
||||
|
||||
<hr>
|
||||
<a href="debug.html">Enable debug</a>
|
||||
|
|
|
|||
500
lib/9p.js
500
lib/9p.js
|
|
@ -4,7 +4,23 @@
|
|||
// 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";
|
||||
import { S_IFREG, S_IFDIR, STATUS_UNLINKED } from "./filesystem.js";
|
||||
import * as marshall from "../lib/marshall.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";
|
||||
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;
|
||||
|
|
@ -16,13 +32,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,50 +65,35 @@ 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;
|
||||
var FID_XATTR = 2;
|
||||
|
||||
function range(size)
|
||||
{
|
||||
return Array.from(Array(size).keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @param {FS} filesystem
|
||||
* @param {CPU} cpu
|
||||
* @param {Function} receive
|
||||
*/
|
||||
function Virtio9p(filesystem, cpu, bus) {
|
||||
/** @type {FS} */
|
||||
this.fs = filesystem;
|
||||
|
||||
/** @const @type {BusConnector} */
|
||||
this.bus = bus;
|
||||
|
||||
//this.configspace = [0x0, 0x4, 0x68, 0x6F, 0x73, 0x74]; // length of string and "host" string
|
||||
//this.configspace = [0x0, 0x9, 0x2F, 0x64, 0x65, 0x76, 0x2F, 0x72, 0x6F, 0x6F, 0x74 ]; // length of string and "/dev/root" string
|
||||
this.configspace_tagname = [0x68, 0x6F, 0x73, 0x74, 0x39, 0x70]; // "host9p" string
|
||||
this.configspace_taglen = this.configspace_tagname.length; // num bytes
|
||||
this.VERSION = "9P2000.L";
|
||||
this.BLOCKSIZE = 8192; // Let's define one page.
|
||||
this.msize = 8192; // maximum message size
|
||||
this.replybuffer = new Uint8Array(this.msize*2); // Twice the msize to stay on the safe site
|
||||
this.replybuffersize = 0;
|
||||
|
||||
this.fids = [];
|
||||
|
||||
/** @type {VirtIO} */
|
||||
this.virtio = new VirtIO(cpu,
|
||||
function init_virtio(cpu, configspace_taglen, configspace_tagname, receive)
|
||||
{
|
||||
const virtio = new VirtIO(cpu,
|
||||
{
|
||||
name: "virtio-9p",
|
||||
pci_id: 0x06 << 3,
|
||||
|
|
@ -131,12 +132,13 @@ function Virtio9p(filesystem, cpu, bus) {
|
|||
" (expected queue_id of 0)");
|
||||
return;
|
||||
}
|
||||
while(this.virtqueue.has_request())
|
||||
const virtqueue = virtio.queues[0];
|
||||
while(virtqueue.has_request())
|
||||
{
|
||||
const bufchain = this.virtqueue.pop_request();
|
||||
this.ReceiveRequest(bufchain);
|
||||
const bufchain = virtqueue.pop_request();
|
||||
receive(bufchain);
|
||||
}
|
||||
this.virtqueue.notify_me_after(0);
|
||||
virtqueue.notify_me_after(0);
|
||||
// Don't flush replies here: async replies are not completed yet.
|
||||
},
|
||||
],
|
||||
|
|
@ -153,21 +155,48 @@ function Virtio9p(filesystem, cpu, bus) {
|
|||
{
|
||||
bytes: 2,
|
||||
name: "mount tag length",
|
||||
read: () => this.configspace_taglen,
|
||||
read: () => 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,
|
||||
// Note: configspace_tagname may have changed after set_state
|
||||
read: () => this.configspace_tagname[index] || 0,
|
||||
read: () => configspace_tagname[index] || 0,
|
||||
write: data => { /* read only */ },
|
||||
})
|
||||
)),
|
||||
},
|
||||
});
|
||||
return virtio;
|
||||
}
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @param {FS} filesystem
|
||||
* @param {CPU} cpu
|
||||
*/
|
||||
export function Virtio9p(filesystem, cpu, bus) {
|
||||
/** @type {FS} */
|
||||
this.fs = filesystem;
|
||||
|
||||
/** @const @type {BusConnector} */
|
||||
this.bus = bus;
|
||||
|
||||
this.configspace_tagname = [0x68, 0x6F, 0x73, 0x74, 0x39, 0x70]; // "host9p" string
|
||||
this.configspace_taglen = this.configspace_tagname.length; // num bytes
|
||||
|
||||
this.virtio = init_virtio(cpu, this.configspace_taglen, this.configspace_tagname, this.ReceiveRequest.bind(this));
|
||||
this.virtqueue = this.virtio.queues[0];
|
||||
|
||||
this.VERSION = "9P2000.L";
|
||||
this.BLOCKSIZE = 8192; // Let's define one page.
|
||||
this.msize = 8192; // maximum message size
|
||||
this.replybuffer = new Uint8Array(this.msize*2); // Twice the msize to stay on the safe site
|
||||
this.replybuffersize = 0;
|
||||
this.fids = [];
|
||||
}
|
||||
|
||||
Virtio9p.prototype.get_state = function()
|
||||
|
|
@ -209,7 +238,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};
|
||||
};
|
||||
|
|
@ -222,17 +251,17 @@ Virtio9p.prototype.update_dbg_name = function(idx, newname)
|
|||
}
|
||||
};
|
||||
|
||||
Virtio9p.prototype.reset = function() {
|
||||
Virtio9p.prototype.reset = function()
|
||||
{
|
||||
this.fids = [];
|
||||
this.virtio.reset();
|
||||
};
|
||||
|
||||
|
||||
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<payload.length; i++)
|
||||
// this.replybuffer[7+i] = payload[i];
|
||||
|
|
@ -262,7 +291,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var size = header[0];
|
||||
var id = header[1];
|
||||
var tag = header[2];
|
||||
//message.Debug("size:" + size + " id:" + id + " tag:" + tag);
|
||||
//dbg_log("size:" + size + " id:" + id + " tag:" + tag, LOG_9P);
|
||||
|
||||
switch(id)
|
||||
{
|
||||
|
|
@ -290,24 +319,18 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var req = marshall.Unmarshall(["w", "w"], buffer, state);
|
||||
var fid = req[0];
|
||||
var mode = req[1];
|
||||
message.Debug("[open] fid=" + fid + ", mode=" + mode);
|
||||
dbg_log("[open] fid=" + fid + ", mode=" + mode, LOG_9P);
|
||||
var idx = this.fids[fid].inodeid;
|
||||
var inode = this.fs.GetInode(idx);
|
||||
message.Debug("file open " + this.fids[fid].dbg_name);
|
||||
//if (inode.status === STATUS_LOADING) return;
|
||||
var ret = this.fs.OpenInode(idx, mode);
|
||||
dbg_log("file open " + this.fids[fid].dbg_name + " tag:"+tag, LOG_9P);
|
||||
await this.fs.OpenInode(idx, mode);
|
||||
|
||||
this.fs.AddEvent(this.fids[fid].inodeid,
|
||||
function() {
|
||||
message.Debug("file opened " + this.fids[fid].dbg_name + " tag:"+tag);
|
||||
var req = [];
|
||||
req[0] = inode.qid;
|
||||
req[1] = this.msize - 24;
|
||||
marshall.Marshall(["Q", "w"], req, this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, 13+4);
|
||||
this.SendReply(bufchain);
|
||||
}.bind(this)
|
||||
);
|
||||
req = [];
|
||||
req[0] = inode.qid;
|
||||
req[1] = this.msize - 24;
|
||||
marshall.Marshall(["Q", "w"], req, this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, 13+4);
|
||||
this.SendReply(bufchain);
|
||||
break;
|
||||
|
||||
case 70: // link
|
||||
|
|
@ -315,7 +338,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var dfid = req[0];
|
||||
var fid = req[1];
|
||||
var name = req[2];
|
||||
message.Debug("[link] dfid=" + dfid + ", name=" + name);
|
||||
dbg_log("[link] dfid=" + dfid + ", name=" + name, LOG_9P);
|
||||
|
||||
var ret = this.fs.Link(this.fids[dfid].inodeid, this.fids[fid].inodeid, name);
|
||||
|
||||
|
|
@ -343,7 +366,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var name = req[1];
|
||||
var symgt = req[2];
|
||||
var gid = req[3];
|
||||
message.Debug("[symlink] fid=" + fid + ", name=" + name + ", symgt=" + symgt + ", gid=" + gid);
|
||||
dbg_log("[symlink] fid=" + fid + ", name=" + name + ", symgt=" + symgt + ", gid=" + gid, LOG_9P);
|
||||
var idx = this.fs.CreateSymlink(name, this.fids[fid].inodeid, symgt);
|
||||
var inode = this.fs.GetInode(idx);
|
||||
inode.uid = this.fids[fid].uid;
|
||||
|
|
@ -361,7 +384,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var major = req[3];
|
||||
var minor = req[4];
|
||||
var gid = req[5];
|
||||
message.Debug("[mknod] fid=" + fid + ", name=" + name + ", major=" + major + ", minor=" + minor+ "");
|
||||
dbg_log("[mknod] fid=" + fid + ", name=" + name + ", major=" + major + ", minor=" + minor+ "", LOG_9P);
|
||||
var idx = this.fs.CreateNode(name, this.fids[fid].inodeid, major, minor);
|
||||
var inode = this.fs.GetInode(idx);
|
||||
inode.mode = mode;
|
||||
|
|
@ -378,7 +401,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var req = marshall.Unmarshall(["w"], buffer, state);
|
||||
var fid = req[0];
|
||||
var inode = this.fs.GetInode(this.fids[fid].inodeid);
|
||||
message.Debug("[readlink] fid=" + fid + " name=" + this.fids[fid].dbg_name + " target=" + inode.symlink);
|
||||
dbg_log("[readlink] fid=" + fid + " name=" + this.fids[fid].dbg_name + " target=" + inode.symlink, LOG_9P);
|
||||
size = marshall.Marshall(["s"], [inode.symlink], this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, size);
|
||||
this.SendReply(bufchain);
|
||||
|
|
@ -391,7 +414,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var name = req[1];
|
||||
var mode = req[2];
|
||||
var gid = req[3];
|
||||
message.Debug("[mkdir] fid=" + fid + ", name=" + name + ", mode=" + mode + ", gid=" + gid);
|
||||
dbg_log("[mkdir] fid=" + fid + ", name=" + name + ", mode=" + mode + ", gid=" + gid, LOG_9P);
|
||||
var idx = this.fs.CreateDirectory(name, this.fids[fid].inodeid);
|
||||
var inode = this.fs.GetInode(idx);
|
||||
inode.mode = mode | S_IFDIR;
|
||||
|
|
@ -410,7 +433,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var mode = req[3];
|
||||
var gid = req[4];
|
||||
this.bus.send("9p-create", [name, this.fids[fid].inodeid]);
|
||||
message.Debug("[create] fid=" + fid + ", name=" + name + ", flags=" + flags + ", mode=" + mode + ", gid=" + gid);
|
||||
dbg_log("[create] fid=" + fid + ", name=" + name + ", flags=" + flags + ", mode=" + mode + ", gid=" + gid, LOG_9P);
|
||||
var idx = this.fs.CreateFile(name, this.fids[fid].inodeid);
|
||||
this.fids[fid].inodeid = idx;
|
||||
this.fids[fid].type = FID_INODE;
|
||||
|
|
@ -430,7 +453,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var flags = req[2];
|
||||
var lock_length = req[4] === 0 ? Infinity : req[4];
|
||||
var lock_request = this.fs.DescribeLock(req[1], req[3], lock_length, req[5], req[6]);
|
||||
message.Debug("[lock] fid=" + fid +
|
||||
dbg_log("[lock] fid=" + fid +
|
||||
", type=" + P9_LOCK_TYPES[lock_request.type] + ", start=" + lock_request.start +
|
||||
", length=" + lock_request.length + ", proc_id=" + lock_request.proc_id);
|
||||
|
||||
|
|
@ -446,7 +469,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var fid = req[0];
|
||||
var lock_length = req[3] === 0 ? Infinity : req[3];
|
||||
var lock_request = this.fs.DescribeLock(req[1], req[2], lock_length, req[4], req[5]);
|
||||
message.Debug("[getlock] fid=" + fid +
|
||||
dbg_log("[getlock] fid=" + fid +
|
||||
", type=" + P9_LOCK_TYPES[lock_request.type] + ", start=" + lock_request.start +
|
||||
", length=" + lock_request.length + ", proc_id=" + lock_request.proc_id);
|
||||
|
||||
|
|
@ -472,10 +495,10 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var req = marshall.Unmarshall(["w", "d"], buffer, state);
|
||||
var fid = req[0];
|
||||
var inode = this.fs.GetInode(this.fids[fid].inodeid);
|
||||
message.Debug("[getattr]: fid=" + fid + " name=" + this.fids[fid].dbg_name + " request mask=" + req[1]);
|
||||
dbg_log("[getattr]: fid=" + fid + " name=" + this.fids[fid].dbg_name + " request mask=" + req[1], LOG_9P);
|
||||
if(!inode || inode.status === STATUS_UNLINKED)
|
||||
{
|
||||
message.Debug("getattr: unlinked");
|
||||
dbg_log("getattr: unlinked", LOG_9P);
|
||||
this.SendError(tag, "No such file or directory", ENOENT);
|
||||
this.SendReply(bufchain);
|
||||
break;
|
||||
|
|
@ -528,7 +551,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
], buffer, state);
|
||||
var fid = req[0];
|
||||
var inode = this.fs.GetInode(this.fids[fid].inodeid);
|
||||
message.Debug("[setattr]: fid=" + fid + " request mask=" + req[1] + " name=" + this.fids[fid].dbg_name);
|
||||
dbg_log("[setattr]: fid=" + fid + " request mask=" + req[1] + " name=" + this.fids[fid].dbg_name, LOG_9P);
|
||||
if(req[1] & P9_SETATTR_MODE) {
|
||||
// XXX: check mode (S_IFREG or S_IFDIR or similar should be set)
|
||||
inode.mode = req[2];
|
||||
|
|
@ -575,11 +598,11 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var offset = req[1];
|
||||
var count = req[2];
|
||||
var inode = this.fs.GetInode(this.fids[fid].inodeid);
|
||||
if(id === 40) message.Debug("[treaddir]: fid=" + fid + " offset=" + offset + " count=" + count);
|
||||
if(id === 116) message.Debug("[read]: fid=" + fid + " (" + this.fids[fid].dbg_name + ") offset=" + offset + " count=" + count + " fidtype=" + this.fids[fid].type);
|
||||
if(id === 40) dbg_log("[treaddir]: fid=" + fid + " offset=" + offset + " count=" + count, LOG_9P);
|
||||
if(id === 116) dbg_log("[read]: fid=" + fid + " (" + this.fids[fid].dbg_name + ") offset=" + offset + " count=" + count + " fidtype=" + this.fids[fid].type, LOG_9P);
|
||||
if(!inode || inode.status === STATUS_UNLINKED)
|
||||
{
|
||||
message.Debug("read/treaddir: unlinked");
|
||||
dbg_log("read/treaddir: unlinked", LOG_9P);
|
||||
this.SendError(tag, "No such file or directory", ENOENT);
|
||||
this.SendReply(bufchain);
|
||||
break;
|
||||
|
|
@ -592,7 +615,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
this.BuildReply(id, tag, 4 + count);
|
||||
this.SendReply(bufchain);
|
||||
} else {
|
||||
this.fs.OpenInode(this.fids[fid].inodeid, undefined);
|
||||
await this.fs.OpenInode(this.fids[fid].inodeid, undefined);
|
||||
const inodeid = this.fids[fid].inodeid;
|
||||
|
||||
count = Math.min(count, this.replybuffer.length - (7 + 4));
|
||||
|
|
@ -633,7 +656,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
|
||||
const filename = this.fids[fid].dbg_name;
|
||||
|
||||
message.Debug("[write]: fid=" + fid + " (" + filename + ") offset=" + offset + " count=" + count + " fidtype=" + this.fids[fid].type);
|
||||
dbg_log("[write]: fid=" + fid + " (" + filename + ") offset=" + offset + " count=" + count + " fidtype=" + this.fids[fid].type, LOG_9P);
|
||||
if(this.fids[fid].type === FID_XATTR)
|
||||
{
|
||||
// XXX: xattr not supported yet. Ignore write.
|
||||
|
|
@ -660,7 +683,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var oldname = req[1];
|
||||
var newdirfid = req[2];
|
||||
var newname = req[3];
|
||||
message.Debug("[renameat]: oldname=" + oldname + " newname=" + newname);
|
||||
dbg_log("[renameat]: oldname=" + oldname + " newname=" + newname, LOG_9P);
|
||||
var ret = await this.fs.Rename(this.fids[olddirfid].inodeid, oldname, this.fids[newdirfid].inodeid, newname);
|
||||
if(ret < 0) {
|
||||
let error_message = "";
|
||||
|
|
@ -690,7 +713,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var dirfd = req[0];
|
||||
var name = req[1];
|
||||
var flags = req[2];
|
||||
message.Debug("[unlink]: dirfd=" + dirfd + " name=" + name + " flags=" + flags);
|
||||
dbg_log("[unlink]: dirfd=" + dirfd + " name=" + name + " flags=" + flags, LOG_9P);
|
||||
var fid = this.fs.Search(this.fids[dirfd].inodeid, name);
|
||||
if(fid === -1) {
|
||||
this.SendError(tag, "No such file or directory", ENOENT);
|
||||
|
|
@ -717,7 +740,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
|
||||
case 100: // version
|
||||
var version = marshall.Unmarshall(["w", "s"], buffer, state);
|
||||
message.Debug("[version]: msize=" + version[0] + " version=" + version[1]);
|
||||
dbg_log("[version]: msize=" + version[0] + " version=" + version[1], LOG_9P);
|
||||
if(this.msize !== version[0])
|
||||
{
|
||||
this.msize = version[0];
|
||||
|
|
@ -733,7 +756,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var req = marshall.Unmarshall(["w", "w", "s", "s", "w"], buffer, state);
|
||||
var fid = req[0];
|
||||
var uid = req[4];
|
||||
message.Debug("[attach]: fid=" + fid + " afid=" + hex8(req[1]) + " uname=" + req[2] + " aname=" + req[3]);
|
||||
dbg_log("[attach]: fid=" + fid + " afid=" + h(req[1]) + " uname=" + req[2] + " aname=" + req[3], LOG_9P);
|
||||
this.fids[fid] = this.Createfid(0, FID_INODE, uid, "");
|
||||
var inode = this.fs.GetInode(this.fids[fid].inodeid);
|
||||
marshall.Marshall(["Q"], [inode.qid], this.replybuffer, 7);
|
||||
|
|
@ -745,7 +768,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
case 108: // tflush
|
||||
var req = marshall.Unmarshall(["h"], buffer, state);
|
||||
var oldtag = req[0];
|
||||
message.Debug("[flush] " + tag);
|
||||
dbg_log("[flush] " + tag, LOG_9P);
|
||||
//marshall.Marshall(["Q"], [inode.qid], this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, 0);
|
||||
this.SendReply(bufchain);
|
||||
|
|
@ -757,7 +780,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var fid = req[0];
|
||||
var nwfid = req[1];
|
||||
var nwname = req[2];
|
||||
message.Debug("[walk]: fid=" + req[0] + " nwfid=" + req[1] + " nwname=" + nwname);
|
||||
dbg_log("[walk]: fid=" + req[0] + " nwfid=" + req[1] + " nwname=" + nwname, LOG_9P);
|
||||
if(nwname === 0) {
|
||||
this.fids[nwfid] = this.Createfid(this.fids[fid].inodeid, FID_INODE, this.fids[fid].uid, this.fids[fid].dbg_name);
|
||||
//this.fids[nwfid].inodeid = this.fids[fid].inodeid;
|
||||
|
|
@ -775,17 +798,17 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var offset = 7+2;
|
||||
var nwidx = 0;
|
||||
//console.log(idx, this.fs.GetInode(idx));
|
||||
message.Debug("walk in dir " + this.fids[fid].dbg_name + " to: " + walk.toString());
|
||||
dbg_log("walk in dir " + this.fids[fid].dbg_name + " to: " + walk.toString(), LOG_9P);
|
||||
for(var i=0; i<nwname; i++) {
|
||||
idx = this.fs.Search(idx, walk[i]);
|
||||
|
||||
if(idx === -1) {
|
||||
message.Debug("Could not find: " + walk[i]);
|
||||
dbg_log("Could not find: " + walk[i], LOG_9P);
|
||||
break;
|
||||
}
|
||||
offset += marshall.Marshall(["Q"], [this.fs.GetInode(idx).qid], this.replybuffer, offset);
|
||||
nwidx++;
|
||||
//message.Debug(this.fids[nwfid].inodeid);
|
||||
//dbg_log(this.fids[nwfid].inodeid, LOG_9P);
|
||||
//this.fids[nwfid].inodeid = idx;
|
||||
//this.fids[nwfid].type = FID_INODE;
|
||||
this.fids[nwfid] = this.Createfid(idx, FID_INODE, this.fids[fid].uid, walk[i]);
|
||||
|
|
@ -797,7 +820,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
|
||||
case 120: // clunk
|
||||
var req = marshall.Unmarshall(["w"], buffer, state);
|
||||
message.Debug("[clunk]: fid=" + req[0]);
|
||||
dbg_log("[clunk]: fid=" + req[0], LOG_9P);
|
||||
if(this.fids[req[0]] && this.fids[req[0]].inodeid >= 0) {
|
||||
await this.fs.CloseInode(this.fids[req[0]].inodeid);
|
||||
this.fids[req[0]].inodeid = -1;
|
||||
|
|
@ -813,7 +836,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var name = req[1];
|
||||
var attr_size = req[2];
|
||||
var flags = req[3];
|
||||
message.Debug("[txattrcreate]: fid=" + fid + " name=" + name + " attr_size=" + attr_size + " flags=" + flags);
|
||||
dbg_log("[txattrcreate]: fid=" + fid + " name=" + name + " attr_size=" + attr_size + " flags=" + flags, LOG_9P);
|
||||
|
||||
// XXX: xattr not supported yet. E.g. checks corresponding to the flags needed.
|
||||
this.fids[fid].type = FID_XATTR;
|
||||
|
|
@ -829,7 +852,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var fid = req[0];
|
||||
var newfid = req[1];
|
||||
var name = req[2];
|
||||
message.Debug("[xattrwalk]: fid=" + req[0] + " newfid=" + req[1] + " name=" + req[2]);
|
||||
dbg_log("[xattrwalk]: fid=" + req[0] + " newfid=" + req[1] + " name=" + req[2], LOG_9P);
|
||||
|
||||
// Workaround for Linux restarts writes until full blocksize
|
||||
this.SendError(tag, "Setxattr not supported", EOPNOTSUPP);
|
||||
|
|
@ -850,8 +873,8 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
break;
|
||||
|
||||
default:
|
||||
message.Debug("Error in Virtio9p: Unknown id " + id + " received");
|
||||
message.Abort();
|
||||
dbg_log("Error in Virtio9p: Unknown id " + id + " received", LOG_9P);
|
||||
dbg_assert(false);
|
||||
//this.SendError(tag, "Operation i not supported", EOPNOTSUPP);
|
||||
//this.SendReply(bufchain);
|
||||
break;
|
||||
|
|
@ -860,3 +883,288 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
//consistency checks if there are problems with the filesystem
|
||||
//this.fs.Check();
|
||||
};
|
||||
|
||||
/** @typedef {function(Uint8Array, function(Uint8Array):void):void} */
|
||||
let P9Handler;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @param {P9Handler} handle_fn
|
||||
* @param {CPU} cpu
|
||||
*/
|
||||
export function Virtio9pHandler(handle_fn, cpu) {
|
||||
/** @type {P9Handler} */
|
||||
this.handle_fn = handle_fn;
|
||||
this.tag_bufchain = new Map();
|
||||
|
||||
this.configspace_tagname = [0x68, 0x6F, 0x73, 0x74, 0x39, 0x70]; // "host9p" string
|
||||
this.configspace_taglen = this.configspace_tagname.length; // num bytes
|
||||
|
||||
this.virtio = init_virtio(
|
||||
cpu,
|
||||
this.configspace_taglen,
|
||||
this.configspace_tagname,
|
||||
async (bufchain) => {
|
||||
// TODO: split into header + data blobs to avoid unnecessary copying.
|
||||
const reqbuf = new Uint8Array(bufchain.length_readable);
|
||||
bufchain.get_next_blob(reqbuf);
|
||||
|
||||
var reqheader = marshall.Unmarshall(["w", "b", "h"], reqbuf, { offset : 0 });
|
||||
var reqtag = reqheader[2];
|
||||
|
||||
this.tag_bufchain.set(reqtag, bufchain);
|
||||
this.handle_fn(reqbuf, (replybuf) => {
|
||||
var replyheader = marshall.Unmarshall(["w", "b", "h"], replybuf, { offset: 0 });
|
||||
var replytag = replyheader[2];
|
||||
|
||||
const bufchain = this.tag_bufchain.get(replytag);
|
||||
if(!bufchain)
|
||||
{
|
||||
console.error("No bufchain found for tag: " + replytag);
|
||||
return;
|
||||
}
|
||||
|
||||
bufchain.set_next_blob(replybuf);
|
||||
this.virtqueue.push_reply(bufchain);
|
||||
this.virtqueue.flush_replies();
|
||||
|
||||
this.tag_bufchain.delete(replytag);
|
||||
});
|
||||
}
|
||||
);
|
||||
this.virtqueue = this.virtio.queues[0];
|
||||
}
|
||||
|
||||
Virtio9pHandler.prototype.get_state = function()
|
||||
{
|
||||
var state = [];
|
||||
|
||||
state[0] = this.configspace_tagname;
|
||||
state[1] = this.configspace_taglen;
|
||||
state[2] = this.virtio;
|
||||
state[3] = this.tag_bufchain;
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
Virtio9pHandler.prototype.set_state = function(state)
|
||||
{
|
||||
this.configspace_tagname = state[0];
|
||||
this.configspace_taglen = state[1];
|
||||
this.virtio.set_state(state[2]);
|
||||
this.virtqueue = this.virtio.queues[0];
|
||||
this.tag_bufchain = state[3];
|
||||
};
|
||||
|
||||
|
||||
Virtio9pHandler.prototype.reset = function()
|
||||
{
|
||||
this.virtio.reset();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @param {string} url
|
||||
* @param {CPU} cpu
|
||||
*/
|
||||
export function Virtio9pProxy(url, cpu)
|
||||
{
|
||||
this.socket = undefined;
|
||||
this.cpu = cpu;
|
||||
|
||||
// TODO: circular buffer?
|
||||
this.send_queue = [];
|
||||
this.url = url;
|
||||
|
||||
this.reconnect_interval = 10000;
|
||||
this.last_connect_attempt = Date.now() - this.reconnect_interval;
|
||||
this.send_queue_limit = 64;
|
||||
this.destroyed = false;
|
||||
|
||||
this.tag_bufchain = new Map();
|
||||
|
||||
this.configspace_tagname = [0x68, 0x6F, 0x73, 0x74, 0x39, 0x70]; // "host9p" string
|
||||
this.configspace_taglen = this.configspace_tagname.length; // num bytes
|
||||
|
||||
this.virtio = init_virtio(
|
||||
cpu,
|
||||
this.configspace_taglen,
|
||||
this.configspace_tagname,
|
||||
async (bufchain) => {
|
||||
// TODO: split into header + data blobs to avoid unnecessary copying.
|
||||
const reqbuf = new Uint8Array(bufchain.length_readable);
|
||||
bufchain.get_next_blob(reqbuf);
|
||||
|
||||
const reqheader = marshall.Unmarshall(["w", "b", "h"], reqbuf, { offset : 0 });
|
||||
const reqtag = reqheader[2];
|
||||
|
||||
this.tag_bufchain.set(reqtag, bufchain);
|
||||
this.send(reqbuf);
|
||||
}
|
||||
);
|
||||
this.virtqueue = this.virtio.queues[0];
|
||||
}
|
||||
|
||||
Virtio9pProxy.prototype.get_state = function()
|
||||
{
|
||||
var state = [];
|
||||
|
||||
state[0] = this.configspace_tagname;
|
||||
state[1] = this.configspace_taglen;
|
||||
state[2] = this.virtio;
|
||||
state[3] = this.tag_bufchain;
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
Virtio9pProxy.prototype.set_state = function(state)
|
||||
{
|
||||
this.configspace_tagname = state[0];
|
||||
this.configspace_taglen = state[1];
|
||||
this.virtio.set_state(state[2]);
|
||||
this.virtqueue = this.virtio.queues[0];
|
||||
this.tag_bufchain = state[3];
|
||||
};
|
||||
|
||||
Virtio9pProxy.prototype.reset = function() {
|
||||
this.virtio.reset();
|
||||
};
|
||||
|
||||
Virtio9pProxy.prototype.handle_message = function(e)
|
||||
{
|
||||
const replybuf = new Uint8Array(e.data);
|
||||
const replyheader = marshall.Unmarshall(["w", "b", "h"], replybuf, { offset: 0 });
|
||||
const replytag = replyheader[2];
|
||||
|
||||
const bufchain = this.tag_bufchain.get(replytag);
|
||||
if(!bufchain)
|
||||
{
|
||||
console.error("Virtio9pProxy: No bufchain found for tag: " + replytag);
|
||||
return;
|
||||
}
|
||||
|
||||
bufchain.set_next_blob(replybuf);
|
||||
this.virtqueue.push_reply(bufchain);
|
||||
this.virtqueue.flush_replies();
|
||||
|
||||
this.tag_bufchain.delete(replytag);
|
||||
};
|
||||
|
||||
Virtio9pProxy.prototype.handle_close = function(e)
|
||||
{
|
||||
//console.log("onclose", e);
|
||||
|
||||
if(!this.destroyed)
|
||||
{
|
||||
this.connect();
|
||||
setTimeout(this.connect.bind(this), this.reconnect_interval);
|
||||
}
|
||||
};
|
||||
|
||||
Virtio9pProxy.prototype.handle_open = function(e)
|
||||
{
|
||||
//console.log("open", e);
|
||||
|
||||
for(var i = 0; i < this.send_queue.length; i++)
|
||||
{
|
||||
this.send(this.send_queue[i]);
|
||||
}
|
||||
|
||||
this.send_queue = [];
|
||||
};
|
||||
|
||||
Virtio9pProxy.prototype.handle_error = function(e)
|
||||
{
|
||||
//console.log("onerror", e);
|
||||
};
|
||||
|
||||
Virtio9pProxy.prototype.destroy = function()
|
||||
{
|
||||
this.destroyed = true;
|
||||
if(this.socket)
|
||||
{
|
||||
this.socket.close();
|
||||
}
|
||||
};
|
||||
|
||||
Virtio9pProxy.prototype.connect = function()
|
||||
{
|
||||
if(typeof WebSocket === "undefined")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.socket)
|
||||
{
|
||||
var state = this.socket.readyState;
|
||||
|
||||
if(state === 0 || state === 1)
|
||||
{
|
||||
// already or almost there
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var now = Date.now();
|
||||
|
||||
if(this.last_connect_attempt + this.reconnect_interval > now)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.last_connect_attempt = Date.now();
|
||||
|
||||
try
|
||||
{
|
||||
this.socket = new WebSocket(this.url);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
console.error(e);
|
||||
return;
|
||||
}
|
||||
|
||||
this.socket.binaryType = "arraybuffer";
|
||||
|
||||
this.socket.onopen = this.handle_open.bind(this);
|
||||
this.socket.onmessage = this.handle_message.bind(this);
|
||||
this.socket.onclose = this.handle_close.bind(this);
|
||||
this.socket.onerror = this.handle_error.bind(this);
|
||||
};
|
||||
|
||||
Virtio9pProxy.prototype.send = function(data)
|
||||
{
|
||||
//console.log("send", data);
|
||||
|
||||
if(!this.socket || this.socket.readyState !== 1)
|
||||
{
|
||||
this.send_queue.push(data);
|
||||
|
||||
if(this.send_queue.length > 2 * this.send_queue_limit)
|
||||
{
|
||||
this.send_queue = this.send_queue.slice(-this.send_queue_limit);
|
||||
}
|
||||
|
||||
this.connect();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.socket.send(data);
|
||||
}
|
||||
};
|
||||
|
||||
Virtio9pProxy.prototype.change_proxy = function(url)
|
||||
{
|
||||
this.url = url;
|
||||
|
||||
if(this.socket)
|
||||
{
|
||||
this.socket.onclose = function() {};
|
||||
this.socket.onerror = function() {};
|
||||
this.socket.close();
|
||||
this.socket = undefined;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,16 +3,25 @@
|
|||
// -------------------------------------------------
|
||||
// Implementation of a unix filesystem in memory.
|
||||
|
||||
"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 * as marshall from "../lib/marshall.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,12 +33,13 @@ 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 texten = new TextEncoder();
|
||||
|
||||
/** @const */ var JSONFS_VERSION = 3;
|
||||
|
||||
|
|
@ -49,10 +59,9 @@ 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.<!Inode>} */
|
||||
this.inodes = [];
|
||||
this.events = [];
|
||||
|
||||
this.storage = storage;
|
||||
|
||||
|
|
@ -129,47 +138,13 @@ FS.prototype.set_state = function(state)
|
|||
|
||||
// -----------------------------------------------------
|
||||
|
||||
FS.prototype.AddEvent = function(id, OnEvent) {
|
||||
var inode = this.inodes[id];
|
||||
if(inode.status === STATUS_OK || inode.status === STATUS_ON_STORAGE) {
|
||||
OnEvent();
|
||||
}
|
||||
else if(this.is_forwarder(inode))
|
||||
{
|
||||
this.follow_fs(inode).AddEvent(inode.foreign_id, OnEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.events.push({id: id, OnEvent: OnEvent});
|
||||
}
|
||||
};
|
||||
|
||||
FS.prototype.HandleEvent = function(id) {
|
||||
const inode = this.inodes[id];
|
||||
if(this.is_forwarder(inode))
|
||||
{
|
||||
this.follow_fs(inode).HandleEvent(inode.foreign_id);
|
||||
}
|
||||
//message.Debug("number of events: " + this.events.length);
|
||||
var newevents = [];
|
||||
for(var i=0; i<this.events.length; i++) {
|
||||
if(this.events[i].id === id) {
|
||||
this.events[i].OnEvent();
|
||||
} else {
|
||||
newevents.push(this.events[i]);
|
||||
}
|
||||
}
|
||||
this.events = newevents;
|
||||
};
|
||||
|
||||
FS.prototype.load_from_json = function(fs)
|
||||
{
|
||||
dbg_assert(fs, "Invalid fs passed to load_from_json");
|
||||
|
||||
if(fs["version"] !== JSONFS_VERSION)
|
||||
{
|
||||
throw "The filesystem JSON format has changed. " +
|
||||
"Please update your fs2json (https://github.com/copy/fs2json) and recreate the filesystem JSON.";
|
||||
throw "The filesystem JSON format has changed. Please recreate the filesystem JSON.";
|
||||
}
|
||||
|
||||
var fsroot = fs["fsroot"];
|
||||
|
|
@ -223,7 +198,7 @@ FS.prototype.LoadRecursive = function(data, parentid)
|
|||
}
|
||||
else
|
||||
{
|
||||
dbg_log("Unexpected ifmt: " + h(ifmt) + " (" + name + ")");
|
||||
dbg_log("Unexpected ifmt: " + h(ifmt) + " (" + name + ")", LOG_9P);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -339,9 +314,7 @@ FS.prototype.PushInode = function(inode, parentid, name) {
|
|||
}
|
||||
}
|
||||
|
||||
message.Debug("Error in Filesystem: Pushed inode with name = "+ name + " has no parent");
|
||||
message.Abort();
|
||||
|
||||
dbg_assert(false, "Error in Filesystem: Pushed inode with name = "+ name + " has no parent");
|
||||
};
|
||||
|
||||
/** @constructor */
|
||||
|
|
@ -688,11 +661,11 @@ FS.prototype.CreateBinaryFile = async function(filename, parentid, buffer) {
|
|||
};
|
||||
|
||||
|
||||
FS.prototype.OpenInode = function(id, mode) {
|
||||
FS.prototype.OpenInode = async function(id, mode) {
|
||||
var inode = this.inodes[id];
|
||||
if(this.is_forwarder(inode))
|
||||
{
|
||||
return this.follow_fs(inode).OpenInode(inode.foreign_id, mode);
|
||||
return await this.follow_fs(inode).OpenInode(inode.foreign_id, mode);
|
||||
}
|
||||
if((inode.mode&S_IFMT) === S_IFDIR) {
|
||||
this.FillDirectory(id);
|
||||
|
|
@ -706,12 +679,11 @@ FS.prototype.OpenInode = function(id, mode) {
|
|||
case S_IFCHR: type = "Character Device"; break;
|
||||
}
|
||||
*/
|
||||
//message.Debug("open:" + this.GetFullPath(id) + " type: " + inode.mode + " status:" + inode.status);
|
||||
return true;
|
||||
//dbg_log("open:" + this.GetFullPath(id) + " type: " + inode.mode + " status:" + inode.status, LOG_9P);
|
||||
};
|
||||
|
||||
FS.prototype.CloseInode = async function(id) {
|
||||
//message.Debug("close: " + this.GetFullPath(id));
|
||||
//dbg_log("close: " + this.GetFullPath(id), LOG_9P);
|
||||
var inode = this.inodes[id];
|
||||
if(this.is_forwarder(inode))
|
||||
{
|
||||
|
|
@ -722,7 +694,7 @@ FS.prototype.CloseInode = async function(id) {
|
|||
this.storage.uncache(inode.sha256sum);
|
||||
}
|
||||
if(inode.status === STATUS_UNLINKED) {
|
||||
//message.Debug("Filesystem: Delete unlinked file");
|
||||
//dbg_log("Filesystem: Delete unlinked file", LOG_9P);
|
||||
inode.status = STATUS_INVALID;
|
||||
await this.DeleteData(id);
|
||||
}
|
||||
|
|
@ -732,7 +704,7 @@ FS.prototype.CloseInode = async function(id) {
|
|||
* @return {!Promise<number>} 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;
|
||||
}
|
||||
|
|
@ -1036,7 +1008,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))
|
||||
|
|
@ -1096,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
|
||||
{
|
||||
|
|
@ -1123,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
|
||||
{
|
||||
|
|
@ -1169,7 +1141,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;
|
||||
|
|
@ -1296,24 +1268,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
107
lib/jor1k.js
107
lib/jor1k.js
|
|
@ -1,107 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
// 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. */
|
||||
|
||||
|
||||
function hex8(n)
|
||||
{
|
||||
return h(n);
|
||||
}
|
||||
|
||||
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);
|
||||
require("fs")["readFile"](url, function(err, data)
|
||||
{
|
||||
if(err)
|
||||
{
|
||||
OnError(err);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnSuccess(data.buffer);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
@ -3,15 +3,13 @@
|
|||
// -------------------------------------------------
|
||||
// helper functions for virtio and 9p.
|
||||
|
||||
"use strict";
|
||||
|
||||
var marshall = {};
|
||||
import { dbg_log } from "./../src/log.js";
|
||||
|
||||
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++) {
|
||||
|
|
@ -61,21 +59,21 @@ 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;
|
||||
default:
|
||||
message.Debug("Marshall: Unknown type=" + typelist[i]);
|
||||
dbg_log("Marshall: Unknown type=" + typelist[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
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++) {
|
||||
|
|
@ -112,7 +110,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],
|
||||
|
|
@ -121,10 +119,10 @@ marshall.Unmarshall = function(typelist, struct, state) {
|
|||
});
|
||||
break;
|
||||
default:
|
||||
message.Debug("Error in Unmarshall: Unknown type=" + typelist[i]);
|
||||
dbg_log("Error in Unmarshall: Unknown type=" + typelist[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
state.offset = offset;
|
||||
return output;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
13
manifest.json
Normal file
13
manifest.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "v86",
|
||||
"short_name": "v86",
|
||||
"start_url": "/v86/",
|
||||
"display": "standalone",
|
||||
"icons": [
|
||||
{
|
||||
"src": "192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
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;
|
||||
|
||||
|
|
@ -7,11 +7,14 @@
|
|||
"files": [
|
||||
"Readme.md",
|
||||
"LICENSE",
|
||||
"v86.d.ts",
|
||||
"build/libv86*.js",
|
||||
"build/libv86*.js.map",
|
||||
"build/*.mjs",
|
||||
"build/v86*.wasm"
|
||||
],
|
||||
"main": "build/libv86.js",
|
||||
"repository": "github:copy/v86"
|
||||
"main": "build/libv86.mjs",
|
||||
"types": "v86.d.ts",
|
||||
"repository": "github:copy/v86",
|
||||
"type": "module"
|
||||
}
|
||||
|
|
|
|||
21
src/acpi.js
21
src/acpi.js
|
|
@ -1,15 +1,20 @@
|
|||
"use strict";
|
||||
|
||||
// http://www.uefi.org/sites/default/files/resources/ACPI_6_1.pdf
|
||||
|
||||
/** @const */
|
||||
var PMTIMER_FREQ_SECONDS = 3579545;
|
||||
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 PMTIMER_FREQ_SECONDS = 3579545;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {CPU} cpu
|
||||
*/
|
||||
function ACPI(cpu)
|
||||
export function ACPI(cpu)
|
||||
{
|
||||
/** @type {CPU} */
|
||||
this.cpu = cpu;
|
||||
|
|
@ -64,7 +69,11 @@ function ACPI(cpu)
|
|||
});
|
||||
|
||||
// ACPI status
|
||||
io.register_read(0xB004, this, undefined, function()
|
||||
io.register_read(0xB004, this, function()
|
||||
{
|
||||
dbg_log("ACPI status read8", LOG_ACPI);
|
||||
return this.status & 0xFF;
|
||||
}, function()
|
||||
{
|
||||
dbg_log("ACPI status read", LOG_ACPI);
|
||||
return this.status;
|
||||
|
|
|
|||
648
src/apic.js
648
src/apic.js
|
|
@ -1,648 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
// See Intel's System Programming Guide
|
||||
|
||||
|
||||
/** @const */
|
||||
var APIC_LOG_VERBOSE = false;
|
||||
|
||||
/** @const */
|
||||
var APIC_ADDRESS = 0xFEE00000;
|
||||
|
||||
/** @const */
|
||||
var APIC_TIMER_MODE_MASK = 3 << 17;
|
||||
|
||||
/** @const */
|
||||
var APIC_TIMER_MODE_ONE_SHOT = 0;
|
||||
|
||||
/** @const */
|
||||
var APIC_TIMER_MODE_PERIODIC = 1 << 17;
|
||||
|
||||
/** @const */
|
||||
var APIC_TIMER_MODE_TSC = 2 << 17;
|
||||
|
||||
|
||||
/** @const */
|
||||
var DELIVERY_MODES = [
|
||||
"Fixed (0)",
|
||||
"Lowest Prio (1)",
|
||||
"SMI (2)",
|
||||
"Reserved (3)",
|
||||
"NMI (4)",
|
||||
"INIT (5)",
|
||||
"Reserved (6)",
|
||||
"ExtINT (7)",
|
||||
];
|
||||
|
||||
/** @const */
|
||||
var DESTINATION_MODES = ["physical", "logical"];
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {CPU} cpu
|
||||
*/
|
||||
function APIC(cpu)
|
||||
{
|
||||
/** @type {CPU} */
|
||||
this.cpu = cpu;
|
||||
|
||||
this.apic_id = 0;
|
||||
|
||||
this.timer_divider = 0;
|
||||
this.timer_divider_shift = 1;
|
||||
this.timer_initial_count = 0;
|
||||
this.timer_current_count = 0;
|
||||
|
||||
this.next_tick = v86.microtick();
|
||||
|
||||
this.lvt_timer = IOAPIC_CONFIG_MASKED;
|
||||
this.lvt_thermal_sensor = IOAPIC_CONFIG_MASKED;
|
||||
this.lvt_perf_counter = IOAPIC_CONFIG_MASKED;
|
||||
this.lvt_int0 = IOAPIC_CONFIG_MASKED;
|
||||
this.lvt_int1 = IOAPIC_CONFIG_MASKED;
|
||||
this.lvt_error = IOAPIC_CONFIG_MASKED;
|
||||
|
||||
this.tpr = 0;
|
||||
this.icr0 = 0;
|
||||
this.icr1 = 0;
|
||||
|
||||
this.irr = new Int32Array(8);
|
||||
this.isr = new Int32Array(8);
|
||||
this.tmr = new Int32Array(8);
|
||||
|
||||
this.spurious_vector = 0xFE;
|
||||
this.destination_format = -1;
|
||||
this.local_destination = 0;
|
||||
|
||||
this.error = 0;
|
||||
this.read_error = 0;
|
||||
|
||||
cpu.io.mmap_register(APIC_ADDRESS, 0x100000,
|
||||
(addr) =>
|
||||
{
|
||||
dbg_log("Unsupported read8 from apic: " + h(addr >>> 0), LOG_APIC);
|
||||
var off = addr & 3;
|
||||
addr &= ~3;
|
||||
return this.read32(addr) >> (off * 8) & 0xFF;
|
||||
},
|
||||
(addr, value) =>
|
||||
{
|
||||
dbg_log("Unsupported write8 from apic: " + h(addr) + " <- " + h(value), LOG_APIC);
|
||||
dbg_trace();
|
||||
dbg_assert(false);
|
||||
},
|
||||
(addr) => this.read32(addr),
|
||||
(addr, value) => this.write32(addr, value)
|
||||
);
|
||||
}
|
||||
|
||||
APIC.prototype.read32 = function(addr)
|
||||
{
|
||||
addr = addr - APIC_ADDRESS | 0;
|
||||
|
||||
switch(addr)
|
||||
{
|
||||
case 0x20:
|
||||
dbg_log("APIC read id", LOG_APIC);
|
||||
return this.apic_id;
|
||||
|
||||
case 0x30:
|
||||
// version
|
||||
dbg_log("APIC read version", LOG_APIC);
|
||||
return 0x50014;
|
||||
|
||||
case 0x80:
|
||||
APIC_LOG_VERBOSE && dbg_log("APIC read tpr", LOG_APIC);
|
||||
return this.tpr;
|
||||
|
||||
case 0xD0:
|
||||
dbg_log("Read local destination", LOG_APIC);
|
||||
return this.local_destination;
|
||||
|
||||
case 0xE0:
|
||||
dbg_log("Read destination format", LOG_APIC);
|
||||
return this.destination_format;
|
||||
|
||||
case 0xF0:
|
||||
return this.spurious_vector;
|
||||
|
||||
case 0x100:
|
||||
case 0x110:
|
||||
case 0x120:
|
||||
case 0x130:
|
||||
case 0x140:
|
||||
case 0x150:
|
||||
case 0x160:
|
||||
case 0x170:
|
||||
var index = addr - 0x100 >> 4;
|
||||
dbg_log("Read isr " + index + ": " + h(this.isr[index] >>> 0, 8), LOG_APIC);
|
||||
return this.isr[index];
|
||||
|
||||
case 0x180:
|
||||
case 0x190:
|
||||
case 0x1A0:
|
||||
case 0x1B0:
|
||||
case 0x1C0:
|
||||
case 0x1D0:
|
||||
case 0x1E0:
|
||||
case 0x1F0:
|
||||
var index = addr - 0x180 >> 4;
|
||||
dbg_log("Read tmr " + index + ": " + h(this.tmr[index] >>> 0, 8), LOG_APIC);
|
||||
return this.tmr[index];
|
||||
|
||||
case 0x200:
|
||||
case 0x210:
|
||||
case 0x220:
|
||||
case 0x230:
|
||||
case 0x240:
|
||||
case 0x250:
|
||||
case 0x260:
|
||||
case 0x270:
|
||||
var index = addr - 0x200 >> 4;
|
||||
dbg_log("Read irr " + index + ": " + h(this.irr[index] >>> 0, 8), LOG_APIC);
|
||||
return this.irr[index];
|
||||
|
||||
case 0x280:
|
||||
dbg_log("Read error: " + h(this.read_error >>> 0, 8), LOG_APIC);
|
||||
return this.read_error;
|
||||
|
||||
case 0x300:
|
||||
APIC_LOG_VERBOSE && dbg_log("APIC read icr0", LOG_APIC);
|
||||
return this.icr0;
|
||||
|
||||
case 0x310:
|
||||
dbg_log("APIC read icr1", LOG_APIC);
|
||||
return this.icr1;
|
||||
|
||||
case 0x320:
|
||||
dbg_log("read timer lvt", LOG_APIC);
|
||||
return this.lvt_timer;
|
||||
|
||||
case 0x330:
|
||||
dbg_log("read lvt thermal sensor", LOG_APIC);
|
||||
return this.lvt_thermal_sensor;
|
||||
|
||||
case 0x340:
|
||||
dbg_log("read lvt perf counter", LOG_APIC);
|
||||
return this.lvt_perf_counter;
|
||||
|
||||
case 0x350:
|
||||
dbg_log("read lvt int0", LOG_APIC);
|
||||
return this.lvt_int0;
|
||||
|
||||
case 0x360:
|
||||
dbg_log("read lvt int1", LOG_APIC);
|
||||
return this.lvt_int1;
|
||||
|
||||
case 0x370:
|
||||
dbg_log("read lvt error", LOG_APIC);
|
||||
return this.lvt_error;
|
||||
|
||||
case 0x3E0:
|
||||
// divider
|
||||
dbg_log("read timer divider", LOG_APIC);
|
||||
return this.timer_divider;
|
||||
|
||||
case 0x380:
|
||||
dbg_log("read timer initial count", LOG_APIC);
|
||||
return this.timer_initial_count;
|
||||
|
||||
case 0x390:
|
||||
dbg_log("read timer current count: " + h(this.timer_current_count >>> 0, 8), LOG_APIC);
|
||||
return this.timer_current_count;
|
||||
|
||||
default:
|
||||
dbg_log("APIC read " + h(addr), LOG_APIC);
|
||||
dbg_assert(false);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
APIC.prototype.write32 = function(addr, value)
|
||||
{
|
||||
addr = addr - APIC_ADDRESS | 0;
|
||||
|
||||
switch(addr)
|
||||
{
|
||||
case 0x20:
|
||||
dbg_log("APIC write id: " + h(value >>> 8, 8), LOG_APIC);
|
||||
this.apic_id = value;
|
||||
break;
|
||||
|
||||
case 0x30:
|
||||
// version
|
||||
dbg_log("APIC write version: " + h(value >>> 0, 8) + ", ignored", LOG_APIC);
|
||||
break;
|
||||
|
||||
case 0x80:
|
||||
APIC_LOG_VERBOSE && dbg_log("Set tpr: " + h(value & 0xFF, 2), LOG_APIC);
|
||||
this.tpr = value & 0xFF;
|
||||
this.check_vector();
|
||||
break;
|
||||
|
||||
case 0xB0:
|
||||
var highest_isr = this.highest_isr();
|
||||
if(highest_isr !== -1)
|
||||
{
|
||||
APIC_LOG_VERBOSE && dbg_log("eoi: " + h(value >>> 0, 8) + " for vector " + h(highest_isr), LOG_APIC);
|
||||
this.register_clear_bit(this.isr, highest_isr);
|
||||
if(this.register_get_bit(this.tmr, highest_isr))
|
||||
{
|
||||
// Send eoi to all IO APICs
|
||||
this.cpu.devices.ioapic.remote_eoi(highest_isr);
|
||||
}
|
||||
this.check_vector();
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_log("Bad eoi: No isr set", LOG_APIC);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xD0:
|
||||
dbg_log("Set local destination: " + h(value >>> 0, 8), LOG_APIC);
|
||||
this.local_destination = value & 0xFF000000;
|
||||
break;
|
||||
|
||||
case 0xE0:
|
||||
dbg_log("Set destination format: " + h(value >>> 0, 8), LOG_APIC);
|
||||
this.destination_format = value | 0xFFFFFF;
|
||||
break;
|
||||
|
||||
case 0xF0:
|
||||
dbg_log("Set spurious vector: " + h(value >>> 0, 8), LOG_APIC);
|
||||
this.spurious_vector = value;
|
||||
break;
|
||||
|
||||
case 0x280:
|
||||
// updated readable error register with real error
|
||||
dbg_log("Write error: " + h(value >>> 0, 8), LOG_APIC);
|
||||
this.read_error = this.error;
|
||||
this.error = 0;
|
||||
break;
|
||||
|
||||
case 0x300:
|
||||
var vector = value & 0xFF;
|
||||
var delivery_mode = value >> 8 & 7;
|
||||
var destination_mode = value >> 11 & 1;
|
||||
var is_level = value >> 15 & 1;
|
||||
var destination_shorthand = value >> 18 & 3;
|
||||
var destination = this.icr1 >>> 24;
|
||||
dbg_log("APIC write icr0: " + h(value, 8) + " vector=" + h(vector, 2) + " " +
|
||||
"destination_mode=" + DESTINATION_MODES[destination_mode] + " delivery_mode=" + DELIVERY_MODES[delivery_mode] + " " +
|
||||
"destination_shorthand=" + ["no", "self", "all with self", "all without self"][destination_shorthand], LOG_APIC);
|
||||
|
||||
value &= ~(1 << 12);
|
||||
this.icr0 = value;
|
||||
|
||||
if(destination_shorthand === 0)
|
||||
{
|
||||
// no shorthand
|
||||
this.route(vector, delivery_mode, is_level, destination, destination_mode);
|
||||
}
|
||||
else if(destination_shorthand === 1)
|
||||
{
|
||||
// self
|
||||
this.deliver(vector, IOAPIC_DELIVERY_FIXED, is_level);
|
||||
}
|
||||
else if(destination_shorthand === 2)
|
||||
{
|
||||
// all including self
|
||||
this.deliver(vector, delivery_mode, is_level);
|
||||
}
|
||||
else if(destination_shorthand === 3)
|
||||
{
|
||||
// all but self
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_assert(false);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x310:
|
||||
dbg_log("APIC write icr1: " + h(value >>> 0, 8), LOG_APIC);
|
||||
this.icr1 = value;
|
||||
break;
|
||||
|
||||
case 0x320:
|
||||
dbg_log("timer lvt: " + h(value >>> 0, 8), LOG_APIC);
|
||||
this.lvt_timer = value;
|
||||
break;
|
||||
|
||||
case 0x330:
|
||||
dbg_log("lvt thermal sensor: " + h(value >>> 0, 8), LOG_APIC);
|
||||
this.lvt_thermal_sensor = value;
|
||||
break;
|
||||
|
||||
case 0x340:
|
||||
dbg_log("lvt perf counter: " + h(value >>> 0, 8), LOG_APIC);
|
||||
this.lvt_perf_counter = value;
|
||||
break;
|
||||
|
||||
case 0x350:
|
||||
dbg_log("lvt int0: " + h(value >>> 0, 8), LOG_APIC);
|
||||
this.lvt_int0 = value;
|
||||
break;
|
||||
|
||||
case 0x360:
|
||||
dbg_log("lvt int1: " + h(value >>> 0, 8), LOG_APIC);
|
||||
this.lvt_int1 = value;
|
||||
break;
|
||||
|
||||
case 0x370:
|
||||
dbg_log("lvt error: " + h(value >>> 0, 8), LOG_APIC);
|
||||
this.lvt_error = value;
|
||||
break;
|
||||
|
||||
case 0x3E0:
|
||||
dbg_log("timer divider: " + h(value >>> 0, 8), LOG_APIC);
|
||||
this.timer_divider = value;
|
||||
|
||||
var divide_shift = value & 0b11 | (value & 0b1000) >> 1;
|
||||
this.timer_divider_shift = divide_shift === 0b111 ? 0 : divide_shift + 1;
|
||||
break;
|
||||
|
||||
case 0x380:
|
||||
dbg_log("timer initial: " + h(value >>> 0, 8), LOG_APIC);
|
||||
this.timer_initial_count = value >>> 0;
|
||||
this.timer_current_count = value >>> 0;
|
||||
|
||||
this.next_tick = v86.microtick();
|
||||
this.timer_active = true;
|
||||
break;
|
||||
|
||||
case 0x390:
|
||||
dbg_log("timer current: " + h(value >>> 0, 8), LOG_APIC);
|
||||
dbg_assert(false, "read-only register");
|
||||
break;
|
||||
|
||||
default:
|
||||
dbg_log("APIC write32 " + h(addr) + " <- " + h(value >>> 0, 8), LOG_APIC);
|
||||
dbg_assert(false);
|
||||
}
|
||||
};
|
||||
|
||||
APIC.prototype.timer = function(now)
|
||||
{
|
||||
if(this.timer_current_count === 0)
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
|
||||
const freq = APIC_TIMER_FREQ / (1 << this.timer_divider_shift);
|
||||
|
||||
const steps = (now - this.next_tick) * freq >>> 0;
|
||||
|
||||
this.next_tick += steps / freq;
|
||||
this.timer_current_count -= steps;
|
||||
|
||||
if(this.timer_current_count <= 0)
|
||||
{
|
||||
var mode = this.lvt_timer & APIC_TIMER_MODE_MASK;
|
||||
|
||||
if(mode === APIC_TIMER_MODE_PERIODIC)
|
||||
{
|
||||
this.timer_current_count = this.timer_current_count % this.timer_initial_count;
|
||||
|
||||
if(this.timer_current_count <= 0)
|
||||
{
|
||||
this.timer_current_count += this.timer_initial_count;
|
||||
}
|
||||
dbg_assert(this.timer_current_count !== 0);
|
||||
|
||||
if((this.lvt_timer & IOAPIC_CONFIG_MASKED) === 0)
|
||||
{
|
||||
this.deliver(this.lvt_timer & 0xFF, IOAPIC_DELIVERY_FIXED, false);
|
||||
}
|
||||
}
|
||||
else if(mode === APIC_TIMER_MODE_ONE_SHOT)
|
||||
{
|
||||
this.timer_current_count = 0;
|
||||
dbg_log("APIC timer one shot end", LOG_APIC);
|
||||
|
||||
if((this.lvt_timer & IOAPIC_CONFIG_MASKED) === 0)
|
||||
{
|
||||
this.deliver(this.lvt_timer & 0xFF, IOAPIC_DELIVERY_FIXED, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Math.max(0, this.timer_current_count / freq);
|
||||
};
|
||||
|
||||
APIC.prototype.route = function(vector, mode, is_level, destination, destination_mode)
|
||||
{
|
||||
// TODO
|
||||
this.deliver(vector, mode, is_level);
|
||||
};
|
||||
|
||||
APIC.prototype.deliver = function(vector, mode, is_level)
|
||||
{
|
||||
APIC_LOG_VERBOSE && dbg_log("Deliver " + h(vector, 2) + " mode=" + mode + " level=" + is_level, LOG_APIC);
|
||||
|
||||
if(mode === IOAPIC_DELIVERY_INIT)
|
||||
{
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
|
||||
if(mode === IOAPIC_DELIVERY_NMI)
|
||||
{
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
|
||||
if(vector < 0x10 || vector === 0xFF)
|
||||
{
|
||||
dbg_assert(false, "TODO: Invalid vector");
|
||||
}
|
||||
|
||||
if(this.register_get_bit(this.irr, vector))
|
||||
{
|
||||
dbg_log("Not delivered: irr already set, vector=" + h(vector, 2), LOG_APIC);
|
||||
return;
|
||||
}
|
||||
|
||||
this.register_set_bit(this.irr, vector);
|
||||
|
||||
if(is_level)
|
||||
{
|
||||
this.register_set_bit(this.tmr, vector);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.register_clear_bit(this.tmr, vector);
|
||||
}
|
||||
|
||||
this.check_vector();
|
||||
};
|
||||
|
||||
APIC.prototype.highest_irr = function()
|
||||
{
|
||||
var highest = this.register_get_highest_bit(this.irr);
|
||||
dbg_assert(highest !== 0xFF);
|
||||
dbg_assert(highest >= 0x10 || highest === -1);
|
||||
return highest;
|
||||
};
|
||||
|
||||
APIC.prototype.highest_isr = function()
|
||||
{
|
||||
var highest = this.register_get_highest_bit(this.isr);
|
||||
dbg_assert(highest !== 0xFF);
|
||||
dbg_assert(highest >= 0x10 || highest === -1);
|
||||
return highest;
|
||||
};
|
||||
|
||||
APIC.prototype.check_vector = function()
|
||||
{
|
||||
var highest_irr = this.highest_irr();
|
||||
|
||||
if(highest_irr === -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var highest_isr = this.highest_isr();
|
||||
|
||||
if(highest_isr >= highest_irr)
|
||||
{
|
||||
APIC_LOG_VERBOSE && dbg_log("Higher isr, isr=" + h(highest_isr) + " irr=" + h(highest_irr), LOG_APIC);
|
||||
return;
|
||||
}
|
||||
|
||||
if((highest_irr & 0xF0) <= (this.tpr & 0xF0))
|
||||
{
|
||||
APIC_LOG_VERBOSE && dbg_log("Higher tpr, tpr=" + h(this.tpr & 0xF0) + " irr=" + h(highest_irr), LOG_APIC);
|
||||
return;
|
||||
}
|
||||
|
||||
this.cpu.handle_irqs();
|
||||
};
|
||||
|
||||
APIC.prototype.acknowledge_irq = function()
|
||||
{
|
||||
var highest_irr = this.highest_irr();
|
||||
|
||||
if(highest_irr === -1)
|
||||
{
|
||||
//dbg_log("Spurious", LOG_APIC);
|
||||
return -1;
|
||||
}
|
||||
|
||||
var highest_isr = this.highest_isr();
|
||||
|
||||
if(highest_isr >= highest_irr)
|
||||
{
|
||||
APIC_LOG_VERBOSE && dbg_log("Higher isr, isr=" + h(highest_isr) + " irr=" + h(highest_irr), LOG_APIC);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if((highest_irr & 0xF0) <= (this.tpr & 0xF0))
|
||||
{
|
||||
APIC_LOG_VERBOSE && dbg_log("Higher tpr, tpr=" + h(this.tpr & 0xF0) + " irr=" + h(highest_irr), LOG_APIC);
|
||||
return -1;
|
||||
}
|
||||
|
||||
this.register_clear_bit(this.irr, highest_irr);
|
||||
this.register_set_bit(this.isr, highest_irr);
|
||||
|
||||
APIC_LOG_VERBOSE && dbg_log("Calling vector " + h(highest_irr), LOG_APIC);
|
||||
|
||||
this.check_vector();
|
||||
|
||||
return highest_irr;
|
||||
};
|
||||
|
||||
APIC.prototype.get_state = function()
|
||||
{
|
||||
var state = [];
|
||||
|
||||
state[0] = this.apic_id;
|
||||
state[1] = this.timer_divider;
|
||||
state[2] = this.timer_divider_shift;
|
||||
state[3] = this.timer_initial_count;
|
||||
state[4] = this.timer_current_count;
|
||||
state[5] = this.next_tick;
|
||||
state[6] = this.lvt_timer;
|
||||
state[7] = this.lvt_perf_counter;
|
||||
state[8] = this.lvt_int0;
|
||||
state[9] = this.lvt_int1;
|
||||
state[10] = this.lvt_error;
|
||||
state[11] = this.tpr;
|
||||
state[12] = this.icr0;
|
||||
state[13] = this.icr1;
|
||||
state[14] = this.irr;
|
||||
state[15] = this.isr;
|
||||
state[16] = this.tmr;
|
||||
state[17] = this.spurious_vector;
|
||||
state[18] = this.destination_format;
|
||||
state[19] = this.local_destination;
|
||||
state[20] = this.error;
|
||||
state[21] = this.read_error;
|
||||
state[22] = this.lvt_thermal_sensor;
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
APIC.prototype.set_state = function(state)
|
||||
{
|
||||
this.apic_id = state[0];
|
||||
this.timer_divider = state[1];
|
||||
this.timer_divider_shift = state[2];
|
||||
this.timer_initial_count = state[3];
|
||||
this.timer_current_count = state[4];
|
||||
this.next_tick = state[5];
|
||||
this.lvt_timer = state[6];
|
||||
this.lvt_perf_counter = state[7];
|
||||
this.lvt_int0 = state[8];
|
||||
this.lvt_int1 = state[9];
|
||||
this.lvt_error = state[10];
|
||||
this.tpr = state[11];
|
||||
this.icr0 = state[12];
|
||||
this.icr1 = state[13];
|
||||
this.irr = state[14];
|
||||
this.isr = state[15];
|
||||
this.tmr = state[16];
|
||||
this.spurious_vector = state[17];
|
||||
this.destination_format = state[18];
|
||||
this.local_destination = state[19];
|
||||
this.error = state[20];
|
||||
this.read_error = state[21];
|
||||
this.lvt_thermal_sensor = state[22] || IOAPIC_CONFIG_MASKED;
|
||||
};
|
||||
|
||||
// functions operating on 256-bit registers (for irr, isr, tmr)
|
||||
APIC.prototype.register_get_bit = function(v, bit)
|
||||
{
|
||||
dbg_assert(bit >= 0 && bit < 256);
|
||||
return v[bit >> 5] >> (bit & 31) & 1;
|
||||
};
|
||||
|
||||
APIC.prototype.register_set_bit = function(v, bit)
|
||||
{
|
||||
dbg_assert(bit >= 0 && bit < 256);
|
||||
v[bit >> 5] |= 1 << (bit & 31);
|
||||
};
|
||||
|
||||
APIC.prototype.register_clear_bit = function(v, bit)
|
||||
{
|
||||
dbg_assert(bit >= 0 && bit < 256);
|
||||
v[bit >> 5] &= ~(1 << (bit & 31));
|
||||
};
|
||||
|
||||
APIC.prototype.register_get_highest_bit = function(v)
|
||||
{
|
||||
for(var i = 7; i >= 0; i--)
|
||||
{
|
||||
var word = v[i];
|
||||
|
||||
if(word)
|
||||
{
|
||||
return v86util.int_log2(word >>> 0) | i << 5;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
};
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
"use strict";
|
||||
import { dbg_assert } from "../log.js";
|
||||
import { get_charmap } from "../lib.js";
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {Object=} options
|
||||
*/
|
||||
function DummyScreenAdapter()
|
||||
export function DummyScreenAdapter(options)
|
||||
{
|
||||
var
|
||||
graphic_image_data,
|
||||
|
|
@ -30,7 +32,10 @@ function DummyScreenAdapter()
|
|||
text_mode_width = 0,
|
||||
|
||||
// number of rows
|
||||
text_mode_height = 0;
|
||||
text_mode_height = 0,
|
||||
|
||||
// 8-bit-text to Unicode character map
|
||||
charmap = get_charmap(options?.encoding);
|
||||
|
||||
this.put_char = function(row, col, chr, blinking, bg_color, fg_color)
|
||||
{
|
||||
|
|
@ -112,10 +117,11 @@ function DummyScreenAdapter()
|
|||
return screen;
|
||||
};
|
||||
|
||||
this.get_text_row = function(i)
|
||||
this.get_text_row = function(y)
|
||||
{
|
||||
const offset = i * text_mode_width;
|
||||
return String.fromCharCode.apply(String, text_mode_data.subarray(offset, offset + text_mode_width));
|
||||
const begin = y * text_mode_width;
|
||||
const end = begin + text_mode_width;
|
||||
return Array.from(text_mode_data.subarray(begin, end), chr => charmap[chr]).join("");
|
||||
};
|
||||
|
||||
this.set_size_text(80, 25);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
"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;
|
||||
|
|
@ -23,17 +25,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
|
||||
|
|
@ -43,18 +45,14 @@ 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_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 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";
|
||||
|
|
@ -92,6 +90,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;
|
||||
}
|
||||
|
|
@ -158,15 +157,19 @@ class GrowableRingbuffer
|
|||
}
|
||||
}
|
||||
|
||||
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()
|
||||
|
|
@ -187,6 +190,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.
|
||||
*
|
||||
|
|
@ -219,8 +233,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;
|
||||
}
|
||||
|
|
@ -240,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]) {
|
||||
|
|
@ -445,7 +476,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);
|
||||
|
||||
|
|
@ -946,19 +977,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);
|
||||
}
|
||||
|
|
@ -974,7 +1017,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(".");
|
||||
|
|
@ -988,7 +1031,7 @@ 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;
|
||||
|
|
@ -997,13 +1040,12 @@ 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;
|
||||
}
|
||||
|
||||
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,10 +1056,14 @@ function fake_tcp_probe(dport, adapter) {
|
|||
/**
|
||||
* @constructor
|
||||
*/
|
||||
function TCPConnection()
|
||||
export function TCPConnection(adapter)
|
||||
{
|
||||
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;
|
||||
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;
|
||||
|
|
@ -1080,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;
|
||||
|
|
@ -1096,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();
|
||||
|
|
@ -1116,7 +1160,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;
|
||||
|
|
@ -1124,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});
|
||||
|
|
|
|||
|
|
@ -1,13 +1,25 @@
|
|||
"use strict";
|
||||
import { LOG_FETCH } from "../const.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
|
||||
*
|
||||
* @param {BusConnector} bus
|
||||
* @param {*=} config
|
||||
* @export
|
||||
*/
|
||||
function FetchNetworkAdapter(bus, config)
|
||||
export function FetchNetworkAdapter(bus, config)
|
||||
{
|
||||
config = config || {};
|
||||
this.bus = bus;
|
||||
|
|
@ -20,7 +32,8 @@ 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/?'
|
||||
|
|
@ -33,27 +46,18 @@ 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();
|
||||
conn.state = TCP_STATE_SYN_RECEIVED;
|
||||
conn.net = this;
|
||||
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);
|
||||
|
|
@ -97,13 +101,27 @@ 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"));
|
||||
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;
|
||||
else req_headers.append(header.key, header.value);
|
||||
}
|
||||
|
||||
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) {
|
||||
target.protocol = "http:";
|
||||
target.hostname = "localhost";
|
||||
target.port = localport.toString(10);
|
||||
} else {
|
||||
console.warn('Unknown port for localhost: "%s"', target.href);
|
||||
this.net.respond_text_and_close(this, 400, "Bad Request", `Unknown port for localhost: ${target.href}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
dbg_log("HTTP Dispatch: " + target.href, LOG_FETCH);
|
||||
this.name = target.href;
|
||||
let opts = {
|
||||
|
|
@ -117,19 +135,17 @@ 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) => {
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
this.write(encoder.encode(header_lines.join("\r\n") + "\r\n\r\n"));
|
||||
let handler = (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");
|
||||
|
||||
this.write(this.net.form_response_head(resp.status, resp.statusText, resp_headers));
|
||||
response_started = true;
|
||||
|
||||
if(resp.body && resp.body.getReader) {
|
||||
|
|
@ -152,18 +168,13 @@ 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) {
|
||||
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]);
|
||||
this.net.respond_text_and_close(this, 502, "Fetch Error", `Fetch ${fetch_url} failed:\n\n${e.stack || e.message}`);
|
||||
}
|
||||
this.close();
|
||||
});
|
||||
|
|
@ -183,19 +194,41 @@ 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}`
|
||||
];
|
||||
|
||||
for(const [key, value] of headers.entries()) {
|
||||
lines.push(`${key}: ${value}`);
|
||||
}
|
||||
|
||||
return new TextEncoder().encode(lines.join("\r\n") + "\r\n\r\n");
|
||||
};
|
||||
|
||||
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"
|
||||
});
|
||||
conn.writev([this.form_response_head(status_code, status_text, headers), new TextEncoder().encode(body)]);
|
||||
conn.close();
|
||||
};
|
||||
|
||||
FetchNetworkAdapter.prototype.parse_http_header = function(header)
|
||||
{
|
||||
const parts = header.match(/^([^:]*):(.*)$/);
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
"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.
|
||||
* @param {string} sha256sum
|
||||
* @param {number} offset
|
||||
* @param {number} count
|
||||
* @param {number} file_size
|
||||
* @return {!Promise<Uint8Array>} 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.
|
||||
|
|
@ -31,7 +33,7 @@ FileStorageInterface.prototype.uncache = function(sha256sum) {};
|
|||
* @constructor
|
||||
* @implements {FileStorageInterface}
|
||||
*/
|
||||
function MemoryFileStorage()
|
||||
export function MemoryFileStorage()
|
||||
{
|
||||
/**
|
||||
* From sha256sum to file data.
|
||||
|
|
@ -82,8 +84,9 @@ MemoryFileStorage.prototype.uncache = function(sha256sum)
|
|||
* @implements {FileStorageInterface}
|
||||
* @param {FileStorageInterface} file_storage
|
||||
* @param {string} baseurl
|
||||
* @param {function(number,Uint8Array):ArrayBuffer} zstd_decompress
|
||||
*/
|
||||
function ServerFileStorageWrapper(file_storage, baseurl)
|
||||
export function ServerFileStorageWrapper(file_storage, baseurl, zstd_decompress)
|
||||
{
|
||||
dbg_assert(baseurl, "ServerMemoryFileStorage: baseurl should not be empty");
|
||||
|
||||
|
|
@ -94,19 +97,27 @@ 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<Uint8Array>}
|
||||
*/
|
||||
ServerFileStorageWrapper.prototype.load_from_server = function(sha256sum)
|
||||
ServerFileStorageWrapper.prototype.load_from_server = function(sha256sum, file_size)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}});
|
||||
|
|
@ -117,14 +128,15 @@ ServerFileStorageWrapper.prototype.load_from_server = function(sha256sum)
|
|||
* @param {string} sha256sum
|
||||
* @param {number} offset
|
||||
* @param {number} count
|
||||
* @param {number} file_size
|
||||
* @return {!Promise<Uint8Array>}
|
||||
*/
|
||||
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);
|
||||
const data = await this.storage.read(sha256sum, offset, count, file_size);
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"use strict";
|
||||
// For Types Only
|
||||
import { BusConnector } from "../bus.js";
|
||||
|
||||
/**
|
||||
* Network adapter "inbrowser" which connects the emulated NIC
|
||||
|
|
@ -16,7 +17,7 @@
|
|||
* @param {BusConnector} bus
|
||||
* @param {*=} config
|
||||
*/
|
||||
function InBrowserNetworkAdapter(bus, config)
|
||||
export function InBrowserNetworkAdapter(bus, config)
|
||||
{
|
||||
const id = config.id || 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
"use strict";
|
||||
// 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;
|
||||
|
||||
/**
|
||||
|
|
@ -14,7 +11,7 @@ const PLATFOM_WINDOWS = typeof window !== "undefined" && window.navigator.platfo
|
|||
*
|
||||
* @param {BusConnector} bus
|
||||
*/
|
||||
function KeyboardAdapter(bus)
|
||||
export function KeyboardAdapter(bus)
|
||||
{
|
||||
var
|
||||
/**
|
||||
|
|
@ -48,12 +45,9 @@ 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,
|
||||
|
|
@ -128,12 +122,9 @@ 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
|
||||
|
|
@ -246,7 +237,9 @@ function KeyboardAdapter(bus)
|
|||
"Insert": 0xe052,
|
||||
"Delete": 0xe053,
|
||||
|
||||
"MetaLeft": 0xe05b,
|
||||
"OSLeft": 0xe05b,
|
||||
"MetaRight": 0xe05c,
|
||||
"OSRight": 0xe05c,
|
||||
"ContextMenu": 0xe05d,
|
||||
};
|
||||
|
|
@ -260,6 +253,7 @@ function KeyboardAdapter(bus)
|
|||
window.removeEventListener("keyup", keyup_handler, false);
|
||||
window.removeEventListener("keydown", keydown_handler, false);
|
||||
window.removeEventListener("blur", blur_handler, false);
|
||||
window.removeEventListener("input", input_handler, false);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -274,6 +268,7 @@ function KeyboardAdapter(bus)
|
|||
window.addEventListener("keyup", keyup_handler, false);
|
||||
window.addEventListener("keydown", keydown_handler, false);
|
||||
window.addEventListener("blur", blur_handler, false);
|
||||
window.addEventListener("input", input_handler, false);
|
||||
};
|
||||
this.init();
|
||||
|
||||
|
|
@ -386,6 +381,37 @@ function KeyboardAdapter(bus)
|
|||
keys_pressed = {};
|
||||
}
|
||||
|
||||
function input_handler(e)
|
||||
{
|
||||
if(!keyboard.bus)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!may_handle(e))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch(e.inputType)
|
||||
{
|
||||
case "insertText":
|
||||
for(var i = 0; i < e.data.length; i++)
|
||||
{
|
||||
keyboard.simulate_char(e.data[i]);
|
||||
}
|
||||
break;
|
||||
|
||||
case "insertLineBreak":
|
||||
keyboard.simulate_press(13); // enter
|
||||
break;
|
||||
|
||||
case "deleteContentBackward":
|
||||
keyboard.simulate_press(8); // backspace
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {KeyboardEvent|Object} e
|
||||
* @param {boolean} keydown
|
||||
|
|
@ -402,6 +428,12 @@ function KeyboardAdapter(bus)
|
|||
return;
|
||||
}
|
||||
|
||||
if(e.code === "" || e.key === "Process" || e.key === "Unidentified" || e.keyCode === 229)
|
||||
{
|
||||
// Handling mobile browsers and virtual keyboards
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault && e.preventDefault();
|
||||
|
||||
if(PLATFOM_WINDOWS)
|
||||
|
|
|
|||
5821
src/browser/main.js
5821
src/browser/main.js
File diff suppressed because it is too large
Load diff
|
|
@ -1,14 +1,16 @@
|
|||
"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;
|
||||
const SPEED_FACTOR = 1;
|
||||
|
||||
var left_down = false,
|
||||
right_down = false,
|
||||
|
|
@ -218,6 +220,9 @@ 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)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"use strict";
|
||||
// For Types Only
|
||||
import { BusConnector } from "../bus.js";
|
||||
|
||||
/**
|
||||
* An ethernet-through-websocket adapter, to be used with
|
||||
|
|
@ -12,7 +13,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;
|
||||
|
|
|
|||
|
|
@ -1,287 +1,281 @@
|
|||
"use strict";
|
||||
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") + ":" + v86util.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") + ":" + v86util.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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
"use strict";
|
||||
import { dbg_assert } from "../log.js";
|
||||
import { get_charmap } from "../lib.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
|
||||
* @param {Object} options
|
||||
* @param {function()} screen_fill_buffer
|
||||
*/
|
||||
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;
|
||||
|
|
@ -92,9 +97,8 @@ function ScreenAdapter(options, screen_fill_buffer)
|
|||
cursor_end,
|
||||
cursor_enabled,
|
||||
|
||||
// 8-bit Unicode character maps
|
||||
charmap_default = [],
|
||||
charmap = charmap_default,
|
||||
// 8-bit-text to Unicode character map
|
||||
charmap = get_charmap(options.encoding),
|
||||
|
||||
// render loop state
|
||||
timer_id = 0,
|
||||
|
|
@ -314,52 +318,6 @@ function ScreenAdapter(options, screen_fill_buffer)
|
|||
|
||||
this.init = function()
|
||||
{
|
||||
// map 8-bit DOS codepage 437 character range 0-31 to 16-bit Unicode codepoints
|
||||
const charmap_low = new Uint16Array([
|
||||
0x20, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
|
||||
0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
|
||||
0x25BA, 0x25C4, 0x2195, 0x203C, 0xB6, 0xA7, 0x25AC, 0x21A8,
|
||||
0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC
|
||||
]);
|
||||
// map 8-bit DOS codepage 437 character range 127-255 to 16-bit Unicode codepoints
|
||||
const charmap_high = new Uint16Array([
|
||||
0x2302,
|
||||
0xC7, 0xFC, 0xE9, 0xE2, 0xE4, 0xE0, 0xE5, 0xE7,
|
||||
0xEA, 0xEB, 0xE8, 0xEF, 0xEE, 0xEC, 0xC4, 0xC5,
|
||||
0xC9, 0xE6, 0xC6, 0xF4, 0xF6, 0xF2, 0xFB, 0xF9,
|
||||
0xFF, 0xD6, 0xDC, 0xA2, 0xA3, 0xA5, 0x20A7, 0x192,
|
||||
0xE1, 0xED, 0xF3, 0xFA, 0xF1, 0xD1, 0xAA, 0xBA,
|
||||
0xBF, 0x2310, 0xAC, 0xBD, 0xBC, 0xA1, 0xAB, 0xBB,
|
||||
0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
|
||||
0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
|
||||
0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
|
||||
0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
|
||||
0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
|
||||
0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
|
||||
0x3B1, 0xDF, 0x393, 0x3C0, 0x3A3, 0x3C3, 0xB5, 0x3C4,
|
||||
0x3A6, 0x398, 0x3A9, 0x3B4, 0x221E, 0x3C6, 0x3B5, 0x2229,
|
||||
0x2261, 0xB1, 0x2265, 0x2264, 0x2320, 0x2321, 0xF7,
|
||||
0x2248, 0xB0, 0x2219, 0xB7, 0x221A, 0x207F, 0xB2, 0x25A0, 0xA0
|
||||
]);
|
||||
|
||||
// initialize 8-bit DOS codepage 437 map charmap[256] (Uint8 -> String[1])
|
||||
for(var i = 0, chr; i < 256; i++)
|
||||
{
|
||||
if(i > 126)
|
||||
{
|
||||
chr = charmap_high[i - 0x7F];
|
||||
}
|
||||
else if(i < 32)
|
||||
{
|
||||
chr = charmap_low[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
chr = i;
|
||||
}
|
||||
charmap_default.push(String.fromCharCode(chr));
|
||||
}
|
||||
|
||||
// setup text mode cursor DOM element
|
||||
cursor_element.classList.add("cursor");
|
||||
cursor_element.style.position = "absolute";
|
||||
|
|
@ -713,11 +671,6 @@ function ScreenAdapter(options, screen_fill_buffer)
|
|||
update_scale_graphic();
|
||||
};
|
||||
|
||||
this.set_charmap = function(text_charmap)
|
||||
{
|
||||
charmap = text_charmap || charmap_default;
|
||||
};
|
||||
|
||||
this.set_scale = function(s_x, s_y)
|
||||
{
|
||||
scale_x = s_x;
|
||||
|
|
@ -885,10 +838,10 @@ function ScreenAdapter(options, screen_fill_buffer)
|
|||
text_mode_data[offset + BG_COLOR_INDEX] === bg_color &&
|
||||
text_mode_data[offset + FG_COLOR_INDEX] === fg_color)
|
||||
{
|
||||
var ascii = text_mode_data[offset + CHARACTER_INDEX];
|
||||
const chr = charmap[text_mode_data[offset + CHARACTER_INDEX]];
|
||||
|
||||
text += charmap[ascii];
|
||||
dbg_assert(charmap[ascii]);
|
||||
text += chr;
|
||||
dbg_assert(chr);
|
||||
|
||||
i++;
|
||||
offset += TEXT_BUF_COMPONENT_SIZE;
|
||||
|
|
@ -968,16 +921,14 @@ function ScreenAdapter(options, screen_fill_buffer)
|
|||
|
||||
this.get_text_row = function(y)
|
||||
{
|
||||
let result = "";
|
||||
|
||||
for(let x = 0; x < text_mode_width; x++)
|
||||
const begin = y * text_mode_width * TEXT_BUF_COMPONENT_SIZE + CHARACTER_INDEX;
|
||||
const end = begin + text_mode_width * TEXT_BUF_COMPONENT_SIZE;
|
||||
let row = "";
|
||||
for(let i = begin; i < end; i += TEXT_BUF_COMPONENT_SIZE)
|
||||
{
|
||||
const index = (y * text_mode_width + x) * TEXT_BUF_COMPONENT_SIZE;
|
||||
const character = text_mode_data[index + CHARACTER_INDEX];
|
||||
result += charmap[character];
|
||||
row += charmap[text_mode_data[i]];
|
||||
}
|
||||
|
||||
return result;
|
||||
return row;
|
||||
};
|
||||
|
||||
this.init();
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
"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 +219,7 @@ function SerialRecordingAdapter(bus)
|
|||
* @constructor
|
||||
* @param {BusConnector} bus
|
||||
*/
|
||||
function SerialAdapterXtermJS(element, bus)
|
||||
export function SerialAdapterXtermJS(element, bus)
|
||||
{
|
||||
this.element = element;
|
||||
|
||||
|
|
@ -227,19 +230,21 @@ 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");
|
||||
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,25 @@
|
|||
"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";
|
||||
|
||||
/** @const */
|
||||
var DAC_QUEUE_RESERVE = 0.2;
|
||||
// For Types Only
|
||||
import { BusConnector } from "../bus.js";
|
||||
|
||||
/** @const */
|
||||
var AUDIOBUFFER_MINIMUM_SAMPLING_RATE = 8000;
|
||||
/* global registerProcessor, sampleRate */
|
||||
|
||||
const DAC_QUEUE_RESERVE = 0.2;
|
||||
|
||||
const AUDIOBUFFER_MINIMUM_SAMPLING_RATE = 8000;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {!BusConnector} bus
|
||||
*/
|
||||
function SpeakerAdapter(bus)
|
||||
export function SpeakerAdapter(bus)
|
||||
{
|
||||
if(typeof window === "undefined")
|
||||
{
|
||||
|
|
@ -462,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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,100 +1,31 @@
|
|||
"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";
|
||||
import { dbg_assert, dbg_trace, dbg_log, set_log_level } from "../log.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";
|
||||
import { EEXIST, ENOENT } from "../../lib/9p.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";
|
||||
|
||||
/**
|
||||
* Constructor for emulator instances.
|
||||
*
|
||||
* Usage: `new V86(options);`
|
||||
*
|
||||
* Options can have the following properties (all optional, default in parenthesis):
|
||||
*
|
||||
* - `memory_size number` (64 * 1024 * 1024) - The memory size in bytes, should
|
||||
* be a power of 2.
|
||||
* - `vga_memory_size number` (8 * 1024 * 1024) - VGA memory size in bytes.
|
||||
*
|
||||
* - `autostart boolean` (false) - If emulation should be started when emulator
|
||||
* is ready.
|
||||
*
|
||||
* - `disable_keyboard boolean` (false) - If the keyboard should be disabled.
|
||||
* - `disable_mouse boolean` (false) - If the mouse should be disabled.
|
||||
*
|
||||
* - `network_relay_url string` (No network card) - The url of a server running
|
||||
* websockproxy. See [networking.md](networking.md). Setting this will
|
||||
* enable an emulated ne2k network card. Only provided for backwards
|
||||
* compatibility, use `net_device` instead.
|
||||
*
|
||||
* - `net_device Object` (null) - An object with the following properties:
|
||||
* - `relay_url: string` - See above
|
||||
* - `type: "ne2k" | "virtio"` - the type of the emulated cards
|
||||
*
|
||||
* - `net_devices Array<Object>` - Like `net_device`, but allows specifying
|
||||
* more than one network card (up to 4). (currently not implemented)
|
||||
*
|
||||
* - `bios Object` (No bios) - Either a url pointing to a bios or an
|
||||
* ArrayBuffer, see below.
|
||||
* - `vga_bios Object` (No VGA bios) - VGA bios, see below.
|
||||
* - `hda Object` (No hard disk) - First hard disk, see below.
|
||||
* - `fda Object` (No floppy disk) - First floppy disk, see below.
|
||||
* - `cdrom Object` (No CD) - See below.
|
||||
*
|
||||
* - `bzimage Object` - A Linux kernel image to boot (only bzimage format), see below.
|
||||
* - `initrd Object` - A Linux ramdisk image, see below.
|
||||
* - `bzimage_initrd_from_filesystem boolean` - Automatically fetch bzimage and
|
||||
* initrd from the specified `filesystem`.
|
||||
*
|
||||
* - `initial_state Object` (Normal boot) - An initial state to load, see
|
||||
* [`restore_state`](#restore_statearraybuffer-state) and below.
|
||||
*
|
||||
* - `filesystem Object` (No 9p filesystem) - A 9p filesystem, see
|
||||
* [filesystem.md](filesystem.md).
|
||||
*
|
||||
* - `serial_container HTMLTextAreaElement` (No serial terminal) - A textarea
|
||||
* that will receive and send data to the emulated serial terminal.
|
||||
* Alternatively the serial terminal can also be accessed programatically,
|
||||
* see [serial.html](../examples/serial.html).
|
||||
*
|
||||
* - `screen_container HTMLElement` (No screen) - An HTMLElement. This should
|
||||
* have a certain structure, see [basic.html](../examples/basic.html). Only
|
||||
* provided for backwards compatibility, use `screen` instead.
|
||||
*
|
||||
* - `screen Object` (No screen) - An object with the following properties:
|
||||
* - `container HTMLElement` - An HTMLElement, see above.
|
||||
* - `scale` (1) - Set initial scale_x and scale_y, if 0 disable automatic upscaling and dpi-adaption
|
||||
*
|
||||
* ***
|
||||
*
|
||||
* There are two ways to load images (`bios`, `vga_bios`, `cdrom`, `hda`, ...):
|
||||
*
|
||||
* - Pass an object that has a url. Optionally, `async: true` and `size:
|
||||
* size_in_bytes` can be added to the object, so that sectors of the image
|
||||
* are loaded on demand instead of being loaded before boot (slower, but
|
||||
* strongly recommended for big files). In that case, the `Range: bytes=...`
|
||||
* header must be supported on the server.
|
||||
*
|
||||
* ```javascript
|
||||
* // download file before boot
|
||||
* bios: {
|
||||
* url: "bios/seabios.bin"
|
||||
* }
|
||||
* // download file sectors as requested, size is required
|
||||
* hda: {
|
||||
* url: "disk/linux.iso",
|
||||
* async: true,
|
||||
* size: 16 * 1024 * 1024
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* - Pass an `ArrayBuffer` or `File` object as `buffer` property.
|
||||
*
|
||||
* ```javascript
|
||||
* // use <input type=file>
|
||||
* bios: {
|
||||
* buffer: document.all.hd_image.files[0]
|
||||
* }
|
||||
* // start with empty hard disk
|
||||
* hda: {
|
||||
* buffer: new ArrayBuffer(16 * 1024 * 1024)
|
||||
* }
|
||||
* ```
|
||||
* For API usage, see v86.d.ts in the root of this repository.
|
||||
*
|
||||
* @param {{
|
||||
disable_mouse: (boolean|undefined),
|
||||
|
|
@ -105,14 +36,13 @@
|
|||
} | undefined),
|
||||
}} options
|
||||
* @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;
|
||||
set_log_level(options.log_level);
|
||||
}
|
||||
|
||||
//var worker = new Worker("src/browser/worker.js");
|
||||
|
|
@ -122,7 +52,7 @@ function V86(options)
|
|||
this.cpu_exception_hook = function(n) {};
|
||||
|
||||
const bus = Bus.create();
|
||||
const adapter_bus = this.bus = bus[0];
|
||||
this.bus = bus[0];
|
||||
this.emulator_bus = bus[1];
|
||||
|
||||
var cpu;
|
||||
|
|
@ -136,8 +66,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(); },
|
||||
"apic_acknowledge_irq": function() { return cpu.devices.apic.acknowledge_irq(); },
|
||||
"get_rand_int": function() { return get_rand_int(); },
|
||||
"stop_idling": function() { return cpu.stop_idling(); },
|
||||
|
||||
"io_port_read8": function(addr) { return cpu.io.port_read8(addr); },
|
||||
|
|
@ -159,11 +88,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 +114,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";
|
||||
|
|
@ -192,9 +123,7 @@ 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")
|
||||
{
|
||||
|
|
@ -207,7 +136,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 +147,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;
|
||||
|
|
@ -306,8 +235,6 @@ V86.prototype.continue_init = async function(emulator, options)
|
|||
settings.cpuid_level = options.cpuid_level;
|
||||
settings.virtio_balloon = options.virtio_balloon;
|
||||
settings.virtio_console = options.virtio_console;
|
||||
settings.virtio_net = options.virtio_net;
|
||||
settings.screen_options = options.screen_options;
|
||||
|
||||
const relay_url = options.network_relay_url || options.net_device && options.net_device.relay_url;
|
||||
if(relay_url)
|
||||
|
|
@ -357,21 +284,20 @@ V86.prototype.continue_init = async function(emulator, options)
|
|||
}
|
||||
else
|
||||
{
|
||||
this.screen_adapter = new DummyScreenAdapter();
|
||||
this.screen_adapter = new DummyScreenAdapter(screen_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)
|
||||
{
|
||||
|
|
@ -471,7 +397,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)),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -493,7 +419,15 @@ V86.prototype.continue_init = async function(emulator, options)
|
|||
add_file("bzimage", options.bzimage);
|
||||
add_file("initrd", options.initrd);
|
||||
|
||||
if(options.filesystem)
|
||||
if(options.filesystem && options.filesystem.handle9p)
|
||||
{
|
||||
settings.handle9p = options.filesystem.handle9p;
|
||||
}
|
||||
else if(options.filesystem && options.filesystem.proxy_url)
|
||||
{
|
||||
settings.proxy9p = options.filesystem.proxy_url;
|
||||
}
|
||||
else if(options.filesystem)
|
||||
{
|
||||
var fs_url = options.filesystem.basefs;
|
||||
var base_url = options.filesystem.baseurl;
|
||||
|
|
@ -502,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.bind(this));
|
||||
}
|
||||
settings.fs9p = this.fs9p = new FS(file_storage);
|
||||
|
||||
|
|
@ -552,7 +486,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 +495,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 +552,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
|
||||
|
|
@ -700,8 +634,7 @@ V86.prototype.zstd_decompress_worker = async function(decompressed_size, src)
|
|||
{
|
||||
const env = Object.fromEntries([
|
||||
"cpu_exception_hook", "run_hardware_timers",
|
||||
"cpu_event_halt", "microtick", "get_rand_int",
|
||||
"apic_acknowledge_irq", "stop_idling",
|
||||
"cpu_event_halt", "microtick", "get_rand_int", "stop_idling",
|
||||
"io_port_read8", "io_port_read16", "io_port_read32",
|
||||
"io_port_write8", "io_port_write16", "io_port_write32",
|
||||
"mmap_read8", "mmap_read16", "mmap_read32",
|
||||
|
|
@ -713,7 +646,7 @@ V86.prototype.zstd_decompress_worker = async function(decompressed_size, src)
|
|||
env["__indirect_function_table"] = new WebAssembly.Table({ element: "anyfunc", initial: 1024 });
|
||||
env["abort"] = () => { throw new Error("zstd worker aborted"); };
|
||||
env["log_from_wasm"] = env["console_log_from_wasm"] = (off, len) => {
|
||||
console.log(String.fromCharCode(...new Uint8Array(wasm.exports.memory.buffer, off, len)));
|
||||
console.log(read_sized_string_from_mem(wasm.exports.memory.buffer, off, len));
|
||||
};
|
||||
env["dbg_trace_from_wasm"] = () => console.trace();
|
||||
|
||||
|
|
@ -795,9 +728,7 @@ V86.prototype.get_bzimage_initrd_from_filesystem = function(filesystem)
|
|||
};
|
||||
|
||||
/**
|
||||
* Start emulation. Do nothing if emulator is running already. Can be
|
||||
* asynchronous.
|
||||
* @export
|
||||
* Start emulation. Do nothing if emulator is running already. Can be asynchronous.
|
||||
*/
|
||||
V86.prototype.run = async function()
|
||||
{
|
||||
|
|
@ -806,7 +737,6 @@ V86.prototype.run = async function()
|
|||
|
||||
/**
|
||||
* Stop emulation. Do nothing if emulator is not running. Can be asynchronous.
|
||||
* @export
|
||||
*/
|
||||
V86.prototype.stop = async function()
|
||||
{
|
||||
|
|
@ -826,8 +756,7 @@ V86.prototype.stop = async function()
|
|||
};
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* @export
|
||||
* Free resources associated with this instance
|
||||
*/
|
||||
V86.prototype.destroy = async function()
|
||||
{
|
||||
|
|
@ -844,7 +773,6 @@ V86.prototype.destroy = async function()
|
|||
|
||||
/**
|
||||
* Restart (force a reboot).
|
||||
* @export
|
||||
*/
|
||||
V86.prototype.restart = function()
|
||||
{
|
||||
|
|
@ -852,14 +780,12 @@ V86.prototype.restart = function()
|
|||
};
|
||||
|
||||
/**
|
||||
* Add an event listener (the emulator is an event emitter). A list of events
|
||||
* can be found at [events.md](events.md).
|
||||
* Add an event listener (the emulator is an event emitter).
|
||||
*
|
||||
* The callback function gets a single argument which depends on the event.
|
||||
*
|
||||
* @param {string} event Name of the event.
|
||||
* @param {function(?)} listener The callback function.
|
||||
* @export
|
||||
*/
|
||||
V86.prototype.add_listener = function(event, listener)
|
||||
{
|
||||
|
|
@ -871,7 +797,6 @@ V86.prototype.add_listener = function(event, listener)
|
|||
*
|
||||
* @param {string} event
|
||||
* @param {function(*)} listener
|
||||
* @export
|
||||
*/
|
||||
V86.prototype.remove_listener = function(event, listener)
|
||||
{
|
||||
|
|
@ -891,7 +816,6 @@ V86.prototype.remove_listener = function(event, listener)
|
|||
* state buffer.
|
||||
*
|
||||
* @param {ArrayBuffer} state
|
||||
* @export
|
||||
*/
|
||||
V86.prototype.restore_state = async function(state)
|
||||
{
|
||||
|
|
@ -903,7 +827,6 @@ V86.prototype.restore_state = async function(state)
|
|||
* Asynchronously save the current state of the emulator.
|
||||
*
|
||||
* @return {Promise<ArrayBuffer>}
|
||||
* @export
|
||||
*/
|
||||
V86.prototype.save_state = async function()
|
||||
{
|
||||
|
|
@ -914,7 +837,6 @@ V86.prototype.save_state = async function()
|
|||
/**
|
||||
* @return {number}
|
||||
* @ignore
|
||||
* @export
|
||||
*/
|
||||
V86.prototype.get_instruction_counter = function()
|
||||
{
|
||||
|
|
@ -931,7 +853,6 @@ V86.prototype.get_instruction_counter = function()
|
|||
|
||||
/**
|
||||
* @return {boolean}
|
||||
* @export
|
||||
*/
|
||||
V86.prototype.is_running = function()
|
||||
{
|
||||
|
|
@ -941,37 +862,128 @@ 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)
|
||||
{
|
||||
const fda = this.v86.cpu.devices.fdc.drives[0];
|
||||
if(file.url && !file.async)
|
||||
{
|
||||
v86util.load_file(file.url, {
|
||||
done: result =>
|
||||
{
|
||||
this.v86.cpu.devices.fdc.set_fda(new v86util.SyncBuffer(result));
|
||||
},
|
||||
await new Promise(resolve => {
|
||||
load_file(file.url, {
|
||||
done: result =>
|
||||
{
|
||||
fda.insert_disk(new SyncBuffer(result));
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
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);
|
||||
fda.insert_disk(image);
|
||||
};
|
||||
await image.load();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Eject the floppy drive.
|
||||
* @export
|
||||
* Set the image inserted in the second floppy drive, also at runtime.
|
||||
*/
|
||||
V86.prototype.set_fdb = async function(file)
|
||||
{
|
||||
const fdb = this.v86.cpu.devices.fdc.drives[1];
|
||||
if(file.url && !file.async)
|
||||
{
|
||||
await new Promise(resolve => {
|
||||
load_file(file.url, {
|
||||
done: result =>
|
||||
{
|
||||
fdb.insert_disk(new SyncBuffer(result));
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
const image = buffer_from_object(file, this.zstd_decompress_worker.bind(this));
|
||||
image.onload = () =>
|
||||
{
|
||||
fdb.insert_disk(image);
|
||||
};
|
||||
await image.load();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Eject floppy drive fda.
|
||||
*/
|
||||
V86.prototype.eject_fda = function()
|
||||
{
|
||||
this.v86.cpu.devices.fdc.eject_fda();
|
||||
this.v86.cpu.devices.fdc.drives[0].eject_disk();
|
||||
};
|
||||
|
||||
/**
|
||||
* Eject second floppy drive fdb.
|
||||
*/
|
||||
V86.prototype.eject_fdb = function()
|
||||
{
|
||||
this.v86.cpu.devices.fdc.drives[1].eject_disk();
|
||||
};
|
||||
|
||||
/**
|
||||
* Return buffer object of floppy disk of drive fda or null if the drive is empty.
|
||||
* @return {Uint8Array|null}
|
||||
*/
|
||||
V86.prototype.get_disk_fda = function()
|
||||
{
|
||||
return this.v86.cpu.devices.fdc.drives[0].get_buffer();
|
||||
};
|
||||
|
||||
/**
|
||||
* Return buffer object of second floppy disk of drive fdb or null if the drive is empty.
|
||||
* @return {Uint8Array|null}
|
||||
*/
|
||||
V86.prototype.get_disk_fdb = function()
|
||||
{
|
||||
return this.v86.cpu.devices.fdc.drives[1].get_buffer();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.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.set_cdrom(image);
|
||||
};
|
||||
await image.load();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Eject the CD-ROM.
|
||||
*/
|
||||
V86.prototype.eject_cdrom = function()
|
||||
{
|
||||
this.v86.cpu.devices.cdrom.eject();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -980,47 +992,47 @@ V86.prototype.eject_fda = function()
|
|||
* Do nothing if there is no keyboard controller.
|
||||
*
|
||||
* @param {Array.<number>} codes
|
||||
* @export
|
||||
* @param {number=} delay
|
||||
*/
|
||||
V86.prototype.keyboard_send_scancodes = function(codes)
|
||||
V86.prototype.keyboard_send_scancodes = async function(codes, delay)
|
||||
{
|
||||
for(var i = 0; i < codes.length; i++)
|
||||
{
|
||||
this.bus.send("keyboard-code", codes[i]);
|
||||
if(delay) await new Promise(resolve => setTimeout(resolve, delay));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Send translated keys
|
||||
* @ignore
|
||||
* @export
|
||||
* @param {Array.<number>} codes
|
||||
* @param {number=} delay
|
||||
*/
|
||||
V86.prototype.keyboard_send_keys = function(codes)
|
||||
V86.prototype.keyboard_send_keys = async function(codes, delay)
|
||||
{
|
||||
for(var i = 0; i < codes.length; i++)
|
||||
{
|
||||
this.keyboard_adapter.simulate_press(codes[i]);
|
||||
if(delay) await new Promise(resolve => setTimeout(resolve, delay));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Send text
|
||||
* @ignore
|
||||
* @export
|
||||
* Send text, assuming the guest OS uses a US keyboard layout
|
||||
* @param {string} string
|
||||
* @param {number=} delay
|
||||
*/
|
||||
V86.prototype.keyboard_send_text = function(string)
|
||||
V86.prototype.keyboard_send_text = async function(string, delay)
|
||||
{
|
||||
for(var i = 0; i < string.length; i++)
|
||||
{
|
||||
this.keyboard_adapter.simulate_char(string[i]);
|
||||
if(delay) await new Promise(resolve => setTimeout(resolve, delay));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Download a screenshot.
|
||||
*
|
||||
* @ignore
|
||||
* @export
|
||||
* Download a screenshot (returns an <img> element, only works in browsers)
|
||||
*/
|
||||
V86.prototype.screen_make_screenshot = function()
|
||||
{
|
||||
|
|
@ -1036,9 +1048,6 @@ V86.prototype.screen_make_screenshot = function()
|
|||
*
|
||||
* @param {number} sx
|
||||
* @param {number} sy
|
||||
*
|
||||
* @ignore
|
||||
* @export
|
||||
*/
|
||||
V86.prototype.screen_set_scale = function(sx, sy)
|
||||
{
|
||||
|
|
@ -1049,10 +1058,7 @@ V86.prototype.screen_set_scale = function(sx, sy)
|
|||
};
|
||||
|
||||
/**
|
||||
* Go fullscreen.
|
||||
*
|
||||
* @ignore
|
||||
* @export
|
||||
* Go fullscreen (only browsers)
|
||||
*/
|
||||
V86.prototype.screen_go_fullscreen = function()
|
||||
{
|
||||
|
|
@ -1094,21 +1100,21 @@ V86.prototype.screen_go_fullscreen = function()
|
|||
/**
|
||||
* Lock the mouse cursor: It becomes invisble and is not moved out of the
|
||||
* browser window.
|
||||
*
|
||||
* @ignore
|
||||
* @export
|
||||
*/
|
||||
V86.prototype.lock_mouse = function()
|
||||
V86.prototype.lock_mouse = async function()
|
||||
{
|
||||
var elem = document.body;
|
||||
const elem = document.body;
|
||||
|
||||
var fn = elem["requestPointerLock"] ||
|
||||
elem["mozRequestPointerLock"] ||
|
||||
elem["webkitRequestPointerLock"];
|
||||
|
||||
if(fn)
|
||||
try
|
||||
{
|
||||
fn.call(elem);
|
||||
await elem.requestPointerLock({
|
||||
unadjustedMovement: true,
|
||||
});
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
// as per MDN, retry without unadjustedMovement option
|
||||
await elem.requestPointerLock();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1117,34 +1123,33 @@ V86.prototype.lock_mouse = function()
|
|||
*
|
||||
* @param {boolean} enabled
|
||||
*/
|
||||
V86.prototype.mouse_set_status = function(enabled)
|
||||
V86.prototype.mouse_set_enabled = function(enabled)
|
||||
{
|
||||
if(this.mouse_adapter)
|
||||
{
|
||||
this.mouse_adapter.emu_enabled = enabled;
|
||||
}
|
||||
};
|
||||
V86.prototype.mouse_set_status = V86.prototype.mouse_set_enabled;
|
||||
|
||||
/**
|
||||
* Enable or disable sending keyboard events to the emulated PS2 controller.
|
||||
*
|
||||
* @param {boolean} enabled
|
||||
* @export
|
||||
*/
|
||||
V86.prototype.keyboard_set_status = function(enabled)
|
||||
V86.prototype.keyboard_set_enabled = function(enabled)
|
||||
{
|
||||
if(this.keyboard_adapter)
|
||||
{
|
||||
this.keyboard_adapter.emu_enabled = enabled;
|
||||
}
|
||||
};
|
||||
|
||||
V86.prototype.keyboard_set_status = V86.prototype.keyboard_set_enabled;
|
||||
|
||||
/**
|
||||
* Send a string to the first emulated serial terminal.
|
||||
*
|
||||
* @param {string} data
|
||||
* @export
|
||||
*/
|
||||
V86.prototype.serial0_send = function(data)
|
||||
{
|
||||
|
|
@ -1158,7 +1163,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)
|
||||
{
|
||||
|
|
@ -1208,52 +1212,12 @@ V86.prototype.serial_set_clear_to_send = function(serial, status)
|
|||
this.bus.send("serial" + serial + "-clear-to-send-input", status);
|
||||
};
|
||||
|
||||
/**
|
||||
* Mount another filesystem to the current filesystem.
|
||||
* @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)
|
||||
{
|
||||
let file_storage = new MemoryFileStorage();
|
||||
|
||||
if(baseurl)
|
||||
{
|
||||
file_storage = new ServerFileStorageWrapper(file_storage, baseurl);
|
||||
}
|
||||
const newfs = new FS(file_storage, this.fs9p.qidcounter);
|
||||
if(baseurl)
|
||||
{
|
||||
dbg_assert(typeof basefs === "object", "Filesystem: basefs must be a JSON object");
|
||||
newfs.load_from_json(basefs);
|
||||
}
|
||||
|
||||
const idx = this.fs9p.Mount(path, newfs);
|
||||
|
||||
if(idx === -ENOENT)
|
||||
{
|
||||
throw new FileNotFoundError();
|
||||
}
|
||||
else if(idx === -EEXIST)
|
||||
{
|
||||
throw new FileExistsError();
|
||||
}
|
||||
else if(idx < 0)
|
||||
{
|
||||
dbg_assert(false, "Unexpected error code: " + (-idx));
|
||||
throw new Error("Failed to mount. Error number: " + (-idx));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Write to a file in the 9p filesystem. Nothing happens if no filesystem has
|
||||
* been initialized.
|
||||
*
|
||||
* @param {string} file
|
||||
* @param {Uint8Array} data
|
||||
* @export
|
||||
*/
|
||||
V86.prototype.create_file = async function(file, data)
|
||||
{
|
||||
|
|
@ -1287,7 +1251,6 @@ V86.prototype.create_file = async function(file, data)
|
|||
* initialized.
|
||||
*
|
||||
* @param {string} file
|
||||
* @export
|
||||
*/
|
||||
V86.prototype.read_file = async function(file)
|
||||
{
|
||||
|
|
@ -1369,52 +1332,110 @@ V86.prototype.automatically = function(steps)
|
|||
run(steps);
|
||||
};
|
||||
|
||||
V86.prototype.wait_until_vga_screen_contains = function(text)
|
||||
/**
|
||||
* Wait until expected text is present on the VGA text screen.
|
||||
*
|
||||
* Returns immediately if the expected text is already present on screen
|
||||
* at the time this funtion is called.
|
||||
*
|
||||
* An optional timeout may be specified in `options.timeout_msec`, returns
|
||||
* false if the timeout expires before the expected text could be detected.
|
||||
*
|
||||
* Expected text (or texts, see below) must be of type string or RegExp,
|
||||
* strings are tested against the beginning of a screen line, regular
|
||||
* expressions against the full line but may use wildcards for partial
|
||||
* matching.
|
||||
*
|
||||
* Two methods of text detection are supported depending on the type of the
|
||||
* argument `expected`:
|
||||
*
|
||||
* 1. If `expected` is a string or RegExp then the given text string or
|
||||
* regular expression may match any line on screen for this function
|
||||
* to succeed.
|
||||
*
|
||||
* 2. If `expected` is an array of strings and/or RegExp objects then the
|
||||
* list of expected lines must match exactly at "the bottom" of the
|
||||
* screen. The "bottom" line is the first non-empty line starting from
|
||||
* the screen's end.
|
||||
* Expected lines should not contain any trailing whitespace and/or
|
||||
* newline characters. Expecting an empty line is valid.
|
||||
*
|
||||
* Returns `true` on success and `false` when the timeout has expired.
|
||||
*
|
||||
* @param {string|RegExp|Array<string|RegExp>} expected
|
||||
* @param {{timeout_msec:(number|undefined)}=} options
|
||||
*/
|
||||
V86.prototype.wait_until_vga_screen_contains = async function(expected, options)
|
||||
{
|
||||
return new Promise(resolve =>
|
||||
const match_multi = Array.isArray(expected);
|
||||
const timeout_msec = options?.timeout_msec || 0;
|
||||
const changed_rows = new Set();
|
||||
const screen_put_char = args => changed_rows.add(args[0]);
|
||||
const contains_expected = (screen_line, pattern) => pattern.test ? pattern.test(screen_line) : screen_line.startsWith(pattern);
|
||||
const screen_lines = [];
|
||||
|
||||
this.add_listener("screen-put-char", screen_put_char);
|
||||
|
||||
for(const screen_line of this.screen_adapter.get_text_screen())
|
||||
{
|
||||
function test_line(line)
|
||||
if(match_multi)
|
||||
{
|
||||
return typeof text === "string" ? line.includes(text) : text.test(line);
|
||||
screen_lines.push(screen_line.trimRight());
|
||||
}
|
||||
|
||||
for(const line of this.screen_adapter.get_text_screen())
|
||||
else if(contains_expected(screen_line, expected))
|
||||
{
|
||||
if(test_line(line))
|
||||
this.remove_listener("screen-put-char", screen_put_char);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
let succeeded = false;
|
||||
const end = timeout_msec ? performance.now() + timeout_msec : 0;
|
||||
loop: while(!end || performance.now() < end)
|
||||
{
|
||||
if(match_multi)
|
||||
{
|
||||
let screen_height = screen_lines.length;
|
||||
while(screen_height > 0 && screen_lines[screen_height - 1] === "")
|
||||
{
|
||||
resolve(true);
|
||||
return;
|
||||
screen_height--;
|
||||
}
|
||||
}
|
||||
|
||||
const changed_rows = new Set();
|
||||
|
||||
function put_char(args)
|
||||
{
|
||||
const [row, col, char] = args;
|
||||
changed_rows.add(row);
|
||||
}
|
||||
|
||||
const check = () =>
|
||||
{
|
||||
for(const row of changed_rows)
|
||||
const screen_offset = screen_height - expected.length;
|
||||
if(screen_offset >= 0)
|
||||
{
|
||||
const line = this.screen_adapter.get_text_row(row);
|
||||
if(test_line(line))
|
||||
let matches = true;
|
||||
for(let i = 0; i < expected.length && matches; i++)
|
||||
{
|
||||
this.remove_listener("screen-put-char", put_char);
|
||||
resolve();
|
||||
return;
|
||||
matches = contains_expected(screen_lines[screen_offset + i], expected[i]);
|
||||
}
|
||||
if(matches)
|
||||
{
|
||||
succeeded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changed_rows.clear();
|
||||
setTimeout(check, 100);
|
||||
};
|
||||
check();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
this.add_listener("screen-put-char", put_char);
|
||||
});
|
||||
for(const row of changed_rows)
|
||||
{
|
||||
const screen_line = this.screen_adapter.get_text_row(row);
|
||||
if(match_multi)
|
||||
{
|
||||
screen_lines[row] = screen_line.trimRight();
|
||||
}
|
||||
else if(contains_expected(screen_line, expected))
|
||||
{
|
||||
succeeded = true;
|
||||
break loop;
|
||||
}
|
||||
}
|
||||
changed_rows.clear();
|
||||
}
|
||||
|
||||
this.remove_listener("screen-put-char", screen_put_char);
|
||||
return succeeded;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1447,6 +1468,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
|
||||
|
|
@ -1470,3 +1496,19 @@ function FileNotFoundError(message)
|
|||
this.message = message || "File not found";
|
||||
}
|
||||
FileNotFoundError.prototype = Error.prototype;
|
||||
|
||||
/* global module, self */
|
||||
|
||||
if(typeof module !== "undefined" && typeof module.exports !== "undefined")
|
||||
{
|
||||
module.exports["V86"] = V86;
|
||||
}
|
||||
else if(typeof window !== "undefined")
|
||||
{
|
||||
window["V86"] = V86;
|
||||
}
|
||||
else if(typeof importScripts === "function")
|
||||
{
|
||||
// web worker
|
||||
self["V86"] = V86;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,15 @@
|
|||
"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 +18,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;
|
||||
|
|
@ -25,7 +36,8 @@ 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); }));
|
||||
|
|
@ -82,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
|
||||
|
|
@ -174,17 +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();
|
||||
conn.state = TCP_STATE_SYN_RECEIVED;
|
||||
conn.net = this;
|
||||
conn.tuple = tuple;
|
||||
conn.stream_id = this.last_stream++;
|
||||
this.tcp_conn[tuple] = conn;
|
||||
|
||||
conn.on("data", data => {
|
||||
if(data.length !== 0) {
|
||||
|
|
@ -211,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);
|
||||
},
|
||||
|
|
@ -220,7 +229,7 @@ WispNetworkAdapter.prototype.on_tcp_connection = function(packet, tuple)
|
|||
}
|
||||
});
|
||||
|
||||
conn.accept(packet);
|
||||
conn.accept();
|
||||
return true;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
"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 +20,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 +42,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 +55,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);
|
||||
};
|
||||
|
|
|
|||
1491
src/buffer.js
1491
src/buffer.js
File diff suppressed because it is too large
Load diff
|
|
@ -1,9 +1,9 @@
|
|||
"use strict";
|
||||
import { dbg_assert } from "./log.js";
|
||||
|
||||
var Bus = {};
|
||||
export var Bus = {};
|
||||
|
||||
/** @constructor */
|
||||
function BusConnector()
|
||||
export function BusConnector()
|
||||
{
|
||||
this.listeners = {};
|
||||
this.pair = undefined;
|
||||
|
|
|
|||
5
src/cjs.js
Normal file
5
src/cjs.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* @define {boolean}
|
||||
* Overridden for production by closure compiler
|
||||
*/
|
||||
var DEBUG = true;
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
"use strict";
|
||||
/*
|
||||
* Compile time configuration, some only relevant for debug mode
|
||||
*/
|
||||
|
||||
/**
|
||||
* @define {boolean}
|
||||
* Overridden for production by closure compiler
|
||||
*/
|
||||
var DEBUG = true;
|
||||
|
||||
/** @const */
|
||||
var LOG_TO_FILE = false;
|
||||
|
||||
/**
|
||||
* @const
|
||||
* Enables logging all IO port reads and writes. Very verbose
|
||||
*/
|
||||
var LOG_ALL_IO = false;
|
||||
|
||||
/**
|
||||
* @const
|
||||
*/
|
||||
var DUMP_GENERATED_WASM = false;
|
||||
|
||||
/**
|
||||
* @const
|
||||
*/
|
||||
var DUMP_UNCOMPILED_ASSEMBLY = false;
|
||||
|
||||
/**
|
||||
* @const
|
||||
* More accurate filenames in 9p debug messages at the cost of performance.
|
||||
*/
|
||||
var TRACK_FILENAMES = false;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @const
|
||||
* Draws entire buffer and visualizes the layers that would be drawn
|
||||
*/
|
||||
var DEBUG_SCREEN_LAYERS = DEBUG && false;
|
||||
|
||||
/**
|
||||
* @const
|
||||
* How many ticks the TSC does per millisecond
|
||||
*/
|
||||
var TSC_RATE = 1 * 1000 * 1000;
|
||||
|
||||
/** @const */
|
||||
var APIC_TIMER_FREQ = TSC_RATE;
|
||||
201
src/const.js
201
src/const.js
|
|
@ -1,41 +1,38 @@
|
|||
"use strict";
|
||||
export const
|
||||
LOG_ALL = -1,
|
||||
LOG_NONE = 0,
|
||||
|
||||
var
|
||||
/** @const */ LOG_ALL = -1,
|
||||
/** @const */ 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<Array<string|number>>}
|
||||
*/
|
||||
var LOG_NAMES = [
|
||||
export const LOG_NAMES = [
|
||||
[1, ""],
|
||||
[LOG_CPU, "CPU"],
|
||||
[LOG_DISK, "DISK"],
|
||||
|
|
@ -62,105 +59,81 @@ var LOG_NAMES = [
|
|||
[LOG_FETCH, "FETC"],
|
||||
];
|
||||
|
||||
var
|
||||
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,
|
||||
REG_LDTR = 7; // local descriptor table register
|
||||
|
||||
/** @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
|
||||
|
||||
var
|
||||
/**
|
||||
* The minimum number of bytes that can be memory-mapped
|
||||
* by one device.
|
||||
*
|
||||
* @const
|
||||
*/
|
||||
export 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 */
|
||||
var CR0_PG = 1 << 31;
|
||||
/** @const */
|
||||
var CR4_PAE = 1 << 5;
|
||||
export const CR0_PG = 1 << 31;
|
||||
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;
|
||||
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 */ var 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 */ var FW_CFG_FILE_START = 0xC000;
|
||||
|
||||
/** @const */ var 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 */
|
||||
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;
|
||||
/** @const */
|
||||
var MIXER_CHANNEL_RIGHT = 1;
|
||||
/** @const */
|
||||
var MIXER_CHANNEL_BOTH = 2;
|
||||
/** @const */
|
||||
var MIXER_SRC_MASTER = 0;
|
||||
/** @const */
|
||||
var MIXER_SRC_PCSPEAKER = 1;
|
||||
/** @const */
|
||||
var MIXER_SRC_DAC = 2;
|
||||
export const MIXER_CHANNEL_LEFT = 0;
|
||||
export const MIXER_CHANNEL_RIGHT = 1;
|
||||
export const MIXER_CHANNEL_BOTH = 2;
|
||||
export const MIXER_SRC_MASTER = 0;
|
||||
export const MIXER_SRC_PCSPEAKER = 1;
|
||||
export const MIXER_SRC_DAC = 2;
|
||||
|
|
|
|||
1129
src/cpu.js
1129
src/cpu.js
File diff suppressed because it is too large
Load diff
662
src/debug.js
662
src/debug.js
|
|
@ -1,662 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
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)
|
||||
{
|
||||
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) + ": " +
|
||||
v86util.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)
|
||||
{
|
||||
if(wabt === undefined)
|
||||
{
|
||||
if(typeof require === "function")
|
||||
{
|
||||
wabt = require("./libwabt.js");
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -1,10 +1,15 @@
|
|||
"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;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"use strict";
|
||||
import { dbg_log, LOG_LEVEL } from "./log.js";
|
||||
|
||||
// A minimal elf parser for loading 32 bit, x86, little endian, executable elf files
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ function create_struct(struct)
|
|||
}
|
||||
|
||||
/** @param {ArrayBuffer} buffer */
|
||||
function read_elf(buffer)
|
||||
export function read_elf(buffer)
|
||||
{
|
||||
const view = new DataView(buffer);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
"use strict";
|
||||
|
||||
var global = {};
|
||||
var process = { hrtime: function() {} };
|
||||
|
||||
|
|
@ -9,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) {},
|
||||
|
|
|
|||
1851
src/floppy.js
1851
src/floppy.js
File diff suppressed because it is too large
Load diff
2447
src/ide.js
2447
src/ide.js
File diff suppressed because it is too large
Load diff
22
src/io.js
22
src/io.js
|
|
@ -1,4 +1,12 @@
|
|||
"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";
|
||||
|
||||
// For Types Only
|
||||
import { CPU } from "./cpu.js";
|
||||
|
||||
// Enables logging all IO port reads and writes. Very verbose
|
||||
const LOG_ALL_IO = false;
|
||||
|
||||
/**
|
||||
* The ISA IO bus
|
||||
|
|
@ -7,7 +15,7 @@
|
|||
* @constructor
|
||||
* @param {CPU} cpu
|
||||
*/
|
||||
function IO(cpu)
|
||||
export function IO(cpu)
|
||||
{
|
||||
/** @const */
|
||||
this.ports = [];
|
||||
|
|
@ -87,9 +95,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)
|
||||
{
|
||||
|
|
@ -356,7 +364,7 @@ IO.prototype.port_read8 = function(port_addr)
|
|||
}
|
||||
var value = entry.read8.call(entry.device, port_addr);
|
||||
dbg_assert(typeof value === "number");
|
||||
dbg_assert(value < 0x100 && value >= 0, "8 bit port returned large value: " + h(port_addr));
|
||||
if(value < 0 || value >= 0x100) dbg_assert(false, "8 bit port returned large value: " + h(port_addr));
|
||||
return value;
|
||||
};
|
||||
|
||||
|
|
@ -373,7 +381,7 @@ IO.prototype.port_read16 = function(port_addr)
|
|||
}
|
||||
var value = entry.read16.call(entry.device, port_addr);
|
||||
dbg_assert(typeof value === "number");
|
||||
dbg_assert(value < 0x10000 && value >= 0, "16 bit port returned large value: " + h(port_addr));
|
||||
if(value < 0 || value >= 0x10000) dbg_assert(false, "16 bit port returned large value: " + h(port_addr));
|
||||
return value;
|
||||
};
|
||||
|
||||
|
|
|
|||
364
src/ioapic.js
364
src/ioapic.js
|
|
@ -1,364 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
// http://download.intel.com/design/chipsets/datashts/29056601.pdf
|
||||
|
||||
/** @const */
|
||||
var IOAPIC_ADDRESS = 0xFEC00000;
|
||||
|
||||
/** @const */
|
||||
var IOREGSEL = 0;
|
||||
|
||||
/** @const */
|
||||
var IOWIN = 0x10;
|
||||
|
||||
/** @const */
|
||||
var IOAPIC_IRQ_COUNT = 24;
|
||||
|
||||
/** @const */
|
||||
var IOAPIC_ID = 0; // must match value in seabios
|
||||
|
||||
|
||||
/** @const */
|
||||
var IOAPIC_CONFIG_TRIGGER_MODE_LEVEL = 1 << 15;
|
||||
|
||||
/** @const */
|
||||
var IOAPIC_CONFIG_MASKED = 1 << 16;
|
||||
|
||||
/** @const */
|
||||
var IOAPIC_CONFIG_DELIVS = 1 << 12;
|
||||
|
||||
/** @const */
|
||||
var IOAPIC_CONFIG_REMOTE_IRR = 1 << 14;
|
||||
|
||||
/** @const */
|
||||
var IOAPIC_CONFIG_READONLY_MASK = IOAPIC_CONFIG_REMOTE_IRR | IOAPIC_CONFIG_DELIVS | 0xFFFE0000;
|
||||
|
||||
/** @const */
|
||||
var IOAPIC_DELIVERY_FIXED = 0;
|
||||
|
||||
/** @const */
|
||||
var IOAPIC_DELIVERY_LOWEST_PRIORITY = 1;
|
||||
|
||||
/** @const */
|
||||
var IOAPIC_DELIVERY_NMI = 4;
|
||||
|
||||
/** @const */
|
||||
var IOAPIC_DELIVERY_INIT = 5;
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {CPU} cpu
|
||||
*/
|
||||
function IOAPIC(cpu)
|
||||
{
|
||||
/** @type {CPU} */
|
||||
this.cpu = cpu;
|
||||
|
||||
this.ioredtbl_config = new Int32Array(IOAPIC_IRQ_COUNT);
|
||||
this.ioredtbl_destination = new Int32Array(IOAPIC_IRQ_COUNT);
|
||||
|
||||
for(var i = 0; i < this.ioredtbl_config.length; i++)
|
||||
{
|
||||
// disable interrupts
|
||||
this.ioredtbl_config[i] = IOAPIC_CONFIG_MASKED;
|
||||
}
|
||||
|
||||
// IOAPIC register selection
|
||||
this.ioregsel = 0;
|
||||
|
||||
this.ioapic_id = IOAPIC_ID;
|
||||
|
||||
this.irr = 0;
|
||||
this.irq_value = 0;
|
||||
|
||||
dbg_assert(MMAP_BLOCK_SIZE >= 0x20);
|
||||
cpu.io.mmap_register(IOAPIC_ADDRESS, MMAP_BLOCK_SIZE,
|
||||
(addr) =>
|
||||
{
|
||||
addr = addr - IOAPIC_ADDRESS | 0;
|
||||
|
||||
if(addr >= IOWIN && addr < IOWIN + 4)
|
||||
{
|
||||
const byte = addr - IOWIN;
|
||||
dbg_log("ioapic read8 byte " + byte + " " + h(this.ioregsel), LOG_APIC);
|
||||
return this.read(this.ioregsel) >> (8 * byte) & 0xFF;
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_log("Unexpected IOAPIC register read: " + h(addr >>> 0), LOG_APIC);
|
||||
dbg_assert(false);
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
(addr, value) =>
|
||||
{
|
||||
dbg_assert(false, "unsupported write8 from ioapic: " + h(addr >>> 0));
|
||||
},
|
||||
(addr) =>
|
||||
{
|
||||
addr = addr - IOAPIC_ADDRESS | 0;
|
||||
|
||||
if(addr === IOREGSEL)
|
||||
{
|
||||
return this.ioregsel;
|
||||
}
|
||||
else if(addr === IOWIN)
|
||||
{
|
||||
return this.read(this.ioregsel);
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_log("Unexpected IOAPIC register read: " + h(addr >>> 0), LOG_APIC);
|
||||
dbg_assert(false);
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
(addr, value) =>
|
||||
{
|
||||
addr = addr - IOAPIC_ADDRESS | 0;
|
||||
|
||||
if(addr === IOREGSEL)
|
||||
{
|
||||
this.ioregsel = value;
|
||||
}
|
||||
else if(addr === IOWIN)
|
||||
{
|
||||
this.write(this.ioregsel, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_log("Unexpected IOAPIC register write: " + h(addr >>> 0) + " <- " + h(value >>> 0, 8), LOG_APIC);
|
||||
dbg_assert(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
IOAPIC.prototype.remote_eoi = function(vector)
|
||||
{
|
||||
for(var i = 0; i < IOAPIC_IRQ_COUNT; i++)
|
||||
{
|
||||
var config = this.ioredtbl_config[i];
|
||||
|
||||
if((config & 0xFF) === vector && (config & IOAPIC_CONFIG_REMOTE_IRR))
|
||||
{
|
||||
dbg_log("Clear remote IRR for irq=" + h(i), LOG_APIC);
|
||||
this.ioredtbl_config[i] &= ~IOAPIC_CONFIG_REMOTE_IRR;
|
||||
this.check_irq(i);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IOAPIC.prototype.check_irq = function(irq)
|
||||
{
|
||||
var mask = 1 << irq;
|
||||
|
||||
if((this.irr & mask) === 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var config = this.ioredtbl_config[irq];
|
||||
|
||||
if((config & IOAPIC_CONFIG_MASKED) === 0)
|
||||
{
|
||||
var delivery_mode = config >> 8 & 7;
|
||||
var destination_mode = config >> 11 & 1;
|
||||
var vector = config & 0xFF;
|
||||
var destination = this.ioredtbl_destination[irq] >>> 24;
|
||||
var is_level = (config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL) === IOAPIC_CONFIG_TRIGGER_MODE_LEVEL;
|
||||
|
||||
if((config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL) === 0)
|
||||
{
|
||||
this.irr &= ~mask;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ioredtbl_config[irq] |= IOAPIC_CONFIG_REMOTE_IRR;
|
||||
|
||||
if(config & IOAPIC_CONFIG_REMOTE_IRR)
|
||||
{
|
||||
dbg_log("No route: level interrupt and remote IRR still set", LOG_APIC);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(delivery_mode === IOAPIC_DELIVERY_FIXED || delivery_mode === IOAPIC_DELIVERY_LOWEST_PRIORITY)
|
||||
{
|
||||
this.cpu.devices.apic.route(vector, delivery_mode, is_level, destination, destination_mode);
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_assert(false, "TODO");
|
||||
}
|
||||
|
||||
this.ioredtbl_config[irq] &= ~IOAPIC_CONFIG_DELIVS;
|
||||
}
|
||||
};
|
||||
|
||||
IOAPIC.prototype.set_irq = function(i)
|
||||
{
|
||||
if(i >= IOAPIC_IRQ_COUNT)
|
||||
{
|
||||
dbg_assert(false, "Bad irq: " + i, LOG_APIC);
|
||||
return;
|
||||
}
|
||||
|
||||
var mask = 1 << i;
|
||||
|
||||
if((this.irq_value & mask) === 0)
|
||||
{
|
||||
APIC_LOG_VERBOSE && dbg_log("apic set irq " + i, LOG_APIC);
|
||||
|
||||
this.irq_value |= mask;
|
||||
|
||||
var config = this.ioredtbl_config[i];
|
||||
if((config & (IOAPIC_CONFIG_TRIGGER_MODE_LEVEL|IOAPIC_CONFIG_MASKED)) ===
|
||||
IOAPIC_CONFIG_MASKED)
|
||||
{
|
||||
// edge triggered and masked
|
||||
return;
|
||||
}
|
||||
|
||||
this.irr |= mask;
|
||||
|
||||
this.check_irq(i);
|
||||
}
|
||||
};
|
||||
|
||||
IOAPIC.prototype.clear_irq = function(i)
|
||||
{
|
||||
if(i >= IOAPIC_IRQ_COUNT)
|
||||
{
|
||||
dbg_assert(false, "Bad irq: " + i, LOG_APIC);
|
||||
return;
|
||||
}
|
||||
|
||||
var mask = 1 << i;
|
||||
|
||||
if((this.irq_value & mask) === mask)
|
||||
{
|
||||
this.irq_value &= ~mask;
|
||||
|
||||
var config = this.ioredtbl_config[i];
|
||||
if(config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL)
|
||||
{
|
||||
this.irr &= ~mask;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IOAPIC.prototype.read = function(reg)
|
||||
{
|
||||
if(reg === 0)
|
||||
{
|
||||
dbg_log("IOAPIC Read id", LOG_APIC);
|
||||
return this.ioapic_id << 24;
|
||||
}
|
||||
else if(reg === 1)
|
||||
{
|
||||
dbg_log("IOAPIC Read version", LOG_APIC);
|
||||
return 0x11 | IOAPIC_IRQ_COUNT - 1 << 16;
|
||||
}
|
||||
else if(reg === 2)
|
||||
{
|
||||
dbg_log("IOAPIC Read arbitration id", LOG_APIC);
|
||||
return this.ioapic_id << 24;
|
||||
}
|
||||
else if(reg >= 0x10 && reg < 0x10 + 2 * IOAPIC_IRQ_COUNT)
|
||||
{
|
||||
var irq = reg - 0x10 >> 1;
|
||||
var index = reg & 1;
|
||||
|
||||
if(index)
|
||||
{
|
||||
var value = this.ioredtbl_destination[irq];
|
||||
dbg_log("IOAPIC Read destination irq=" + h(irq) + " -> " + h(value, 8), LOG_APIC);
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = this.ioredtbl_config[irq];
|
||||
dbg_log("IOAPIC Read config irq=" + h(irq) + " -> " + h(value, 8), LOG_APIC);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_log("IOAPIC register read outside of range " + h(reg), LOG_APIC);
|
||||
dbg_assert(false);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
IOAPIC.prototype.write = function(reg, value)
|
||||
{
|
||||
//dbg_log("IOAPIC write " + h(reg) + " <- " + h(value, 8), LOG_APIC);
|
||||
|
||||
if(reg === 0)
|
||||
{
|
||||
this.ioapic_id = value >>> 24 & 0x0F;
|
||||
}
|
||||
else if(reg === 1 || reg === 2)
|
||||
{
|
||||
dbg_log("Invalid write: " + reg, LOG_APIC);
|
||||
}
|
||||
else if(reg >= 0x10 && reg < 0x10 + 2 * IOAPIC_IRQ_COUNT)
|
||||
{
|
||||
var irq = reg - 0x10 >> 1;
|
||||
var index = reg & 1;
|
||||
|
||||
if(index)
|
||||
{
|
||||
this.ioredtbl_destination[irq] = value & 0xFF000000;
|
||||
dbg_log("Write destination " + h(value >>> 0, 8) + " irq=" + h(irq) + " dest=" + h(value >>> 24, 2), LOG_APIC);
|
||||
}
|
||||
else
|
||||
{
|
||||
var old_value = this.ioredtbl_config[irq];
|
||||
this.ioredtbl_config[irq] = value & ~IOAPIC_CONFIG_READONLY_MASK | old_value & IOAPIC_CONFIG_READONLY_MASK;
|
||||
|
||||
var vector = value & 0xFF;
|
||||
var delivery_mode = value >> 8 & 7;
|
||||
var destination_mode = value >> 11 & 1;
|
||||
var is_level = value >> 15 & 1;
|
||||
var disabled = value >> 16 & 1;
|
||||
|
||||
dbg_log("Write config " + h(value >>> 0, 8) +
|
||||
" irq=" + h(irq) +
|
||||
" vector=" + h(vector, 2) +
|
||||
" deliverymode=" + DELIVERY_MODES[delivery_mode] +
|
||||
" destmode=" + DESTINATION_MODES[destination_mode] +
|
||||
" is_level=" + is_level +
|
||||
" disabled=" + disabled, LOG_APIC);
|
||||
|
||||
this.check_irq(irq);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_log("IOAPIC register write outside of range " + h(reg) + ": " + h(value >>> 0, 8), LOG_APIC);
|
||||
dbg_assert(false);
|
||||
}
|
||||
};
|
||||
|
||||
IOAPIC.prototype.get_state = function()
|
||||
{
|
||||
var state = [];
|
||||
state[0] = this.ioredtbl_config;
|
||||
state[1] = this.ioredtbl_destination;
|
||||
state[2] = this.ioregsel;
|
||||
state[3] = this.ioapic_id;
|
||||
state[4] = this.irr;
|
||||
state[5] = this.irq_value;
|
||||
return state;
|
||||
};
|
||||
|
||||
IOAPIC.prototype.set_state = function(state)
|
||||
{
|
||||
this.ioredtbl_config = state[0];
|
||||
this.ioredtbl_destination = state[1];
|
||||
this.ioregsel = state[2];
|
||||
this.ioapic_id = state[3];
|
||||
this.irr = state[4];
|
||||
this.irq_value = state[5];
|
||||
};
|
||||
255
src/iso9660.js
Normal file
255
src/iso9660.js
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
// Source: https://wiki.osdev.org/ISO_9660
|
||||
|
||||
// Limitations:
|
||||
// - can only generate iso files
|
||||
// - only supports a single directory, no file system hierarchy
|
||||
// - root directory entry is limited to 2 KiB (~42 files)
|
||||
// - filenames are normalised to 8.3 length and [A-Z0-9_.]
|
||||
|
||||
import { dbg_assert } from "./log.js";
|
||||
|
||||
const BLOCK_SIZE = 2 * 1024; // 0x800
|
||||
|
||||
const FILE_FLAGS_HIDDEN = 1 << 0;
|
||||
const FILE_FLAGS_DIRECTORY = 1 << 1;
|
||||
const FILE_FLAGS_ASSOCIATED_FILE = 1 << 2;
|
||||
const FILE_FLAGS_HAS_EXTENDED_ATTRIBUTE_RECORD = 1 << 3;
|
||||
const FILE_FLAGS_HAS_PERMISSIONS = 1 << 4;
|
||||
const FILE_FLAGS_NOT_FINAL = 1 << 7;
|
||||
|
||||
/**
|
||||
* @param {Array.<{ name: string, contents: Uint8Array}>} files
|
||||
*/
|
||||
export function generate(files)
|
||||
{
|
||||
const te = new TextEncoder();
|
||||
const date = new Date;
|
||||
|
||||
const write8 = (b, v) => { b.buffer[b.offset++] = v; };
|
||||
const write_le16 = (b, v) => { b.buffer[b.offset++] = v; b.buffer[b.offset++] = v >> 8; };
|
||||
const write_le32 = (b, v) => { b.buffer[b.offset++] = v; b.buffer[b.offset++] = v >> 8; b.buffer[b.offset++] = v >> 16; b.buffer[b.offset++] = v >> 24; };
|
||||
const write_be16 = (b, v) => { b.buffer[b.offset++] = v >> 8; b.buffer[b.offset++] = v; };
|
||||
const write_be32 = (b, v) => { b.buffer[b.offset++] = v >> 24; b.buffer[b.offset++] = v >> 16; b.buffer[b.offset++] = v >> 8; b.buffer[b.offset++] = v; };
|
||||
const write_lebe16 = (b, v) => { write_le16(b, v); write_be16(b, v); };
|
||||
const write_lebe32 = (b, v) => { write_le32(b, v); write_be32(b, v); };
|
||||
const fill = (b, len, v) => { b.buffer.fill(v, b.offset, b.offset += len); };
|
||||
const write_ascii = (b, v) => { b.offset += te.encodeInto(v, b.buffer.subarray(b.offset)).written; };
|
||||
const write_padded_ascii = (b, len, v) => { b.offset += te.encodeInto(v.padEnd(len), b.buffer.subarray(b.offset)).written; };
|
||||
const write_dummy_date_ascii = b => { fill(b, 16, 0x20); write8(b, 0); };
|
||||
const write_date_compact = b => {
|
||||
write8(b, date.getUTCFullYear() - 1900);
|
||||
write8(b, 1 + date.getUTCMonth());
|
||||
write8(b, date.getUTCDate());
|
||||
write8(b, date.getUTCHours());
|
||||
write8(b, date.getUTCMinutes());
|
||||
write8(b, date.getUTCSeconds());
|
||||
write8(b, 0);
|
||||
};
|
||||
const skip = (b, len) => { b.offset += len; };
|
||||
|
||||
const write_record = (b, name, flags, is_special, lba, len) => {
|
||||
if(!is_special) name = sanitise_filename(name) + ";1";
|
||||
// write name first and get its length
|
||||
const START = buffer.offset;
|
||||
const NAME_OFFSET = 33;
|
||||
const name_len = te.encodeInto(name, b.buffer.subarray(b.offset + NAME_OFFSET)).written;
|
||||
const pad = (name_len & 1) ? 0 : 1;
|
||||
const len_field = 33 + name_len + pad;
|
||||
dbg_assert(len_field < 256);
|
||||
write8(buffer, len_field); // Length of directory record
|
||||
write8(buffer, 0); // Extended Attribute Record length
|
||||
write_lebe32(buffer, lba); // Location of extent (LBA)
|
||||
write_lebe32(buffer, len); // Data length (size of extent)
|
||||
write_date_compact(buffer);
|
||||
write8(buffer, flags);
|
||||
write8(buffer, 0); // File unit size for files recorded in interleaved mode, zero otherwise
|
||||
write8(buffer, 0); // Interleave gap size for files recorded in interleaved mode, zero otherwise
|
||||
write_lebe16(buffer, 1); // Volume sequence number - the volume that this extent is recorded on
|
||||
write8(buffer, name_len); // length of file name
|
||||
dbg_assert(buffer.offset === START + NAME_OFFSET);
|
||||
skip(buffer, name_len + pad); // File name: was already written
|
||||
dbg_assert(buffer.offset === START + len_field);
|
||||
};
|
||||
const write_special_directory_record = (b, name, lba, len) => write_record(b, name, FILE_FLAGS_DIRECTORY, true, lba, len);
|
||||
const write_file_record = (b, name, lba, len) => write_record(b, name, 0, false, lba, len);
|
||||
|
||||
function round_byte_size_to_block_size(n)
|
||||
{
|
||||
return 1 + Math.floor((n - 1) / BLOCK_SIZE);
|
||||
}
|
||||
dbg_assert(round_byte_size_to_block_size(0) === 0);
|
||||
dbg_assert(round_byte_size_to_block_size(1) === 1);
|
||||
dbg_assert(round_byte_size_to_block_size(BLOCK_SIZE - 1) === 1);
|
||||
dbg_assert(round_byte_size_to_block_size(BLOCK_SIZE) === 1);
|
||||
dbg_assert(round_byte_size_to_block_size(BLOCK_SIZE + 1) === 2);
|
||||
dbg_assert(round_byte_size_to_block_size(2 * BLOCK_SIZE) === 2);
|
||||
dbg_assert(round_byte_size_to_block_size(2 * BLOCK_SIZE + 1) === 3);
|
||||
dbg_assert(round_byte_size_to_block_size(10 * BLOCK_SIZE + 1) === 11);
|
||||
|
||||
function to_msdos_filename(name)
|
||||
{
|
||||
const dot = name.lastIndexOf(".");
|
||||
if(dot === -1) return name.substr(0, 8);
|
||||
return name.substr(0, Math.min(8, dot)) + "." + name.substr(dot + 1, 3);
|
||||
}
|
||||
|
||||
dbg_assert(to_msdos_filename("abcdefghijkl.qwerty") === "abcdefgh.qwe");
|
||||
dbg_assert(to_msdos_filename("abcdefghijkl") === "abcdefgh");
|
||||
|
||||
function sanitise_filename(name)
|
||||
{
|
||||
return to_msdos_filename(name.toUpperCase().replace(/[^A-Z0-9_.]/g, ""));
|
||||
}
|
||||
|
||||
// layout:
|
||||
// (lba = one block of BLOCK_SIZE bytes)
|
||||
// LBA | contents
|
||||
// ------+--------
|
||||
// 0..15 | System Area (could be used for mbr, but not used by us)
|
||||
// 16 | Primary Volume Descriptor
|
||||
// 17 | Volume Descriptor Set Terminator
|
||||
// 18 | empty
|
||||
// 19 | Little Endian Path Table
|
||||
// 20 | empty
|
||||
// 21 | Big Endian Path Table
|
||||
// 22 | empty
|
||||
// 23 | Root directory
|
||||
// 24..n | File contents
|
||||
const SYSTEM_AREA_SIZE = 16 * BLOCK_SIZE;
|
||||
const PRIMARY_VOLUME_LBA = 16;
|
||||
const VOLUME_SET_TERMINATOR_LBA = 17;
|
||||
const LE_PATH_TABLE_LBA = 19;
|
||||
const BE_PATH_TABLE_LBA = 21;
|
||||
const ROOT_DIRECTORY_LBA = 23;
|
||||
const LE_PATH_TABLE_SIZE = BLOCK_SIZE;
|
||||
const BE_PATH_TABLE_SIZE = BLOCK_SIZE;
|
||||
const ROOT_DIRECTORY_SIZE = BLOCK_SIZE;
|
||||
|
||||
let next_file_lba = 24;
|
||||
files = files.map(({ name, contents }) => {
|
||||
const lba = next_file_lba;
|
||||
next_file_lba += round_byte_size_to_block_size(contents.length);
|
||||
name = to_msdos_filename(name);
|
||||
return { name, contents, lba };
|
||||
});
|
||||
|
||||
const N_LBAS = next_file_lba;
|
||||
const total_size = N_LBAS * BLOCK_SIZE;
|
||||
|
||||
const buffer = {
|
||||
buffer: new Uint8Array(total_size),
|
||||
offset: SYSTEM_AREA_SIZE,
|
||||
};
|
||||
|
||||
// LBA 16: Primary Volume Descriptor
|
||||
dbg_assert(buffer.offset === PRIMARY_VOLUME_LBA * BLOCK_SIZE);
|
||||
write8(buffer, 0x01); // Volume Descriptor type: Primary Volume Descriptor
|
||||
write_ascii(buffer, "CD001"); // Always CD001
|
||||
write8(buffer, 0x01); // Version
|
||||
write8(buffer, 0x00); // unused
|
||||
write_padded_ascii(buffer, 32, "V86"); // System Identifier
|
||||
write_padded_ascii(buffer, 32, "CDROM"); // Identification of this volume
|
||||
skip(buffer, 8); // unused
|
||||
write_lebe32(buffer, N_LBAS);
|
||||
skip(buffer, 32); // unused
|
||||
dbg_assert(buffer.offset === 0x8000 + 120);
|
||||
|
||||
write_lebe16(buffer, 1); // Volume Set Size
|
||||
write_lebe16(buffer, 1); // Volume Sequence Number
|
||||
dbg_assert(buffer.offset === 0x8080);
|
||||
|
||||
write_lebe16(buffer, BLOCK_SIZE);
|
||||
|
||||
write_lebe32(buffer, 10); // Path Table Size
|
||||
write_le32(buffer, LE_PATH_TABLE_LBA); // Location of Type-L Path Table
|
||||
write_le32(buffer, 0); // Location of the Optional Type-L Path Table
|
||||
write_be32(buffer, BE_PATH_TABLE_LBA); // Location of Type-M Path Table
|
||||
write_be32(buffer, 0); // Location of the Optional Type-M Path Table
|
||||
dbg_assert(buffer.offset === 0x8000 + 156);
|
||||
|
||||
// Directory entry for the root directory
|
||||
write_special_directory_record(buffer, "\x00", ROOT_DIRECTORY_LBA, 0x800);
|
||||
dbg_assert(buffer.offset === 0x8000 + 190);
|
||||
|
||||
fill(buffer, 128, 0x20); // Volume Set Identifier
|
||||
fill(buffer, 128, 0x20); // Publisher Identifier
|
||||
fill(buffer, 128, 0x20); // Data Preparer Identifier
|
||||
fill(buffer, 128, 0x20); // Application Identifier
|
||||
fill(buffer, 37, 0x20); // Copyright File Identifier
|
||||
fill(buffer, 37, 0x20); // Abstract File Identifier
|
||||
fill(buffer, 37, 0x20); // Bibliographic File Identifier
|
||||
|
||||
dbg_assert(buffer.offset === 0x8000 + 813);
|
||||
|
||||
write_dummy_date_ascii(buffer); // Volume Creation Date and Time
|
||||
write_dummy_date_ascii(buffer); // Volume Modification Date and Time
|
||||
write_dummy_date_ascii(buffer); // Volume Expiration Date and Time
|
||||
write_dummy_date_ascii(buffer); // Volume Effective Date and Time
|
||||
|
||||
write8(buffer, 0x01); // File Structure Version
|
||||
dbg_assert(buffer.offset === 0x8000 + 882);
|
||||
|
||||
write8(buffer, 0x00); // Unused
|
||||
skip(buffer, 512); // Application Used
|
||||
skip(buffer, 653); // Reserved
|
||||
|
||||
// LBA 17: Volume Descriptor Set Terminator
|
||||
dbg_assert(buffer.offset === VOLUME_SET_TERMINATOR_LBA * BLOCK_SIZE);
|
||||
write8(buffer, 0xFF); // 0xFF: Volume Descriptor Set Terminator
|
||||
write_ascii(buffer, "CD001"); // Always CD001
|
||||
write8(buffer, 0x01); // Version
|
||||
|
||||
// LBA 19: Little Endian Path Table
|
||||
buffer.offset = LE_PATH_TABLE_LBA * BLOCK_SIZE;
|
||||
write8(buffer, 0x01); // Length of Directory Identifier
|
||||
write8(buffer, 0x00); // Extended Attribute Record Length
|
||||
write_le32(buffer, ROOT_DIRECTORY_LBA); // Location of Extent (LBA)
|
||||
write_le16(buffer, 1); // Directory number of parent directory
|
||||
write_ascii(buffer, "\x00"); // file name
|
||||
dbg_assert(buffer.offset < LE_PATH_TABLE_LBA * BLOCK_SIZE + LE_PATH_TABLE_SIZE);
|
||||
|
||||
// LBA 21: Big Endian Path Table
|
||||
buffer.offset = BE_PATH_TABLE_LBA * BLOCK_SIZE;
|
||||
write8(buffer, 0x01); // Length of Directory Identifier
|
||||
write8(buffer, 0x00); // Extended Attribute Record Length
|
||||
write_be32(buffer, ROOT_DIRECTORY_LBA); // Location of Extent (LBA)
|
||||
write_be16(buffer, 1); // Directory number of parent directory
|
||||
write_ascii(buffer, "\x00"); // file name
|
||||
dbg_assert(buffer.offset < BE_PATH_TABLE_LBA * BLOCK_SIZE + BE_PATH_TABLE_SIZE);
|
||||
|
||||
// LBA 23: root directory
|
||||
buffer.offset = ROOT_DIRECTORY_LBA * BLOCK_SIZE;
|
||||
write_special_directory_record(buffer, "\x00", ROOT_DIRECTORY_LBA, 0x800); // "."
|
||||
write_special_directory_record(buffer, "\x01", ROOT_DIRECTORY_LBA, 0x800); // ".."
|
||||
for(const { name, contents, lba } of files)
|
||||
{
|
||||
write_file_record(buffer, name, lba, contents.length);
|
||||
}
|
||||
// TODO: this assertion can fail if too many files are used as input
|
||||
// ROOT_DIRECTORY_SIZE should be choosen dynamically
|
||||
dbg_assert(buffer.offset < ROOT_DIRECTORY_LBA * BLOCK_SIZE + ROOT_DIRECTORY_SIZE);
|
||||
|
||||
// file contents
|
||||
for(let { contents, lba } of files)
|
||||
{
|
||||
buffer.buffer.set(contents, lba * BLOCK_SIZE);
|
||||
}
|
||||
|
||||
return buffer.buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} buffer
|
||||
*/
|
||||
export function is_probably_iso9660_file(buffer)
|
||||
{
|
||||
return (
|
||||
buffer.length >= 17 * BLOCK_SIZE &&
|
||||
buffer[BLOCK_SIZE + 0] === 1 && // Primary Volume Descriptor
|
||||
buffer[BLOCK_SIZE + 1] === 67 && // "C"
|
||||
buffer[BLOCK_SIZE + 2] === 68 && // "D"
|
||||
buffer[BLOCK_SIZE + 3] === 48 && // "0"
|
||||
buffer[BLOCK_SIZE + 4] === 48 && // "0"
|
||||
buffer[BLOCK_SIZE + 5] === 49 // "1"
|
||||
);
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
"use strict";
|
||||
import { h } from "./lib.js";
|
||||
import { dbg_assert, dbg_log } from "./log.js";
|
||||
|
||||
|
||||
// https://www.kernel.org/doc/Documentation/x86/boot.txt
|
||||
|
||||
|
|
@ -37,7 +39,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);
|
||||
|
||||
|
|
|
|||
475
src/lib.js
475
src/lib.js
|
|
@ -1,52 +1,20 @@
|
|||
"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)
|
||||
{
|
||||
return Array(size).fill(0);
|
||||
};
|
||||
|
||||
// generates [0, 1, 2, ..., size-1]
|
||||
v86util.range = function(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 +46,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 +57,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 +112,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 +129,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 +180,7 @@ else
|
|||
* @param {number} x
|
||||
* @return {number}
|
||||
*/
|
||||
v86util.int_log2 = function(x)
|
||||
int_log2 = function(x)
|
||||
{
|
||||
x >>>= 0;
|
||||
dbg_assert(x > 0);
|
||||
|
|
@ -236,28 +213,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)
|
||||
if(typeof DEBUG !== "undefined" && 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 +243,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 +314,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 +442,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 +453,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 +479,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 +491,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 +505,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,187 +513,249 @@ 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;
|
||||
export var get_file_size;
|
||||
|
||||
if(typeof XMLHttpRequest === "undefined")
|
||||
if(typeof XMLHttpRequest === "undefined" ||
|
||||
typeof process !== "undefined" && process.versions && process.versions.node)
|
||||
{
|
||||
v86util.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
|
||||
{
|
||||
v86util.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)
|
||||
{
|
||||
const fs = require("fs");
|
||||
|
||||
if(options.range)
|
||||
{
|
||||
dbg_assert(!options.as_json);
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unicode mappings of supported 8-bit code pages.
|
||||
* Each mapping is a string of 256 Unicode symbols used as a lookup table for 8-bit character codes.
|
||||
*
|
||||
* Supported mappings and their encoding labels:
|
||||
* - "cp437": CP437 (MS-DOS Latin US), default
|
||||
* - "cp858": CP858 (Western Europe), the lower 128 bytes are identical to CP437
|
||||
* - "ascii": ASCII (7-Bit), same as CP437 with lower 32 and upper 128 bytes mapped to "."
|
||||
*
|
||||
* @type {Object<string, string>}
|
||||
*/
|
||||
const CHARMAPS =
|
||||
{
|
||||
cp437: " ☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ",
|
||||
cp858: "ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø×ƒáíóúñѪº¿®¬½¼¡«»░▒▓│┤ÁÂÀ©╣║╗╝¢¥┐└┴┬├─┼ãÃ╚╔╩╦╠═╬¤ðÐÊËÈ€ÍÎÏ┘┌█▄¦Ì▀ÓßÔÒõÕµþÞÚÛÙýݯ´±‗¾¶§÷¸°¨·¹³²■ "
|
||||
};
|
||||
|
||||
CHARMAPS.cp858 = CHARMAPS.cp437.slice(0, 128) + CHARMAPS.cp858;
|
||||
CHARMAPS.ascii = CHARMAPS.cp437.split("").map((c, i) => i > 31 && i < 128 ? c : ".").join("");
|
||||
|
||||
/**
|
||||
* Return charmap for given encoding, default to CP437 if encoding is falsey or not defined in CHARMAPS.
|
||||
*
|
||||
* @param {string} encoding
|
||||
* @return {!string}
|
||||
*/
|
||||
export function get_charmap(encoding)
|
||||
{
|
||||
return encoding && CHARMAPS[encoding] ? CHARMAPS[encoding] : CHARMAPS.cp437;
|
||||
}
|
||||
|
|
|
|||
47
src/log.js
47
src/log.js
|
|
@ -1,6 +1,26 @@
|
|||
"use strict";
|
||||
if(typeof DEBUG === "undefined")
|
||||
{
|
||||
globalThis.DEBUG = true;
|
||||
}
|
||||
|
||||
var log_data = [];
|
||||
import { LOG_NAMES } from "./const.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 = [];
|
||||
|
||||
function do_the_log(message)
|
||||
{
|
||||
|
|
@ -16,17 +36,16 @@ function do_the_log(message)
|
|||
|
||||
/**
|
||||
* @type {function((string|number), number=)}
|
||||
* @const
|
||||
*/
|
||||
var dbg_log = (function()
|
||||
export const dbg_log = (function()
|
||||
{
|
||||
if(!DEBUG)
|
||||
{
|
||||
return function() {};
|
||||
}
|
||||
|
||||
/** @const @type {Object.<number, string>} */
|
||||
var dbg_names = LOG_NAMES.reduce(function(a, x)
|
||||
/** @type {Object.<number, string>} */
|
||||
const dbg_names = LOG_NAMES.reduce(function(a, x)
|
||||
{
|
||||
a[x[0]] = x[1];
|
||||
return a;
|
||||
|
|
@ -47,7 +66,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 +79,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 +109,7 @@ var dbg_log = (function()
|
|||
/**
|
||||
* @param {number=} level
|
||||
*/
|
||||
function dbg_trace(level)
|
||||
export function dbg_trace(level)
|
||||
{
|
||||
if(!DEBUG) return;
|
||||
|
||||
|
|
@ -102,7 +121,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 +132,7 @@ function dbg_assert(cond, msg, level)
|
|||
}
|
||||
|
||||
|
||||
function dbg_assert_failed(msg)
|
||||
export function dbg_assert_failed(msg)
|
||||
{
|
||||
debugger;
|
||||
console.trace();
|
||||
|
|
|
|||
27
src/main.js
27
src/main.js
|
|
@ -1,10 +1,12 @@
|
|||
"use strict";
|
||||
import { CPU } from "./cpu.js";
|
||||
import { save_state, restore_state } from "./state.js";
|
||||
export { V86 } from "./browser/starter.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);
|
||||
|
|
@ -111,6 +114,17 @@ if(typeof process !== "undefined")
|
|||
v86.prototype.register_yield = function() {};
|
||||
v86.prototype.unregister_yield = function() {};
|
||||
}
|
||||
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);
|
||||
globalThis["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
|
||||
|
|
@ -152,8 +166,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)
|
||||
// {
|
||||
|
|
@ -196,16 +209,16 @@ 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 */
|
||||
if(typeof performance === "object" && performance.now)
|
||||
{
|
||||
v86.microtick = performance.now.bind(performance);
|
||||
|
|
|
|||
|
|
@ -1,98 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
|
||||
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.<number>|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);
|
||||
};
|
||||
107
src/ne2k.js
107
src/ne2k.js
|
|
@ -1,59 +1,66 @@
|
|||
"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;
|
||||
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.
|
||||
|
|
@ -226,7 +233,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 +295,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;
|
||||
|
|
@ -311,8 +318,7 @@ function Ne2k(cpu, bus, preserve_mac_from_state_image, mac_address_translation,
|
|||
|
||||
this.name = "ne2k";
|
||||
|
||||
/** @const */
|
||||
var use_pci = true;
|
||||
const use_pci = true;
|
||||
|
||||
if(use_pci)
|
||||
{
|
||||
|
|
@ -390,7 +396,12 @@ function Ne2k(cpu, bus, preserve_mac_from_state_image, mac_address_translation,
|
|||
{
|
||||
dbg_log("Read cmd", LOG_NET);
|
||||
return this.cr;
|
||||
});
|
||||
}, function()
|
||||
{
|
||||
dbg_log("Read16 cmd", LOG_NET);
|
||||
return this.cr;
|
||||
}
|
||||
);
|
||||
|
||||
io.register_write(this.port | E8390_CMD, this, function(data_byte)
|
||||
{
|
||||
|
|
|
|||
45
src/pci.js
45
src/pci.js
|
|
@ -1,16 +1,20 @@
|
|||
"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
|
||||
/** @const */ PCI_CONFIG_ADDRESS = 0xCF8,
|
||||
/** @const */ PCI_CONFIG_DATA = 0xCFC;
|
||||
export const PCI_CONFIG_ADDRESS = 0xCF8;
|
||||
export const PCI_CONFIG_DATA = 0xCFC;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {CPU} cpu
|
||||
*/
|
||||
function PCI(cpu)
|
||||
export function PCI(cpu)
|
||||
{
|
||||
this.pci_addr = new Uint8Array(4);
|
||||
this.pci_value = new Uint8Array(4);
|
||||
|
|
@ -390,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)
|
||||
|
|
@ -491,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);
|
||||
|
||||
|
|
@ -514,6 +521,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 = [];
|
||||
|
|
@ -553,17 +561,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)
|
||||
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);
|
||||
|
|
@ -572,18 +569,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), LOG_PCI);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
26
src/pit.js
26
src/pit.js
|
|
@ -1,17 +1,21 @@
|
|||
"use strict";
|
||||
import { v86 } from "./main.js";
|
||||
import { LOG_PIT } from "./const.js";
|
||||
import { h } from "./lib.js";
|
||||
import { dbg_log } from "./log.js";
|
||||
|
||||
/**
|
||||
* @const
|
||||
* In kHz
|
||||
*/
|
||||
var OSCILLATOR_FREQ = 1193.1816666; // 1.193182 MHz
|
||||
// For Types Only
|
||||
import { CPU } from "./cpu.js";
|
||||
|
||||
|
||||
// In kHz
|
||||
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;
|
||||
|
|
@ -304,13 +308,13 @@ PIT.prototype.port43_write = function(reg_byte)
|
|||
|
||||
if(read_mode === 1)
|
||||
{
|
||||
// msb
|
||||
this.counter_next_low[i] = 0;
|
||||
// lsb
|
||||
this.counter_next_low[i] = 1;
|
||||
}
|
||||
else if(read_mode === 2)
|
||||
{
|
||||
// lsb
|
||||
this.counter_next_low[i] = 1;
|
||||
// msb
|
||||
this.counter_next_low[i] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
83
src/ps2.js
83
src/ps2.js
|
|
@ -1,6 +1,12 @@
|
|||
"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 +14,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;
|
||||
|
|
@ -16,6 +22,40 @@ 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;
|
||||
|
||||
|
|
@ -101,42 +141,13 @@ 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()
|
||||
{
|
||||
|
|
@ -286,7 +297,8 @@ PS2.prototype.mouse_send_delta = function(delta_x, delta_y)
|
|||
|
||||
// note: delta_x or delta_y can be floating point numbers
|
||||
|
||||
var factor = this.resolution * this.sample_rate / 80;
|
||||
//const factor = this.resolution * this.sample_rate / 80;
|
||||
const factor = 1;
|
||||
|
||||
this.mouse_delta_x += delta_x * factor;
|
||||
this.mouse_delta_y += delta_y * factor;
|
||||
|
|
@ -298,8 +310,7 @@ PS2.prototype.mouse_send_delta = function(delta_x, delta_y)
|
|||
|
||||
if(change_x || change_y)
|
||||
{
|
||||
var now = Date.now();
|
||||
|
||||
//var now = Date.now();
|
||||
//if(now - this.last_mouse_packet < 1000 / this.sample_rate)
|
||||
//{
|
||||
// // TODO: set timeout
|
||||
|
|
|
|||
141
src/rtc.js
141
src/rtc.js
|
|
@ -1,57 +1,66 @@
|
|||
"use strict";
|
||||
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_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;
|
||||
// For Types Only
|
||||
import { CPU } from "./cpu.js";
|
||||
import { DMA } from "./dma.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;
|
||||
|
||||
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_DIAG_STATUS = 0x0e;
|
||||
export const CMOS_RESET_CODE = 0x0f;
|
||||
|
||||
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
|
||||
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
|
||||
*/
|
||||
function RTC(cpu)
|
||||
export function RTC(cpu)
|
||||
{
|
||||
/** @const @type {CPU} */
|
||||
this.cpu = cpu;
|
||||
|
|
@ -78,8 +87,13 @@ function RTC(cpu)
|
|||
this.cmos_b = 2;
|
||||
this.cmos_c = 0;
|
||||
|
||||
this.cmos_diag_status = 0;
|
||||
|
||||
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;
|
||||
|
|
@ -106,6 +120,9 @@ 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;
|
||||
state[14] = this.cmos_diag_status;
|
||||
|
||||
return state;
|
||||
};
|
||||
|
|
@ -124,6 +141,9 @@ 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;
|
||||
this.cmos_diag_status = state[14] || 0;
|
||||
};
|
||||
|
||||
RTC.prototype.timer = function(time, legacy_mode)
|
||||
|
|
@ -147,6 +167,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;
|
||||
|
||||
|
|
@ -158,6 +185,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;
|
||||
};
|
||||
|
|
@ -285,7 +316,11 @@ RTC.prototype.cmos_port_read = function()
|
|||
return c;
|
||||
|
||||
case CMOS_STATUS_D:
|
||||
return 0;
|
||||
return 1 << 7; // CMOS battery charged
|
||||
|
||||
case CMOS_DIAG_STATUS:
|
||||
dbg_log("cmos diagnostic status read", LOG_RTC);
|
||||
return this.cmos_diag_status;
|
||||
|
||||
case CMOS_CENTURY:
|
||||
case CMOS_CENTURY2:
|
||||
|
|
@ -310,6 +345,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();
|
||||
|
|
@ -336,11 +376,19 @@ 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;
|
||||
|
||||
case CMOS_DIAG_STATUS:
|
||||
this.cmos_diag_status = data_byte;
|
||||
break;
|
||||
|
||||
case CMOS_RTC_SECONDS_ALARM:
|
||||
case CMOS_RTC_MINUTES_ALARM:
|
||||
case CMOS_RTC_HOURS_ALARM:
|
||||
|
|
@ -351,6 +399,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;
|
||||
};
|
||||
|
||||
|
|
|
|||
658
src/rust/cpu/apic.rs
Normal file
658
src/rust/cpu/apic.rs
Normal file
|
|
@ -0,0 +1,658 @@
|
|||
// See Intel's System Programming Guide
|
||||
|
||||
use crate::cpu::{cpu::js, global_pointers::acpi_enabled, ioapic};
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
const APIC_LOG_VERBOSE: bool = false;
|
||||
|
||||
// should probably be kept in sync with TSC_RATE in cpu.rs
|
||||
const APIC_TIMER_FREQ: f64 = 1.0 * 1000.0 * 1000.0;
|
||||
|
||||
const APIC_TIMER_MODE_MASK: u32 = 3 << 17;
|
||||
|
||||
const APIC_TIMER_MODE_ONE_SHOT: u32 = 0;
|
||||
const APIC_TIMER_MODE_PERIODIC: u32 = 1 << 17;
|
||||
|
||||
const _APIC_TIMER_MODE_TSC: u32 = 2 << 17;
|
||||
|
||||
const DELIVERY_MODES: [&str; 8] = [
|
||||
"Fixed (0)",
|
||||
"Lowest Prio (1)",
|
||||
"SMI (2)",
|
||||
"Reserved (3)",
|
||||
"NMI (4)",
|
||||
"INIT (5)",
|
||||
"Reserved (6)",
|
||||
"ExtINT (7)",
|
||||
];
|
||||
|
||||
const DESTINATION_MODES: [&str; 2] = ["physical", "logical"];
|
||||
|
||||
const IOAPIC_CONFIG_MASKED: u32 = 0x10000;
|
||||
|
||||
const IOAPIC_DELIVERY_INIT: u8 = 5;
|
||||
const IOAPIC_DELIVERY_NMI: u8 = 4;
|
||||
const IOAPIC_DELIVERY_FIXED: u8 = 0;
|
||||
|
||||
// keep in sync with cpu.js
|
||||
#[allow(dead_code)]
|
||||
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);
|
||||
const _: () = assert!(std::mem::offset_of!(Apic, spurious_vector) == 40 * 4);
|
||||
const _: () = assert!(std::mem::offset_of!(Apic, lvt_thermal_sensor) == 45 * 4);
|
||||
const _: () = assert!(std::mem::size_of::<Apic>() == APIC_STRUCT_SIZE);
|
||||
#[repr(C)]
|
||||
pub struct Apic {
|
||||
apic_id: u32,
|
||||
timer_divider: u32,
|
||||
timer_divider_shift: u32,
|
||||
timer_initial_count: u32,
|
||||
timer_current_count: u32,
|
||||
timer_last_tick: f64,
|
||||
lvt_timer: u32,
|
||||
lvt_perf_counter: u32,
|
||||
lvt_int0: u32,
|
||||
lvt_int1: u32,
|
||||
lvt_error: u32,
|
||||
tpr: u32,
|
||||
icr0: u32,
|
||||
icr1: u32,
|
||||
irr: [u32; 8],
|
||||
isr: [u32; 8],
|
||||
tmr: [u32; 8],
|
||||
spurious_vector: u32,
|
||||
destination_format: u32,
|
||||
local_destination: u32,
|
||||
error: u32,
|
||||
read_error: u32,
|
||||
lvt_thermal_sensor: u32,
|
||||
}
|
||||
|
||||
static APIC: Mutex<Apic> = Mutex::new(Apic {
|
||||
apic_id: 0,
|
||||
timer_divider: 0,
|
||||
timer_divider_shift: 1,
|
||||
timer_initial_count: 0,
|
||||
timer_current_count: 0,
|
||||
timer_last_tick: 0.0,
|
||||
lvt_timer: IOAPIC_CONFIG_MASKED,
|
||||
lvt_thermal_sensor: IOAPIC_CONFIG_MASKED,
|
||||
lvt_perf_counter: IOAPIC_CONFIG_MASKED,
|
||||
lvt_int0: IOAPIC_CONFIG_MASKED,
|
||||
lvt_int1: IOAPIC_CONFIG_MASKED,
|
||||
lvt_error: IOAPIC_CONFIG_MASKED,
|
||||
tpr: 0,
|
||||
icr0: 0,
|
||||
icr1: 0,
|
||||
irr: [0; 8],
|
||||
isr: [0; 8],
|
||||
tmr: [0; 8],
|
||||
spurious_vector: 0xFE,
|
||||
destination_format: !0,
|
||||
local_destination: 0,
|
||||
error: 0,
|
||||
read_error: 0,
|
||||
});
|
||||
|
||||
pub fn get_apic() -> MutexGuard<'static, Apic> { APIC.try_lock().unwrap() }
|
||||
|
||||
#[no_mangle]
|
||||
pub fn get_apic_addr() -> u32 { &raw mut *get_apic() as u32 }
|
||||
|
||||
pub fn read32(addr: u32) -> u32 {
|
||||
if unsafe { !*acpi_enabled } {
|
||||
return 0;
|
||||
}
|
||||
read32_internal(&mut get_apic(), addr)
|
||||
}
|
||||
|
||||
fn read32_internal(apic: &mut Apic, addr: u32) -> u32 {
|
||||
match addr {
|
||||
0x20 => {
|
||||
dbg_log!("APIC read id");
|
||||
apic.apic_id
|
||||
},
|
||||
|
||||
0x30 => {
|
||||
// version
|
||||
dbg_log!("APIC read version");
|
||||
0x50014
|
||||
},
|
||||
|
||||
0x80 => {
|
||||
if APIC_LOG_VERBOSE {
|
||||
dbg_log!("APIC read tpr");
|
||||
}
|
||||
apic.tpr
|
||||
},
|
||||
|
||||
0xB0 => {
|
||||
// write-only (written by DSL)
|
||||
if APIC_LOG_VERBOSE {
|
||||
dbg_log!("APIC read eoi register");
|
||||
}
|
||||
0
|
||||
},
|
||||
|
||||
0xD0 => {
|
||||
dbg_log!("Read local destination");
|
||||
apic.local_destination
|
||||
},
|
||||
|
||||
0xE0 => {
|
||||
dbg_log!("Read destination format");
|
||||
apic.destination_format
|
||||
},
|
||||
|
||||
0xF0 => apic.spurious_vector,
|
||||
|
||||
0x100 | 0x110 | 0x120 | 0x130 | 0x140 | 0x150 | 0x160 | 0x170 => {
|
||||
let index = ((addr - 0x100) >> 4) as usize;
|
||||
dbg_log!("Read isr {}: {:08x}", index, apic.isr[index] as u32);
|
||||
apic.isr[index]
|
||||
},
|
||||
|
||||
0x180 | 0x190 | 0x1A0 | 0x1B0 | 0x1C0 | 0x1D0 | 0x1E0 | 0x1F0 => {
|
||||
let index = ((addr - 0x180) >> 4) as usize;
|
||||
dbg_log!("Read tmr {}: {:08x}", index, apic.tmr[index] as u32);
|
||||
apic.tmr[index]
|
||||
},
|
||||
|
||||
0x200 | 0x210 | 0x220 | 0x230 | 0x240 | 0x250 | 0x260 | 0x270 => {
|
||||
let index = ((addr - 0x200) >> 4) as usize;
|
||||
dbg_log!("Read irr {}: {:08x}", index, apic.irr[index] as u32);
|
||||
apic.irr[index]
|
||||
},
|
||||
|
||||
0x280 => {
|
||||
dbg_log!("Read error: {:08x}", apic.read_error);
|
||||
apic.read_error
|
||||
},
|
||||
|
||||
0x300 => {
|
||||
if APIC_LOG_VERBOSE {
|
||||
dbg_log!("APIC read icr0");
|
||||
}
|
||||
apic.icr0
|
||||
},
|
||||
|
||||
0x310 => {
|
||||
dbg_log!("APIC read icr1");
|
||||
apic.icr1
|
||||
},
|
||||
|
||||
0x320 => {
|
||||
if APIC_LOG_VERBOSE {
|
||||
dbg_log!("read timer lvt");
|
||||
}
|
||||
apic.lvt_timer
|
||||
},
|
||||
|
||||
0x330 => {
|
||||
dbg_log!("read lvt thermal sensor");
|
||||
apic.lvt_thermal_sensor
|
||||
},
|
||||
|
||||
0x340 => {
|
||||
dbg_log!("read lvt perf counter");
|
||||
apic.lvt_perf_counter
|
||||
},
|
||||
|
||||
0x350 => {
|
||||
dbg_log!("read lvt int0");
|
||||
apic.lvt_int0
|
||||
},
|
||||
|
||||
0x360 => {
|
||||
dbg_log!("read lvt int1");
|
||||
apic.lvt_int1
|
||||
},
|
||||
|
||||
0x370 => {
|
||||
dbg_log!("read lvt error");
|
||||
apic.lvt_error
|
||||
},
|
||||
|
||||
0x3E0 => {
|
||||
// divider
|
||||
dbg_log!("read timer divider");
|
||||
apic.timer_divider
|
||||
},
|
||||
|
||||
0x380 => {
|
||||
dbg_log!("read timer initial count");
|
||||
apic.timer_initial_count
|
||||
},
|
||||
|
||||
0x390 => {
|
||||
let now = unsafe { js::microtick() };
|
||||
if apic.timer_last_tick > now {
|
||||
// should only happen after restore_state
|
||||
dbg_log!("warning: APIC last_tick is in the future, resetting");
|
||||
apic.timer_last_tick = now;
|
||||
}
|
||||
let diff = now - apic.timer_last_tick;
|
||||
let diff_in_ticks = diff * APIC_TIMER_FREQ / (1 << apic.timer_divider_shift) as f64;
|
||||
dbg_assert!(diff_in_ticks >= 0.0);
|
||||
let diff_in_ticks = diff_in_ticks as u64;
|
||||
let result = if diff_in_ticks < apic.timer_initial_count as u64 {
|
||||
apic.timer_initial_count - diff_in_ticks as u32
|
||||
}
|
||||
else {
|
||||
let mode = apic.lvt_timer & APIC_TIMER_MODE_MASK;
|
||||
if mode == APIC_TIMER_MODE_PERIODIC {
|
||||
apic.timer_initial_count
|
||||
- (diff_in_ticks % (apic.timer_initial_count as u64 + 1)) as u32
|
||||
}
|
||||
else if mode == APIC_TIMER_MODE_ONE_SHOT {
|
||||
0
|
||||
}
|
||||
else {
|
||||
dbg_assert!(false, "apic unimplemented timer mode: {:x}", mode);
|
||||
0
|
||||
}
|
||||
};
|
||||
if APIC_LOG_VERBOSE {
|
||||
dbg_log!("read timer current count: {}", result);
|
||||
}
|
||||
result
|
||||
},
|
||||
|
||||
_ => {
|
||||
dbg_log!("APIC read {:x}", addr);
|
||||
dbg_assert!(false);
|
||||
0
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write32(addr: u32, value: u32) {
|
||||
if unsafe { !*acpi_enabled } {
|
||||
return;
|
||||
}
|
||||
write32_internal(&mut get_apic(), addr, value)
|
||||
}
|
||||
|
||||
fn write32_internal(apic: &mut Apic, addr: u32, value: u32) {
|
||||
match addr {
|
||||
0x20 => {
|
||||
dbg_log!("APIC write id: {:08x}", value >> 8);
|
||||
apic.apic_id = value;
|
||||
},
|
||||
|
||||
0x30 => {
|
||||
// version
|
||||
dbg_log!("APIC write version: {:08x}, ignored", value);
|
||||
},
|
||||
|
||||
0x80 => {
|
||||
if APIC_LOG_VERBOSE {
|
||||
dbg_log!("Set tpr: {:02x}", value & 0xFF);
|
||||
}
|
||||
apic.tpr = value & 0xFF;
|
||||
},
|
||||
|
||||
0xB0 => {
|
||||
if let Some(highest_isr) = highest_isr(apic) {
|
||||
if APIC_LOG_VERBOSE {
|
||||
dbg_log!("eoi: {:08x} for vector {:x}", value, highest_isr);
|
||||
}
|
||||
register_clear_bit(&mut apic.isr, highest_isr);
|
||||
if register_get_bit(&apic.tmr, highest_isr) {
|
||||
// Send eoi to all IO APICs
|
||||
ioapic::remote_eoi(apic, highest_isr);
|
||||
}
|
||||
}
|
||||
else {
|
||||
dbg_log!("Bad eoi: No isr set");
|
||||
}
|
||||
},
|
||||
|
||||
0xD0 => {
|
||||
dbg_log!("Set local destination: {:08x}", value);
|
||||
apic.local_destination = value & 0xFF000000;
|
||||
},
|
||||
|
||||
0xE0 => {
|
||||
dbg_log!("Set destination format: {:08x}", value);
|
||||
apic.destination_format = value | 0xFFFFFF;
|
||||
},
|
||||
|
||||
0xF0 => {
|
||||
dbg_log!("Set spurious vector: {:08x}", value);
|
||||
apic.spurious_vector = value;
|
||||
},
|
||||
|
||||
0x280 => {
|
||||
// updated readable error register with real error
|
||||
dbg_log!("Write error: {:08x}", value);
|
||||
apic.read_error = apic.error;
|
||||
apic.error = 0;
|
||||
},
|
||||
|
||||
0x300 => {
|
||||
let vector = (value & 0xFF) as u8;
|
||||
let delivery_mode = ((value >> 8) & 7) as u8;
|
||||
let destination_mode = ((value >> 11) & 1) as u8;
|
||||
let is_level = value & ioapic::IOAPIC_CONFIG_TRIGGER_MODE_LEVEL
|
||||
== ioapic::IOAPIC_CONFIG_TRIGGER_MODE_LEVEL;
|
||||
let destination_shorthand = (value >> 18) & 3;
|
||||
let destination = (apic.icr1 >> 24) as u8;
|
||||
dbg_log!(
|
||||
"APIC write icr0: {:08x} vector={:02x} destination_mode={} delivery_mode={} destination_shorthand={}",
|
||||
value,
|
||||
vector,
|
||||
DESTINATION_MODES[destination_mode as usize],
|
||||
DELIVERY_MODES[delivery_mode as usize],
|
||||
["no", "self", "all with self", "all without self"][destination_shorthand as usize]
|
||||
);
|
||||
|
||||
let mut value = value;
|
||||
value &= !(1 << 12);
|
||||
apic.icr0 = value;
|
||||
|
||||
if destination_shorthand == 0 {
|
||||
// no shorthand
|
||||
route(
|
||||
apic,
|
||||
vector,
|
||||
delivery_mode,
|
||||
is_level,
|
||||
destination,
|
||||
destination_mode,
|
||||
);
|
||||
}
|
||||
else if destination_shorthand == 1 {
|
||||
// self
|
||||
deliver(apic, vector, IOAPIC_DELIVERY_FIXED, is_level);
|
||||
}
|
||||
else if destination_shorthand == 2 {
|
||||
// all including self
|
||||
deliver(apic, vector, delivery_mode, is_level);
|
||||
}
|
||||
else if destination_shorthand == 3 {
|
||||
// all but self
|
||||
}
|
||||
else {
|
||||
dbg_assert!(false);
|
||||
}
|
||||
},
|
||||
|
||||
0x310 => {
|
||||
dbg_log!("APIC write icr1: {:08x}", value);
|
||||
apic.icr1 = value;
|
||||
},
|
||||
|
||||
0x320 => {
|
||||
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;
|
||||
},
|
||||
|
||||
0x330 => {
|
||||
dbg_log!("lvt thermal sensor: {:08x}", value);
|
||||
apic.lvt_thermal_sensor = value;
|
||||
},
|
||||
|
||||
0x340 => {
|
||||
dbg_log!("lvt perf counter: {:08x}", value);
|
||||
apic.lvt_perf_counter = value;
|
||||
},
|
||||
|
||||
0x350 => {
|
||||
dbg_log!("lvt int0: {:08x}", value);
|
||||
apic.lvt_int0 = value;
|
||||
},
|
||||
|
||||
0x360 => {
|
||||
dbg_log!("lvt int1: {:08x}", value);
|
||||
apic.lvt_int1 = value;
|
||||
},
|
||||
|
||||
0x370 => {
|
||||
dbg_log!("lvt error: {:08x}", value);
|
||||
apic.lvt_error = value;
|
||||
},
|
||||
|
||||
0x3E0 => {
|
||||
apic.timer_divider = value;
|
||||
|
||||
let divide_shift = (value & 0b11) | ((value & 0b1000) >> 1);
|
||||
apic.timer_divider_shift = if divide_shift == 0b111 { 0 } else { divide_shift + 1 };
|
||||
dbg_log!(
|
||||
"APIC timer divider: {:08x} shift={} tick={:.6}ms",
|
||||
apic.timer_divider,
|
||||
apic.timer_divider_shift,
|
||||
(1 << apic.timer_divider_shift) as f64 / APIC_TIMER_FREQ
|
||||
);
|
||||
},
|
||||
|
||||
0x380 => {
|
||||
if APIC_LOG_VERBOSE {
|
||||
dbg_log!(
|
||||
"APIC timer initial: {} next_interrupt={:.2}ms",
|
||||
value,
|
||||
value as f64 * (1 << apic.timer_divider_shift) as f64 / APIC_TIMER_FREQ,
|
||||
);
|
||||
}
|
||||
apic.timer_initial_count = value;
|
||||
apic.timer_current_count = value;
|
||||
apic.timer_last_tick = unsafe { js::microtick() };
|
||||
},
|
||||
|
||||
0x390 => {
|
||||
dbg_log!("write timer current: {:08x}", value);
|
||||
dbg_assert!(false, "read-only register");
|
||||
},
|
||||
|
||||
_ => {
|
||||
dbg_log!("APIC write32 {:x} <- {:08x}", addr, value);
|
||||
dbg_assert!(false);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn apic_timer(now: f64) -> f64 { timer(&mut get_apic(), now) }
|
||||
|
||||
fn timer(apic: &mut Apic, now: f64) -> f64 {
|
||||
if apic.timer_initial_count == 0 || apic.timer_current_count == 0 {
|
||||
return 100.0;
|
||||
}
|
||||
|
||||
if apic.timer_last_tick > now {
|
||||
// should only happen after restore_state
|
||||
dbg_log!("warning: APIC last_tick is in the future, resetting");
|
||||
apic.timer_last_tick = now;
|
||||
}
|
||||
|
||||
let diff = now - apic.timer_last_tick;
|
||||
let diff_in_ticks = diff * APIC_TIMER_FREQ / (1 << apic.timer_divider_shift) as f64;
|
||||
dbg_assert!(diff_in_ticks >= 0.0);
|
||||
let diff_in_ticks = diff_in_ticks as u64;
|
||||
|
||||
let time_per_interrupt =
|
||||
apic.timer_initial_count as f64 * (1 << apic.timer_divider_shift) as f64 / APIC_TIMER_FREQ;
|
||||
|
||||
if diff_in_ticks >= apic.timer_initial_count as u64 {
|
||||
let mode = apic.lvt_timer & APIC_TIMER_MODE_MASK;
|
||||
if mode == APIC_TIMER_MODE_PERIODIC {
|
||||
if APIC_LOG_VERBOSE {
|
||||
dbg_log!("APIC timer periodic interrupt");
|
||||
}
|
||||
|
||||
if diff_in_ticks >= 2 * apic.timer_initial_count as u64 {
|
||||
dbg_log!(
|
||||
"warning: APIC skipping {} interrupts initial={} ticks={} last_tick={:.1}ms now={:.1}ms d={:.1}ms",
|
||||
diff_in_ticks / apic.timer_initial_count as u64 - 1,
|
||||
apic.timer_initial_count,
|
||||
diff_in_ticks,
|
||||
apic.timer_last_tick,
|
||||
now,
|
||||
diff,
|
||||
);
|
||||
apic.timer_last_tick = now;
|
||||
}
|
||||
else {
|
||||
apic.timer_last_tick += time_per_interrupt;
|
||||
dbg_assert!(apic.timer_last_tick <= now);
|
||||
}
|
||||
}
|
||||
else if mode == APIC_TIMER_MODE_ONE_SHOT {
|
||||
if APIC_LOG_VERBOSE {
|
||||
dbg_log!("APIC timer one shot end");
|
||||
}
|
||||
apic.timer_current_count = 0;
|
||||
}
|
||||
else {
|
||||
dbg_assert!(false, "apic unimplemented timer mode: {:x}", mode);
|
||||
}
|
||||
|
||||
if apic.lvt_timer & IOAPIC_CONFIG_MASKED == 0 {
|
||||
deliver(
|
||||
apic,
|
||||
(apic.lvt_timer & 0xFF) as u8,
|
||||
IOAPIC_DELIVERY_FIXED,
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
apic.timer_last_tick + time_per_interrupt - now
|
||||
}
|
||||
|
||||
pub fn route(
|
||||
apic: &mut Apic,
|
||||
vector: u8,
|
||||
mode: u8,
|
||||
is_level: bool,
|
||||
_destination: u8,
|
||||
_destination_mode: u8,
|
||||
) {
|
||||
// TODO
|
||||
deliver(apic, vector, mode, is_level);
|
||||
}
|
||||
|
||||
fn deliver(apic: &mut Apic, vector: u8, mode: u8, is_level: bool) {
|
||||
if APIC_LOG_VERBOSE {
|
||||
dbg_log!("Deliver {:02x} mode={} level={}", vector, mode, is_level);
|
||||
}
|
||||
|
||||
if mode == IOAPIC_DELIVERY_INIT {
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
|
||||
if mode == IOAPIC_DELIVERY_NMI {
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
|
||||
if vector < 0x10 || vector == 0xFF {
|
||||
dbg_assert!(false, "TODO: Invalid vector: {:x}", vector);
|
||||
}
|
||||
|
||||
if register_get_bit(&apic.irr, vector) {
|
||||
dbg_log!("Not delivered: irr already set, vector={:02x}", vector);
|
||||
return;
|
||||
}
|
||||
|
||||
register_set_bit(&mut apic.irr, vector);
|
||||
|
||||
if is_level {
|
||||
register_set_bit(&mut apic.tmr, vector);
|
||||
}
|
||||
else {
|
||||
register_clear_bit(&mut apic.tmr, vector);
|
||||
}
|
||||
}
|
||||
|
||||
fn highest_irr(apic: &mut Apic) -> Option<u8> {
|
||||
let highest = register_get_highest_bit(&apic.irr);
|
||||
if let Some(x) = highest {
|
||||
dbg_assert!(x >= 0x10);
|
||||
dbg_assert!(x != 0xFF);
|
||||
}
|
||||
highest
|
||||
}
|
||||
|
||||
fn highest_isr(apic: &mut Apic) -> Option<u8> {
|
||||
let highest = register_get_highest_bit(&apic.isr);
|
||||
if let Some(x) = highest {
|
||||
dbg_assert!(x >= 0x10);
|
||||
dbg_assert!(x != 0xFF);
|
||||
}
|
||||
highest
|
||||
}
|
||||
|
||||
pub fn acknowledge_irq() -> Option<u8> { acknowledge_irq_internal(&mut get_apic()) }
|
||||
|
||||
fn acknowledge_irq_internal(apic: &mut Apic) -> Option<u8> {
|
||||
let highest_irr = match highest_irr(apic) {
|
||||
None => return None,
|
||||
Some(x) => x,
|
||||
};
|
||||
|
||||
if let Some(highest_isr) = highest_isr(apic) {
|
||||
if highest_isr >= highest_irr {
|
||||
if APIC_LOG_VERBOSE {
|
||||
dbg_log!("Higher isr, isr={:x} irr={:x}", highest_isr, highest_irr);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if highest_irr & 0xF0 <= apic.tpr as u8 & 0xF0 {
|
||||
if APIC_LOG_VERBOSE {
|
||||
dbg_log!(
|
||||
"Higher tpr, tpr={:x} irr={:x}",
|
||||
apic.tpr & 0xF0,
|
||||
highest_irr
|
||||
);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
register_clear_bit(&mut apic.irr, highest_irr);
|
||||
register_set_bit(&mut apic.isr, highest_irr);
|
||||
|
||||
if APIC_LOG_VERBOSE {
|
||||
dbg_log!("Calling vector {:x}", highest_irr);
|
||||
}
|
||||
|
||||
dbg_assert!(acknowledge_irq_internal(apic).is_none());
|
||||
|
||||
Some(highest_irr)
|
||||
}
|
||||
|
||||
// functions operating on 256-bit registers (for irr, isr, tmr)
|
||||
fn register_get_bit(v: &[u32; 8], bit: u8) -> bool { v[(bit >> 5) as usize] & 1 << (bit & 31) != 0 }
|
||||
|
||||
fn register_set_bit(v: &mut [u32; 8], bit: u8) { v[(bit >> 5) as usize] |= 1 << (bit & 31); }
|
||||
|
||||
fn register_clear_bit(v: &mut [u32; 8], bit: u8) { v[(bit >> 5) as usize] &= !(1 << (bit & 31)); }
|
||||
|
||||
fn register_get_highest_bit(v: &[u32; 8]) -> Option<u8> {
|
||||
dbg_assert!(v.as_ptr().addr() & std::mem::align_of::<u64>() - 1 == 0);
|
||||
let v: &[u64; 4] = unsafe { std::mem::transmute(v) };
|
||||
for i in (0..4).rev() {
|
||||
let word = v[i];
|
||||
|
||||
if word != 0 {
|
||||
return Some(word.ilog2() as u8 | (i as u8) << 6);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
|
@ -1,23 +1,5 @@
|
|||
#![allow(non_upper_case_globals)]
|
||||
|
||||
extern "C" {
|
||||
fn cpu_exception_hook(interrupt: i32) -> bool;
|
||||
fn call_indirect1(f: i32, x: u16);
|
||||
pub fn microtick() -> f64;
|
||||
pub fn run_hardware_timers(acpi_enabled: bool, t: f64) -> f64;
|
||||
pub fn cpu_event_halt();
|
||||
pub fn apic_acknowledge_irq() -> i32;
|
||||
pub fn stop_idling();
|
||||
|
||||
pub fn io_port_read8(port: i32) -> i32;
|
||||
pub fn io_port_read16(port: i32) -> i32;
|
||||
pub fn io_port_read32(port: i32) -> i32;
|
||||
|
||||
pub fn io_port_write8(port: i32, value: i32);
|
||||
pub fn io_port_write16(port: i32, value: i32);
|
||||
pub fn io_port_write32(port: i32, value: i32);
|
||||
}
|
||||
|
||||
use crate::config;
|
||||
use crate::cpu::fpu::fpu_set_tag_word;
|
||||
use crate::cpu::global_pointers::*;
|
||||
|
|
@ -27,7 +9,8 @@ use crate::cpu::misc_instr::{
|
|||
push16, push32,
|
||||
};
|
||||
use crate::cpu::modrm::{resolve_modrm16, resolve_modrm32};
|
||||
use crate::cpu::pic;
|
||||
use crate::cpu::{apic, ioapic, pic};
|
||||
use crate::dbg::dbg_trace;
|
||||
use crate::gen;
|
||||
use crate::jit;
|
||||
use crate::jit::is_near_end_of_page;
|
||||
|
|
@ -39,11 +22,36 @@ use crate::profiler;
|
|||
use crate::profiler::stat;
|
||||
use crate::softfloat;
|
||||
use crate::state_flags::CachedStateFlags;
|
||||
use crate::util::dbg_trace;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::ptr;
|
||||
|
||||
mod wasm {
|
||||
extern "C" {
|
||||
pub fn call_indirect1(f: i32, x: u16);
|
||||
}
|
||||
}
|
||||
|
||||
pub mod js {
|
||||
extern "C" {
|
||||
pub fn cpu_exception_hook(interrupt: i32) -> bool;
|
||||
pub fn microtick() -> f64;
|
||||
pub fn run_hardware_timers(acpi_enabled: bool, t: f64) -> f64;
|
||||
pub fn cpu_event_halt();
|
||||
pub fn stop_idling();
|
||||
|
||||
pub fn io_port_read8(port: i32) -> i32;
|
||||
pub fn io_port_read16(port: i32) -> i32;
|
||||
pub fn io_port_read32(port: i32) -> i32;
|
||||
|
||||
pub fn io_port_write8(port: i32, value: i32);
|
||||
pub fn io_port_write16(port: i32, value: i32);
|
||||
pub fn io_port_write32(port: i32, value: i32);
|
||||
|
||||
pub fn get_rand_int() -> i32;
|
||||
}
|
||||
}
|
||||
|
||||
/// The offset for our generated functions in the wasm table. Every index less than this is
|
||||
/// reserved for rustc's indirect functions
|
||||
pub const WASM_TABLE_OFFSET: u32 = 1024;
|
||||
|
|
@ -208,7 +216,10 @@ pub const MSR_TEST_CTRL: i32 = 0x33;
|
|||
pub const MSR_SMI_COUNT: i32 = 0x34;
|
||||
pub const IA32_FEAT_CTL: i32 = 0x3A;
|
||||
pub const IA32_SPEC_CTRL: i32 = 0x48;
|
||||
pub const IA32_BIOS_UPDT_TRIG: i32 = 0x79;
|
||||
pub const IA32_BIOS_SIGN_ID: i32 = 0x8B;
|
||||
pub const IA32_PMC0: i32 = 0xC1;
|
||||
pub const IA32_PMC1: i32 = 0xC2;
|
||||
pub const MSR_PLATFORM_INFO: i32 = 0xCE;
|
||||
pub const MSR_TSX_FORCE_ABORT: i32 = 0x10F;
|
||||
pub const IA32_TSX_CTRL: i32 = 0x122;
|
||||
|
|
@ -218,6 +229,8 @@ pub const IA32_SYSENTER_CS: i32 = 0x174;
|
|||
pub const IA32_SYSENTER_ESP: i32 = 0x175;
|
||||
pub const IA32_SYSENTER_EIP: i32 = 0x176;
|
||||
pub const IA32_MCG_CAP: i32 = 0x179;
|
||||
pub const IA32_PERFEVTSEL0: i32 = 0x186;
|
||||
pub const IA32_PERFEVTSEL1: i32 = 0x187;
|
||||
pub const IA32_MISC_ENABLE: i32 = 0x1A0;
|
||||
pub const IA32_PAT: i32 = 0x277;
|
||||
pub const IA32_RTIT_CTL: i32 = 0x570;
|
||||
|
|
@ -230,7 +243,10 @@ pub const IA32_APIC_BASE_BSP: i32 = 1 << 8;
|
|||
pub const IA32_APIC_BASE_EXTD: i32 = 1 << 10;
|
||||
pub const IA32_APIC_BASE_EN: i32 = 1 << 11;
|
||||
|
||||
pub const APIC_ADDRESS: i32 = 0xFEE00000u32 as i32;
|
||||
pub const IOAPIC_MEM_ADDRESS: u32 = 0xFEC00000;
|
||||
pub const IOAPIC_MEM_SIZE: u32 = 32;
|
||||
pub const APIC_MEM_ADDRESS: u32 = 0xFEE00000;
|
||||
pub const APIC_MEM_SIZE: u32 = 0x1000;
|
||||
|
||||
pub const MXCSR_MASK: i32 = 0xffff;
|
||||
pub const MXCSR_FZ: i32 = 1 << 15;
|
||||
|
|
@ -270,6 +286,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;
|
||||
|
|
@ -711,13 +729,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;
|
||||
}
|
||||
|
|
@ -893,13 +911,19 @@ pub unsafe fn call_interrupt_vector(
|
|||
let ss_segment_descriptor =
|
||||
match return_on_pagefault!(lookup_segment_selector(ss_segment_selector)) {
|
||||
Ok((desc, _)) => desc,
|
||||
Err(_) => {
|
||||
Err(
|
||||
SelectorNullOrInvalid::IsNull | SelectorNullOrInvalid::OutsideOfTableLimit,
|
||||
) => {
|
||||
panic!("Unimplemented: #TS handler");
|
||||
},
|
||||
};
|
||||
|
||||
dbg_assert!(!ss_segment_descriptor.is_dc(), "TODO: Handle direction bit");
|
||||
dbg_assert!(new_esp as u32 <= ss_segment_descriptor.effective_limit());
|
||||
if ss_segment_descriptor.is_dc() {
|
||||
dbg_assert!(new_esp as u32 > ss_segment_descriptor.effective_limit());
|
||||
}
|
||||
else {
|
||||
dbg_assert!(new_esp as u32 - 1 <= ss_segment_descriptor.effective_limit());
|
||||
}
|
||||
dbg_assert!(!ss_segment_descriptor.is_system() && ss_segment_descriptor.is_writable());
|
||||
|
||||
if ss_segment_selector.rpl() != cs_segment_descriptor.dpl() {
|
||||
|
|
@ -1158,12 +1182,18 @@ pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool
|
|||
return;
|
||||
},
|
||||
Err(SelectorNullOrInvalid::OutsideOfTableLimit) => {
|
||||
dbg_log!("#gp invalid cs: {:x}", selector);
|
||||
trigger_gp(selector & !3);
|
||||
dbg_log!("#gp invalid cs: {:x}", cs_selector);
|
||||
trigger_gp(cs_selector & !3);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
if cs_info.is_system() {
|
||||
dbg_log!("#gp non-code cs: {:x}", cs_selector);
|
||||
trigger_gp(cs_selector & !3);
|
||||
return;
|
||||
}
|
||||
|
||||
if !cs_info.is_executable() {
|
||||
dbg_log!("#gp non-executable cs: {:x}", cs_selector);
|
||||
trigger_gp(cs_selector & !3);
|
||||
|
|
@ -1202,8 +1232,12 @@ pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool
|
|||
},
|
||||
};
|
||||
|
||||
dbg_assert!(!ss_info.is_dc(), "TODO: Handle direction bit");
|
||||
dbg_assert!(new_esp as u32 <= ss_info.effective_limit());
|
||||
if ss_info.is_dc() {
|
||||
dbg_assert!(new_esp as u32 > ss_info.effective_limit());
|
||||
}
|
||||
else {
|
||||
dbg_assert!(new_esp as u32 - 1 <= ss_info.effective_limit());
|
||||
}
|
||||
dbg_assert!(!ss_info.is_system() && ss_info.is_writable());
|
||||
|
||||
if ss_selector.rpl() != cs_info.dpl()
|
||||
|
|
@ -1225,16 +1259,18 @@ pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool
|
|||
if is_16 { 4 + 2 * parameter_count } else { 8 + 4 * parameter_count };
|
||||
}
|
||||
if ss_info.is_32() {
|
||||
return_on_pagefault!(writable_or_pagefault(
|
||||
return_on_pagefault!(writable_or_pagefault_cpl(
|
||||
cs_info.dpl(),
|
||||
ss_info.base() + new_esp - stack_space,
|
||||
stack_space
|
||||
)); // , cs_info.dpl
|
||||
));
|
||||
}
|
||||
else {
|
||||
return_on_pagefault!(writable_or_pagefault(
|
||||
return_on_pagefault!(writable_or_pagefault_cpl(
|
||||
cs_info.dpl(),
|
||||
ss_info.base() + (new_esp - stack_space & 0xFFFF),
|
||||
stack_space
|
||||
)); // , cs_info.dpl
|
||||
));
|
||||
}
|
||||
|
||||
let old_esp = read_reg32(ESP);
|
||||
|
|
@ -1248,6 +1284,7 @@ pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool
|
|||
|
||||
update_cs_size(cs_info.is_32());
|
||||
|
||||
dbg_assert!(new_ss & 3 == cs_info.dpl() as i32);
|
||||
// XXX: Should be checked before side effects
|
||||
if !switch_seg(SS, new_ss) {
|
||||
dbg_assert!(false);
|
||||
|
|
@ -1269,7 +1306,6 @@ pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool
|
|||
if is_call {
|
||||
if is_16 {
|
||||
for i in (0..parameter_count).rev() {
|
||||
//for(let i = parameter_count - 1; i >= 0; i--)
|
||||
let parameter = safe_read16(old_stack_pointer + 2 * i).unwrap();
|
||||
push16(parameter).unwrap();
|
||||
}
|
||||
|
|
@ -1280,7 +1316,6 @@ pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool
|
|||
}
|
||||
else {
|
||||
for i in (0..parameter_count).rev() {
|
||||
//for(let i = parameter_count - 1; i >= 0; i--)
|
||||
let parameter = safe_read32s(old_stack_pointer + 4 * i).unwrap();
|
||||
push32(parameter).unwrap();
|
||||
}
|
||||
|
|
@ -1299,7 +1334,6 @@ pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool
|
|||
cs_info.dpl(),
|
||||
cs_info.is_dc()
|
||||
);
|
||||
// ok
|
||||
|
||||
if is_call {
|
||||
if is_16 {
|
||||
|
|
@ -1315,6 +1349,8 @@ pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool
|
|||
push32(get_real_eip()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
dbg_assert!(*cpl == cs_info.dpl());
|
||||
}
|
||||
|
||||
// Note: eip from call is ignored
|
||||
|
|
@ -1344,10 +1380,11 @@ pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool
|
|||
|
||||
update_state_flags();
|
||||
}
|
||||
else if info.system_type() == 1 || info.system_type() == 9 {
|
||||
dbg_assert!(false, "TODO: far call task gate");
|
||||
}
|
||||
else {
|
||||
dbg_assert!(false);
|
||||
//let types = { 9: "Available 386 TSS", 0xb: "Busy 386 TSS", 4: "286 Call Gate", 0xc: "386 Call Gate" };
|
||||
//throw debug.unimpl("load system segment descriptor, type = " + (info.access & 15) + " (" + types[info.access & 15] + ")");
|
||||
dbg_assert!(false, "TODO: #gp invalid system type");
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
@ -1806,10 +1843,14 @@ pub unsafe fn readable_or_pagefault(addr: i32, size: i32) -> OrPageFault<()> {
|
|||
}
|
||||
|
||||
pub unsafe fn writable_or_pagefault(addr: i32, size: i32) -> OrPageFault<()> {
|
||||
writable_or_pagefault_cpl(*cpl, addr, size)
|
||||
}
|
||||
|
||||
pub unsafe fn writable_or_pagefault_cpl(other_cpl: u8, addr: i32, size: i32) -> OrPageFault<()> {
|
||||
dbg_assert!(size < 0x1000);
|
||||
dbg_assert!(size > 0);
|
||||
|
||||
let user = *cpl == 3;
|
||||
let user = other_cpl == 3;
|
||||
translate_address(addr, true, user, false, true)?;
|
||||
|
||||
let end = addr + size - 1 & !0xFFF;
|
||||
|
|
@ -2196,7 +2237,7 @@ pub unsafe fn trigger_fault_end_jit() {
|
|||
#[allow(static_mut_refs)]
|
||||
let (code, error_code) = jit_fault.take().unwrap();
|
||||
if DEBUG {
|
||||
if cpu_exception_hook(code) {
|
||||
if js::cpu_exception_hook(code) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -2920,7 +2961,7 @@ pub unsafe fn cycle_internal() {
|
|||
{
|
||||
in_jit = true;
|
||||
}
|
||||
call_indirect1(
|
||||
wasm::call_indirect1(
|
||||
wasm_table_index as i32 + WASM_TABLE_OFFSET as i32,
|
||||
initial_state,
|
||||
);
|
||||
|
|
@ -3105,11 +3146,11 @@ pub unsafe fn segment_prefix_op(seg: i32) {
|
|||
pub unsafe fn main_loop() -> f64 {
|
||||
profiler::stat_increment(stat::MAIN_LOOP);
|
||||
|
||||
let start = microtick();
|
||||
let start = js::microtick();
|
||||
|
||||
if *in_hlt {
|
||||
if *flags & FLAG_INTERRUPT != 0 {
|
||||
let t = run_hardware_timers(*acpi_enabled, start);
|
||||
let t = js::run_hardware_timers(*acpi_enabled, start);
|
||||
handle_irqs();
|
||||
if *in_hlt {
|
||||
profiler::stat_increment(stat::MAIN_LOOP_IDLE);
|
||||
|
|
@ -3125,8 +3166,8 @@ pub unsafe fn main_loop() -> f64 {
|
|||
loop {
|
||||
do_many_cycles_native();
|
||||
|
||||
let now = microtick();
|
||||
let t = run_hardware_timers(*acpi_enabled, now);
|
||||
let now = js::microtick();
|
||||
let t = js::run_hardware_timers(*acpi_enabled, now);
|
||||
handle_irqs();
|
||||
if *in_hlt {
|
||||
return t;
|
||||
|
|
@ -3155,7 +3196,7 @@ pub unsafe fn trigger_de() {
|
|||
dbg_log!("#de");
|
||||
*instruction_pointer = *previous_ip;
|
||||
if DEBUG {
|
||||
if cpu_exception_hook(CPU_EXCEPTION_DE) {
|
||||
if js::cpu_exception_hook(CPU_EXCEPTION_DE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -3168,7 +3209,7 @@ pub unsafe fn trigger_ud() {
|
|||
dbg_trace();
|
||||
*instruction_pointer = *previous_ip;
|
||||
if DEBUG {
|
||||
if cpu_exception_hook(CPU_EXCEPTION_UD) {
|
||||
if js::cpu_exception_hook(CPU_EXCEPTION_UD) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -3181,7 +3222,7 @@ pub unsafe fn trigger_nm() {
|
|||
dbg_trace();
|
||||
*instruction_pointer = *previous_ip;
|
||||
if DEBUG {
|
||||
if cpu_exception_hook(CPU_EXCEPTION_NM) {
|
||||
if js::cpu_exception_hook(CPU_EXCEPTION_NM) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -3193,7 +3234,7 @@ pub unsafe fn trigger_gp(code: i32) {
|
|||
dbg_log!("#gp");
|
||||
*instruction_pointer = *previous_ip;
|
||||
if DEBUG {
|
||||
if cpu_exception_hook(CPU_EXCEPTION_GP) {
|
||||
if js::cpu_exception_hook(CPU_EXCEPTION_GP) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -3285,7 +3326,7 @@ pub unsafe fn safe_read32s(addr: i32) -> OrPageFault<i32> {
|
|||
}
|
||||
|
||||
pub unsafe fn safe_read_f32(addr: i32) -> OrPageFault<f32> {
|
||||
Ok(std::mem::transmute(safe_read32s(addr)?))
|
||||
Ok(f32::from_bits(i32::cast_unsigned(safe_read32s(addr)?)))
|
||||
}
|
||||
|
||||
pub unsafe fn safe_read64s(addr: i32) -> OrPageFault<u64> {
|
||||
|
|
@ -3539,10 +3580,11 @@ pub unsafe fn safe_write_slow_jit(
|
|||
if Page::page_of(*instruction_pointer as u32) == Page::page_of(addr as u32) {
|
||||
// XXX: Check based on virtual address
|
||||
dbg_log!(
|
||||
"SMC: bits={} eip={:x} writeaddr={:x}",
|
||||
"SMC: bits={} eip={:x} writeaddr={:x} value={:x}",
|
||||
bitsize,
|
||||
(*instruction_pointer & !0xFFF | eip_offset_in_page) as u32,
|
||||
addr as u32
|
||||
addr as u32,
|
||||
value_low,
|
||||
);
|
||||
}
|
||||
let crosses_page = (addr & 0xFFF) + bitsize / 8 > 0x1000;
|
||||
|
|
@ -4062,7 +4104,7 @@ pub unsafe fn set_tsc(low: u32, high: u32) {
|
|||
|
||||
#[no_mangle]
|
||||
pub unsafe fn read_tsc() -> u64 {
|
||||
let value = (microtick() * TSC_RATE) as u64 - tsc_offset;
|
||||
let value = (js::microtick() * TSC_RATE) as u64 - tsc_offset;
|
||||
|
||||
if !TSC_ENABLE_IMPRECISE_BROWSER_WORKAROUND {
|
||||
return value;
|
||||
|
|
@ -4247,7 +4289,7 @@ pub unsafe fn trigger_np(code: i32) {
|
|||
dbg_log!("#np");
|
||||
*instruction_pointer = *previous_ip;
|
||||
if DEBUG {
|
||||
if cpu_exception_hook(CPU_EXCEPTION_NP) {
|
||||
if js::cpu_exception_hook(CPU_EXCEPTION_NP) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -4259,7 +4301,7 @@ pub unsafe fn trigger_ss(code: i32) {
|
|||
dbg_log!("#ss");
|
||||
*instruction_pointer = *previous_ip;
|
||||
if DEBUG {
|
||||
if cpu_exception_hook(CPU_EXCEPTION_SS) {
|
||||
if js::cpu_exception_hook(CPU_EXCEPTION_SS) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -4270,17 +4312,14 @@ pub unsafe fn trigger_ss(code: i32) {
|
|||
pub unsafe fn store_current_tsc() { *current_tsc = read_tsc(); }
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe fn handle_irqs() { handle_irqs_internal(&mut pic::get_pic()) }
|
||||
|
||||
pub unsafe fn handle_irqs_internal(pic: &mut pic::Pic) {
|
||||
pub unsafe fn handle_irqs() {
|
||||
if *flags & FLAG_INTERRUPT != 0 {
|
||||
if let Some(irq) = pic::pic_acknowledge_irq(pic) {
|
||||
if let Some(irq) = pic::pic_acknowledge_irq() {
|
||||
pic_call_irq(irq)
|
||||
}
|
||||
else if *acpi_enabled {
|
||||
let irq = apic_acknowledge_irq();
|
||||
if irq >= 0 {
|
||||
pic_call_irq(irq as u8)
|
||||
if let Some(irq) = apic::acknowledge_irq() {
|
||||
pic_call_irq(irq)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4289,12 +4328,68 @@ pub unsafe fn handle_irqs_internal(pic: &mut pic::Pic) {
|
|||
unsafe fn pic_call_irq(interrupt_nr: u8) {
|
||||
*previous_ip = *instruction_pointer; // XXX: What if called after instruction (port IO)
|
||||
if *in_hlt {
|
||||
stop_idling();
|
||||
js::stop_idling();
|
||||
*in_hlt = false;
|
||||
}
|
||||
call_interrupt_vector(interrupt_nr as i32, false, None);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe fn device_raise_irq(i: u8) {
|
||||
pic::set_irq(i);
|
||||
if *acpi_enabled {
|
||||
ioapic::set_irq(i);
|
||||
}
|
||||
handle_irqs()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe fn device_lower_irq(i: u8) {
|
||||
pic::clear_irq(i);
|
||||
if *acpi_enabled {
|
||||
ioapic::clear_irq(i);
|
||||
}
|
||||
handle_irqs()
|
||||
}
|
||||
|
||||
pub fn io_port_read8(port: i32) -> i32 {
|
||||
unsafe {
|
||||
match port {
|
||||
0x20 => pic::port20_read() as i32,
|
||||
0x21 => pic::port21_read() as i32,
|
||||
0xA0 => pic::portA0_read() as i32,
|
||||
0xA1 => pic::portA1_read() as i32,
|
||||
0x4D0 => pic::port4D0_read() as i32,
|
||||
0x4D1 => pic::port4D1_read() as i32,
|
||||
_ => js::io_port_read8(port),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn io_port_read16(port: i32) -> i32 { unsafe { js::io_port_read16(port) } }
|
||||
pub fn io_port_read32(port: i32) -> i32 { unsafe { js::io_port_read32(port) } }
|
||||
|
||||
pub fn io_port_write8(port: i32, value: i32) {
|
||||
unsafe {
|
||||
match port {
|
||||
0x20 | 0x21 | 0xA0 | 0xA1 | 0x4D0 | 0x4D1 => {
|
||||
match port {
|
||||
0x20 => pic::port20_write(value as u8),
|
||||
0x21 => pic::port21_write(value as u8),
|
||||
0xA0 => pic::portA0_write(value as u8),
|
||||
0xA1 => pic::portA1_write(value as u8),
|
||||
0x4D0 => pic::port4D0_write(value as u8),
|
||||
0x4D1 => pic::port4D1_write(value as u8),
|
||||
_ => dbg_assert!(false),
|
||||
};
|
||||
handle_irqs()
|
||||
},
|
||||
_ => js::io_port_write8(port, value),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn io_port_write16(port: i32, value: i32) { unsafe { js::io_port_write16(port, value) } }
|
||||
pub fn io_port_write32(port: i32, value: i32) { unsafe { js::io_port_write32(port, value) } }
|
||||
|
||||
#[no_mangle]
|
||||
#[cfg(debug_assertions)]
|
||||
pub unsafe fn check_page_switch(block_addr: u32, next_block_addr: u32) {
|
||||
|
|
|
|||
|
|
@ -77,10 +77,11 @@ pub unsafe fn fpu_get_sti(mut i: i32) -> F80 {
|
|||
};
|
||||
}
|
||||
|
||||
// only used for debugging
|
||||
#[no_mangle]
|
||||
pub unsafe fn fpu_get_sti_f64(mut i: i32) -> f64 {
|
||||
i = i + *fpu_stack_ptr as i32 & 7;
|
||||
std::mem::transmute((*fpu_st.offset(i as isize)).to_f64())
|
||||
f64::from_bits((*fpu_st.offset(i as isize)).to_f64())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
|
@ -496,7 +497,7 @@ pub unsafe fn fpu_fprem(ieee: bool) {
|
|||
let exp0 = st0.log2();
|
||||
let exp1 = st1.log2();
|
||||
let d = (exp0 - exp1).abs();
|
||||
if !intel_compatibility || d < F80::of_f64(std::mem::transmute(64.0)) {
|
||||
if !intel_compatibility || d < F80::of_f64(f64::to_bits(64.0)) {
|
||||
let fprem_quotient =
|
||||
(if ieee { (st0 / st1).round() } else { (st0 / st1).trunc() }).to_i32();
|
||||
fpu_write_st(*fpu_stack_ptr as i32, st0 % st1);
|
||||
|
|
@ -513,7 +514,7 @@ pub unsafe fn fpu_fprem(ieee: bool) {
|
|||
*fpu_status_word &= !FPU_C2;
|
||||
}
|
||||
else {
|
||||
let n = F80::of_f64(std::mem::transmute(32.0));
|
||||
let n = F80::of_f64(f64::to_bits(32.0));
|
||||
let fprem_quotient =
|
||||
(if ieee { (st0 / st1).round() } else { (st0 / st1).trunc() } / (d - n).two_pow());
|
||||
fpu_write_st(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::cpu::arith::*;
|
||||
use crate::cpu::cpu::js;
|
||||
use crate::cpu::cpu::*;
|
||||
use crate::cpu::fpu::*;
|
||||
use crate::cpu::global_pointers::*;
|
||||
|
|
@ -575,7 +576,6 @@ pub unsafe fn instr_66() {
|
|||
}
|
||||
pub unsafe fn instr_67() {
|
||||
// Address-size override prefix
|
||||
dbg_assert!(is_asize_32() == *is_32);
|
||||
*prefixes |= prefix::PREFIX_MASK_ADDRSIZE;
|
||||
run_prefix_instruction();
|
||||
*prefixes = 0;
|
||||
|
|
@ -995,7 +995,6 @@ pub unsafe fn instr32_99() { write_reg32(EDX, read_reg32(EAX) >> 31); }
|
|||
pub unsafe fn instr16_9A(new_ip: i32, new_cs: i32) {
|
||||
// callf
|
||||
far_jump(new_ip, new_cs, true, false);
|
||||
dbg_assert!(*is_32 || get_real_eip() < 0x10000);
|
||||
}
|
||||
#[no_mangle]
|
||||
pub unsafe fn instr32_9A(new_ip: i32, new_cs: i32) {
|
||||
|
|
@ -1005,7 +1004,6 @@ pub unsafe fn instr32_9A(new_ip: i32, new_cs: i32) {
|
|||
}
|
||||
}
|
||||
far_jump(new_ip, new_cs, true, true);
|
||||
dbg_assert!(*is_32 || get_real_eip() < 0x10000);
|
||||
}
|
||||
#[no_mangle]
|
||||
pub unsafe fn instr_9B() {
|
||||
|
|
@ -2057,13 +2055,11 @@ pub unsafe fn instr32_E9(imm32s: i32) {
|
|||
pub unsafe fn instr16_EA(new_ip: i32, cs: i32) {
|
||||
// jmpf
|
||||
far_jump(new_ip, cs, false, false);
|
||||
dbg_assert!(*is_32 || get_real_eip() < 0x10000);
|
||||
}
|
||||
#[no_mangle]
|
||||
pub unsafe fn instr32_EA(new_ip: i32, cs: i32) {
|
||||
// jmpf
|
||||
far_jump(new_ip, cs, false, true);
|
||||
dbg_assert!(*is_32 || get_real_eip() < 0x10000);
|
||||
}
|
||||
|
||||
pub unsafe fn instr16_EB(imm8: i32) {
|
||||
|
|
@ -2167,12 +2163,12 @@ pub unsafe fn instr_F4() {
|
|||
// due it will immediately call call_interrupt_vector and continue
|
||||
// execution without an unnecessary cycle through do_run
|
||||
if *flags & FLAG_INTERRUPT != 0 {
|
||||
run_hardware_timers(*acpi_enabled, microtick());
|
||||
js::run_hardware_timers(*acpi_enabled, js::microtick());
|
||||
handle_irqs();
|
||||
}
|
||||
else {
|
||||
// execution can never resume (until NMIs are supported)
|
||||
cpu_event_halt();
|
||||
js::cpu_event_halt();
|
||||
}
|
||||
}
|
||||
#[no_mangle]
|
||||
|
|
@ -2391,7 +2387,6 @@ pub unsafe fn instr16_FF_3_mem(addr: i32) {
|
|||
let new_ip = return_on_pagefault!(safe_read16(addr));
|
||||
let new_cs = return_on_pagefault!(safe_read16(addr + 2));
|
||||
far_jump(new_ip, new_cs, true, false);
|
||||
dbg_assert!(*is_32 || get_real_eip() < 0x10000);
|
||||
}
|
||||
pub unsafe fn instr16_FF_4_helper(data: i32) {
|
||||
// jmp near
|
||||
|
|
@ -2414,7 +2409,6 @@ pub unsafe fn instr16_FF_5_mem(addr: i32) {
|
|||
let new_ip = return_on_pagefault!(safe_read16(addr));
|
||||
let new_cs = return_on_pagefault!(safe_read16(addr + 2));
|
||||
far_jump(new_ip, new_cs, false, false);
|
||||
dbg_assert!(*is_32 || get_real_eip() < 0x10000);
|
||||
}
|
||||
pub unsafe fn instr16_FF_6_mem(addr: i32) {
|
||||
return_on_pagefault!(push16(return_on_pagefault!(safe_read16(addr))));
|
||||
|
|
@ -2455,7 +2449,6 @@ pub unsafe fn instr32_FF_3_mem(addr: i32) {
|
|||
}
|
||||
}
|
||||
far_jump(new_ip, new_cs, true, true);
|
||||
dbg_assert!(*is_32 || new_ip < 0x10000);
|
||||
}
|
||||
|
||||
pub unsafe fn instr32_FF_4_helper(data: i32) {
|
||||
|
|
@ -2484,7 +2477,6 @@ pub unsafe fn instr32_FF_5_mem(addr: i32) {
|
|||
}
|
||||
}
|
||||
far_jump(new_ip, new_cs, false, true);
|
||||
dbg_assert!(*is_32 || new_ip < 0x10000);
|
||||
}
|
||||
pub unsafe fn instr32_FF_6_mem(addr: i32) {
|
||||
return_on_pagefault!(push32(return_on_pagefault!(safe_read32s(addr))));
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
extern "C" {
|
||||
fn get_rand_int() -> i32;
|
||||
}
|
||||
|
||||
unsafe fn undefined_instruction() {
|
||||
dbg_assert!(false, "Undefined instructions");
|
||||
trigger_ud()
|
||||
|
|
@ -275,11 +271,11 @@ pub unsafe fn instr16_0F01_4_reg(r: i32) {
|
|||
pub unsafe fn instr32_0F01_4_reg(r: i32) { write_reg32(r, *cr); }
|
||||
#[no_mangle]
|
||||
pub unsafe fn instr16_0F01_4_mem(addr: i32) {
|
||||
return_on_pagefault!(safe_write16(addr, *cr));
|
||||
return_on_pagefault!(safe_write16(addr, *cr & 0xFFFF));
|
||||
}
|
||||
#[no_mangle]
|
||||
pub unsafe fn instr32_0F01_4_mem(addr: i32) {
|
||||
return_on_pagefault!(safe_write16(addr, *cr));
|
||||
return_on_pagefault!(safe_write16(addr, *cr & 0xFFFF));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
|
@ -1207,13 +1203,14 @@ pub unsafe fn instr_0F30() {
|
|||
);
|
||||
let address = low & !(IA32_APIC_BASE_BSP | IA32_APIC_BASE_EXTD | IA32_APIC_BASE_EN);
|
||||
dbg_assert!(
|
||||
address == APIC_ADDRESS,
|
||||
address == APIC_MEM_ADDRESS as i32,
|
||||
"Changing APIC address not supported"
|
||||
);
|
||||
dbg_assert!(low & IA32_APIC_BASE_EXTD == 0, "x2apic not supported");
|
||||
*apic_enabled = low & IA32_APIC_BASE_EN == IA32_APIC_BASE_EN
|
||||
},
|
||||
IA32_TIME_STAMP_COUNTER => set_tsc(low as u32, high as u32),
|
||||
IA32_BIOS_UPDT_TRIG => {}, // windows xp
|
||||
IA32_BIOS_SIGN_ID => {},
|
||||
MISC_FEATURE_ENABLES => {
|
||||
// Linux 4, see: https://patchwork.kernel.org/patch/9528279/
|
||||
|
|
@ -1226,6 +1223,8 @@ pub unsafe fn instr_0F30() {
|
|||
// Only used in 64 bit mode (by SWAPGS), but set by kvm-unit-test
|
||||
dbg_log!("GS Base written");
|
||||
},
|
||||
IA32_PERFEVTSEL0 | IA32_PERFEVTSEL1 => {}, // linux/9legacy
|
||||
IA32_PMC0 | IA32_PMC1 => {}, // linux
|
||||
IA32_PAT => {},
|
||||
IA32_SPEC_CTRL => {}, // linux 5.19
|
||||
IA32_TSX_CTRL => {}, // linux 5.19
|
||||
|
|
@ -1283,7 +1282,7 @@ pub unsafe fn instr_0F32() {
|
|||
IA32_PLATFORM_ID => {},
|
||||
IA32_APIC_BASE => {
|
||||
if *acpi_enabled {
|
||||
low = APIC_ADDRESS;
|
||||
low = APIC_MEM_ADDRESS as i32;
|
||||
if *apic_enabled {
|
||||
low |= IA32_APIC_BASE_EN
|
||||
}
|
||||
|
|
@ -1296,13 +1295,11 @@ pub unsafe fn instr_0F32() {
|
|||
// Enable Misc. Processor Features
|
||||
low = 1 << 0; // fast string
|
||||
},
|
||||
IA32_RTIT_CTL => {
|
||||
// linux4
|
||||
},
|
||||
IA32_RTIT_CTL => {}, // linux4
|
||||
MSR_SMI_COUNT => {},
|
||||
IA32_MCG_CAP => {
|
||||
// netbsd
|
||||
},
|
||||
IA32_MCG_CAP => {}, // netbsd
|
||||
IA32_PERFEVTSEL0 | IA32_PERFEVTSEL1 => {}, // linux/9legacy
|
||||
IA32_PMC0 | IA32_PMC1 => {}, // linux
|
||||
IA32_PAT => {},
|
||||
MSR_PKG_C2_RESIDENCY => {},
|
||||
IA32_SPEC_CTRL => {}, // linux 5.19
|
||||
|
|
@ -2189,14 +2186,14 @@ pub unsafe fn instr_F30F5F_mem(addr: i32, r: i32) {
|
|||
#[no_mangle]
|
||||
pub unsafe fn instr_0F60(source: i32, r: i32) {
|
||||
// punpcklbw mm, mm/m32
|
||||
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
|
||||
let source: [u8; 4] = std::mem::transmute(source);
|
||||
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
|
||||
let source: [u8; 4] = i32::to_le_bytes(source);
|
||||
let mut result = [0; 8];
|
||||
for i in 0..4 {
|
||||
result[2 * i + 0] = destination[i];
|
||||
result[2 * i + 1] = source[i];
|
||||
}
|
||||
write_mmx_reg64(r, std::mem::transmute(result));
|
||||
write_mmx_reg64(r, u64::from_le_bytes(result));
|
||||
transition_fpu_to_mmx();
|
||||
}
|
||||
pub unsafe fn instr_0F60_reg(r1: i32, r2: i32) { instr_0F60(read_mmx32s(r1), r2); }
|
||||
|
|
@ -2207,7 +2204,7 @@ pub unsafe fn instr_0F60_mem(addr: i32, r: i32) {
|
|||
pub unsafe fn instr_660F60(source: reg128, r: i32) {
|
||||
// punpcklbw xmm, xmm/m128
|
||||
// XXX: Aligned access or #gp
|
||||
let destination: [u8; 8] = std::mem::transmute(read_xmm64s(r));
|
||||
let destination: [u8; 8] = u64::to_le_bytes(read_xmm64s(r));
|
||||
let mut result = reg128 { i8: [0; 16] };
|
||||
for i in 0..8 {
|
||||
result.u8[2 * i + 0] = destination[i];
|
||||
|
|
@ -2292,7 +2289,7 @@ pub unsafe fn instr_0F63(source: u64, r: i32) {
|
|||
result[i + 0] = saturate_sw_to_sb(destination[i] as i32);
|
||||
result[i + 4] = saturate_sw_to_sb(source[i] as i32);
|
||||
}
|
||||
write_mmx_reg64(r, std::mem::transmute(result));
|
||||
write_mmx_reg64(r, u64::from_le_bytes(result));
|
||||
transition_fpu_to_mmx();
|
||||
}
|
||||
pub unsafe fn instr_0F63_reg(r1: i32, r2: i32) { instr_0F63(read_mmx64s(r1), r2); }
|
||||
|
|
@ -2324,7 +2321,7 @@ pub unsafe fn instr_0F64(source: u64, r: i32) {
|
|||
for i in 0..8 {
|
||||
result[i] = if destination[i] > source[i] { 255 } else { 0 };
|
||||
}
|
||||
write_mmx_reg64(r, std::mem::transmute(result));
|
||||
write_mmx_reg64(r, u64::from_le_bytes(result));
|
||||
transition_fpu_to_mmx();
|
||||
}
|
||||
pub unsafe fn instr_0F64_reg(r1: i32, r2: i32) { instr_0F64(read_mmx64s(r1), r2); }
|
||||
|
|
@ -2420,7 +2417,7 @@ pub unsafe fn instr_0F67(source: u64, r: i32) {
|
|||
result[i + 0] = saturate_sw_to_ub(destination[i]);
|
||||
result[i + 4] = saturate_sw_to_ub(source[i]);
|
||||
}
|
||||
write_mmx_reg64(r, std::mem::transmute(result));
|
||||
write_mmx_reg64(r, u64::from_le_bytes(result));
|
||||
transition_fpu_to_mmx();
|
||||
}
|
||||
pub unsafe fn instr_0F67_reg(r1: i32, r2: i32) { instr_0F67(read_mmx64s(r1), r2); }
|
||||
|
|
@ -2446,14 +2443,14 @@ pub unsafe fn instr_660F67_mem(addr: i32, r: i32) {
|
|||
#[no_mangle]
|
||||
pub unsafe fn instr_0F68(source: u64, r: i32) {
|
||||
// punpckhbw mm, mm/m64
|
||||
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
|
||||
let source: [u8; 8] = std::mem::transmute(source);
|
||||
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
|
||||
let source: [u8; 8] = u64::to_le_bytes(source);
|
||||
let mut result: [u8; 8] = [0; 8];
|
||||
for i in 0..4 {
|
||||
result[2 * i + 0] = destination[i + 4];
|
||||
result[2 * i + 1] = source[i + 4];
|
||||
}
|
||||
write_mmx_reg64(r, std::mem::transmute(result));
|
||||
write_mmx_reg64(r, u64::from_le_bytes(result));
|
||||
transition_fpu_to_mmx();
|
||||
}
|
||||
pub unsafe fn instr_0F68_reg(r1: i32, r2: i32) { instr_0F68(read_mmx64s(r1), r2); }
|
||||
|
|
@ -2868,13 +2865,13 @@ pub unsafe fn instr_660F73_7_reg(r: i32, imm8: i32) {
|
|||
#[no_mangle]
|
||||
pub unsafe fn instr_0F74(source: u64, r: i32) {
|
||||
// pcmpeqb mm, mm/m64
|
||||
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
|
||||
let source: [u8; 8] = std::mem::transmute(source);
|
||||
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
|
||||
let source: [u8; 8] = u64::to_le_bytes(source);
|
||||
let mut result: [u8; 8] = [0; 8];
|
||||
for i in 0..8 {
|
||||
result[i] = if destination[i] == source[i] { 255 } else { 0 };
|
||||
}
|
||||
write_mmx_reg64(r, std::mem::transmute(result));
|
||||
write_mmx_reg64(r, u64::from_le_bytes(result));
|
||||
transition_fpu_to_mmx();
|
||||
}
|
||||
pub unsafe fn instr_0F74_reg(r1: i32, r2: i32) { instr_0F74(read_mmx64s(r1), r2); }
|
||||
|
|
@ -3241,8 +3238,7 @@ pub unsafe fn instr_0FA2() {
|
|||
},
|
||||
|
||||
1 => {
|
||||
// pentium
|
||||
eax = 3 | 6 << 4 | 15 << 8;
|
||||
eax = 3 | 7 << 4 | 6 << 8; // pentium3
|
||||
ebx = 1 << 16 | 8 << 8; // cpu count, clflush size
|
||||
ecx = 1 << 0 | 1 << 23 | 1 << 30; // sse3, popcnt, rdrand
|
||||
let vme = 0 << 1;
|
||||
|
|
@ -3820,7 +3816,7 @@ pub unsafe fn instr_F20FC2_mem(addr: i32, r: i32, imm: i32) {
|
|||
pub unsafe fn instr_F30FC2(source: i32, r: i32, imm8: i32) {
|
||||
// cmpss xmm, xmm/m32
|
||||
let destination = read_xmm_f32(r);
|
||||
let source: f32 = std::mem::transmute(source);
|
||||
let source: f32 = f32::from_bits(i32::cast_unsigned(source));
|
||||
let result = if sse_comparison(imm8, destination as f64, source as f64) { -1 } else { 0 };
|
||||
write_xmm32(r, result);
|
||||
}
|
||||
|
|
@ -3944,7 +3940,7 @@ pub unsafe fn instr32_0FC7_1_mem(addr: i32) { instr16_0FC7_1_mem(addr) }
|
|||
#[no_mangle]
|
||||
pub unsafe fn instr16_0FC7_6_reg(r: i32) {
|
||||
// rdrand
|
||||
let rand = get_rand_int();
|
||||
let rand = js::get_rand_int();
|
||||
write_reg16(r, rand);
|
||||
*flags &= !FLAGS_ALL;
|
||||
*flags |= 1;
|
||||
|
|
@ -3953,7 +3949,7 @@ pub unsafe fn instr16_0FC7_6_reg(r: i32) {
|
|||
#[no_mangle]
|
||||
pub unsafe fn instr32_0FC7_6_reg(r: i32) {
|
||||
// rdrand
|
||||
let rand = get_rand_int();
|
||||
let rand = js::get_rand_int();
|
||||
write_reg32(r, rand);
|
||||
*flags &= !FLAGS_ALL;
|
||||
*flags |= 1;
|
||||
|
|
@ -4131,7 +4127,7 @@ pub unsafe fn instr_0FD7_mem(_addr: i32, _r: i32) { trigger_ud(); }
|
|||
#[no_mangle]
|
||||
pub unsafe fn instr_0FD7(r1: i32) -> i32 {
|
||||
// pmovmskb r, mm
|
||||
let x: [u8; 8] = std::mem::transmute(read_mmx64s(r1));
|
||||
let x: [u8; 8] = u64::to_le_bytes(read_mmx64s(r1));
|
||||
let mut result = 0;
|
||||
for i in 0..8 {
|
||||
result |= x[i] as i32 >> 7 << i
|
||||
|
|
@ -4155,13 +4151,13 @@ pub unsafe fn instr_660FD7_reg(r1: i32, r2: i32) { write_reg32(r2, instr_660FD7(
|
|||
#[no_mangle]
|
||||
pub unsafe fn instr_0FD8(source: u64, r: i32) {
|
||||
// psubusb mm, mm/m64
|
||||
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
|
||||
let source: [u8; 8] = std::mem::transmute(source);
|
||||
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
|
||||
let source: [u8; 8] = u64::to_le_bytes(source);
|
||||
let mut result = [0; 8];
|
||||
for i in 0..8 {
|
||||
result[i] = saturate_sd_to_ub(destination[i] as i32 - source[i] as i32) as u8;
|
||||
}
|
||||
write_mmx_reg64(r, std::mem::transmute(result));
|
||||
write_mmx_reg64(r, u64::from_le_bytes(result));
|
||||
transition_fpu_to_mmx();
|
||||
}
|
||||
pub unsafe fn instr_0FD8_reg(r1: i32, r2: i32) { instr_0FD8(read_mmx64s(r1), r2); }
|
||||
|
|
@ -4215,13 +4211,13 @@ pub unsafe fn instr_660FD9_mem(addr: i32, r: i32) {
|
|||
#[no_mangle]
|
||||
pub unsafe fn instr_0FDA(source: u64, r: i32) {
|
||||
// pminub mm, mm/m64
|
||||
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
|
||||
let source: [u8; 8] = std::mem::transmute(source);
|
||||
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
|
||||
let source: [u8; 8] = u64::to_le_bytes(source);
|
||||
let mut result = [0; 8];
|
||||
for i in 0..8 {
|
||||
result[i] = u8::min(source[i], destination[i])
|
||||
}
|
||||
write_mmx_reg64(r, std::mem::transmute(result));
|
||||
write_mmx_reg64(r, u64::from_le_bytes(result));
|
||||
transition_fpu_to_mmx();
|
||||
}
|
||||
pub unsafe fn instr_0FDA_reg(r1: i32, r2: i32) { instr_0FDA(read_mmx64s(r1), r2); }
|
||||
|
|
@ -4267,13 +4263,13 @@ pub unsafe fn instr_660FDB_mem(addr: i32, r: i32) {
|
|||
#[no_mangle]
|
||||
pub unsafe fn instr_0FDC(source: u64, r: i32) {
|
||||
// paddusb mm, mm/m64
|
||||
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
|
||||
let source: [u8; 8] = std::mem::transmute(source);
|
||||
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
|
||||
let source: [u8; 8] = u64::to_le_bytes(source);
|
||||
let mut result = [0; 8];
|
||||
for i in 0..8 {
|
||||
result[i] = saturate_ud_to_ub(destination[i] as u32 + source[i] as u32);
|
||||
}
|
||||
write_mmx_reg64(r, std::mem::transmute(result));
|
||||
write_mmx_reg64(r, u64::from_le_bytes(result));
|
||||
transition_fpu_to_mmx();
|
||||
}
|
||||
pub unsafe fn instr_0FDC_reg(r1: i32, r2: i32) { instr_0FDC(read_mmx64s(r1), r2); }
|
||||
|
|
@ -4329,13 +4325,13 @@ pub unsafe fn instr_660FDD_mem(addr: i32, r: i32) {
|
|||
#[no_mangle]
|
||||
pub unsafe fn instr_0FDE(source: u64, r: i32) {
|
||||
// pmaxub mm, mm/m64
|
||||
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
|
||||
let source: [u8; 8] = std::mem::transmute(source);
|
||||
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
|
||||
let source: [u8; 8] = u64::to_le_bytes(source);
|
||||
let mut result = [0; 8];
|
||||
for i in 0..8 {
|
||||
result[i] = u8::max(source[i], destination[i])
|
||||
}
|
||||
write_mmx_reg64(r, std::mem::transmute(result));
|
||||
write_mmx_reg64(r, u64::from_le_bytes(result));
|
||||
transition_fpu_to_mmx();
|
||||
}
|
||||
pub unsafe fn instr_0FDE_reg(r1: i32, r2: i32) { instr_0FDE(read_mmx64s(r1), r2); }
|
||||
|
|
@ -4381,13 +4377,13 @@ pub unsafe fn instr_660FDF_mem(addr: i32, r: i32) {
|
|||
#[no_mangle]
|
||||
pub unsafe fn instr_0FE0(source: u64, r: i32) {
|
||||
// pavgb mm, mm/m64
|
||||
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
|
||||
let source: [u8; 8] = std::mem::transmute(source);
|
||||
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
|
||||
let source: [u8; 8] = u64::to_le_bytes(source);
|
||||
let mut result = [0; 8];
|
||||
for i in 0..8 {
|
||||
result[i] = (destination[i] as i32 + source[i] as i32 + 1 >> 1) as u8;
|
||||
}
|
||||
write_mmx_reg64(r, std::mem::transmute(result));
|
||||
write_mmx_reg64(r, u64::from_le_bytes(result));
|
||||
transition_fpu_to_mmx();
|
||||
}
|
||||
pub unsafe fn instr_0FE0_reg(r1: i32, r2: i32) { instr_0FE0(read_mmx64s(r1), r2); }
|
||||
|
|
@ -4960,8 +4956,8 @@ pub unsafe fn instr_660FF5_mem(addr: i32, r: i32) {
|
|||
#[no_mangle]
|
||||
pub unsafe fn instr_0FF6(source: u64, r: i32) {
|
||||
// psadbw mm, mm/m64
|
||||
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
|
||||
let source: [u8; 8] = std::mem::transmute(source);
|
||||
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
|
||||
let source: [u8; 8] = u64::to_le_bytes(source);
|
||||
let mut sum = 0;
|
||||
for i in 0..8 {
|
||||
sum += (destination[i] as i32 - source[i] as i32).abs() as u64;
|
||||
|
|
@ -4995,8 +4991,8 @@ pub unsafe fn instr_0FF7_mem(_addr: i32, _r: i32) { trigger_ud(); }
|
|||
#[no_mangle]
|
||||
pub unsafe fn maskmovq(r1: i32, r2: i32, addr: i32) {
|
||||
// maskmovq mm, mm
|
||||
let source: [u8; 8] = std::mem::transmute(read_mmx64s(r2));
|
||||
let mask: [u8; 8] = std::mem::transmute(read_mmx64s(r1));
|
||||
let source: [u8; 8] = u64::to_le_bytes(read_mmx64s(r2));
|
||||
let mask: [u8; 8] = u64::to_le_bytes(read_mmx64s(r1));
|
||||
match writable_or_pagefault(addr, 8) {
|
||||
Ok(()) => *page_fault = false,
|
||||
Err(()) => {
|
||||
|
|
@ -5048,13 +5044,13 @@ pub unsafe fn instr_660FF7_reg(r1: i32, r2: i32) {
|
|||
#[no_mangle]
|
||||
pub unsafe fn instr_0FF8(source: u64, r: i32) {
|
||||
// psubb mm, mm/m64
|
||||
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
|
||||
let source: [u8; 8] = std::mem::transmute(source);
|
||||
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
|
||||
let source: [u8; 8] = u64::to_le_bytes(source);
|
||||
let mut result = [0; 8];
|
||||
for i in 0..8 {
|
||||
result[i] = destination[i] - source[i];
|
||||
}
|
||||
write_mmx_reg64(r, std::mem::transmute(result));
|
||||
write_mmx_reg64(r, u64::from_le_bytes(result));
|
||||
transition_fpu_to_mmx();
|
||||
}
|
||||
pub unsafe fn instr_0FF8_reg(r1: i32, r2: i32) { instr_0FF8(read_mmx64s(r1), r2); }
|
||||
|
|
@ -5166,13 +5162,13 @@ pub unsafe fn instr_660FFB_mem(addr: i32, r: i32) {
|
|||
#[no_mangle]
|
||||
pub unsafe fn instr_0FFC(source: u64, r: i32) {
|
||||
// paddb mm, mm/m64
|
||||
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
|
||||
let source: [u8; 8] = std::mem::transmute(source);
|
||||
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
|
||||
let source: [u8; 8] = u64::to_le_bytes(source);
|
||||
let mut result = [0; 8];
|
||||
for i in 0..8 {
|
||||
result[i] = destination[i] + source[i];
|
||||
}
|
||||
write_mmx_reg64(r, std::mem::transmute(result));
|
||||
write_mmx_reg64(r, u64::from_le_bytes(result));
|
||||
transition_fpu_to_mmx();
|
||||
}
|
||||
pub unsafe fn instr_0FFC_reg(r1: i32, r2: i32) { instr_0FFC(read_mmx64s(r1), r2); }
|
||||
|
|
|
|||
316
src/rust/cpu/ioapic.rs
Normal file
316
src/rust/cpu/ioapic.rs
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
// http://download.intel.com/design/chipsets/datashts/29056601.pdf
|
||||
|
||||
use crate::cpu::{apic, global_pointers::acpi_enabled};
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
const IOAPIC_LOG_VERBOSE: bool = false;
|
||||
|
||||
const IOREGSEL: u32 = 0;
|
||||
const IOWIN: u32 = 0x10;
|
||||
|
||||
const IOAPIC_IRQ_COUNT: usize = 24;
|
||||
|
||||
const IOAPIC_FIRST_IRQ_REG: u32 = 0x10;
|
||||
const IOAPIC_LAST_IRQ_REG: u32 = 0x10 + 2 * IOAPIC_IRQ_COUNT as u32;
|
||||
|
||||
const IOAPIC_ID: u32 = 0; // must match value in seabios
|
||||
|
||||
pub const IOAPIC_CONFIG_TRIGGER_MODE_LEVEL: u32 = 1 << 15;
|
||||
|
||||
const IOAPIC_CONFIG_MASKED: u32 = 1 << 16;
|
||||
const IOAPIC_CONFIG_DELIVS: u32 = 1 << 12;
|
||||
const IOAPIC_CONFIG_REMOTE_IRR: u32 = 1 << 14;
|
||||
const IOAPIC_CONFIG_READONLY_MASK: u32 =
|
||||
IOAPIC_CONFIG_REMOTE_IRR | IOAPIC_CONFIG_DELIVS | 0xFFFE0000;
|
||||
|
||||
const IOAPIC_DELIVERY_FIXED: u8 = 0;
|
||||
const IOAPIC_DELIVERY_LOWEST_PRIORITY: u8 = 1;
|
||||
const _IOAPIC_DELIVERY_NMI: u8 = 4;
|
||||
const _IOAPIC_DELIVERY_INIT: u8 = 5;
|
||||
|
||||
const DELIVERY_MODES: [&str; 8] = [
|
||||
"Fixed (0)",
|
||||
"Lowest Prio (1)",
|
||||
"SMI (2)",
|
||||
"Reserved (3)",
|
||||
"NMI (4)",
|
||||
"INIT (5)",
|
||||
"Reserved (6)",
|
||||
"ExtINT (7)",
|
||||
];
|
||||
|
||||
const DESTINATION_MODES: [&str; 2] = ["physical", "logical"];
|
||||
|
||||
// keep in sync with cpu.js
|
||||
#[allow(dead_code)]
|
||||
const IOAPIC_STRUCT_SIZE: usize = 4 * 52;
|
||||
|
||||
// Note: JavaScript (cpu.get_state_apic) depens on this layout
|
||||
const _: () = assert!(std::mem::offset_of!(Ioapic, ioredtbl_destination) == 24 * 4);
|
||||
const _: () = assert!(std::mem::offset_of!(Ioapic, ioregsel) == 48 * 4);
|
||||
const _: () = assert!(std::mem::offset_of!(Ioapic, irq_value) == 51 * 4);
|
||||
const _: () = assert!(std::mem::size_of::<Ioapic>() == IOAPIC_STRUCT_SIZE);
|
||||
#[repr(C)]
|
||||
struct Ioapic {
|
||||
ioredtbl_config: [u32; IOAPIC_IRQ_COUNT],
|
||||
ioredtbl_destination: [u32; IOAPIC_IRQ_COUNT],
|
||||
ioregsel: u32,
|
||||
ioapic_id: u32,
|
||||
irr: u32,
|
||||
irq_value: u32,
|
||||
}
|
||||
|
||||
static IOAPIC: Mutex<Ioapic> = Mutex::new(Ioapic {
|
||||
ioredtbl_config: [IOAPIC_CONFIG_MASKED; IOAPIC_IRQ_COUNT],
|
||||
ioredtbl_destination: [0; IOAPIC_IRQ_COUNT],
|
||||
ioregsel: 0,
|
||||
ioapic_id: IOAPIC_ID,
|
||||
irr: 0,
|
||||
irq_value: 0,
|
||||
});
|
||||
|
||||
fn get_ioapic() -> MutexGuard<'static, Ioapic> { IOAPIC.try_lock().unwrap() }
|
||||
|
||||
#[no_mangle]
|
||||
pub fn get_ioapic_addr() -> u32 { &raw mut *get_ioapic() as u32 }
|
||||
|
||||
pub fn remote_eoi(apic: &mut apic::Apic, vector: u8) {
|
||||
remote_eoi_internal(&mut get_ioapic(), apic, vector);
|
||||
}
|
||||
|
||||
fn remote_eoi_internal(ioapic: &mut Ioapic, apic: &mut apic::Apic, vector: u8) {
|
||||
for i in 0..IOAPIC_IRQ_COUNT as u8 {
|
||||
let config = ioapic.ioredtbl_config[i as usize];
|
||||
|
||||
if (config & 0xFF) as u8 == vector && config & IOAPIC_CONFIG_REMOTE_IRR != 0 {
|
||||
dbg_log!("Clear remote IRR for irq={:x}", i);
|
||||
ioapic.ioredtbl_config[i as usize] &= !IOAPIC_CONFIG_REMOTE_IRR;
|
||||
check_irq(ioapic, apic, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_irq(ioapic: &mut Ioapic, apic: &mut apic::Apic, irq: u8) {
|
||||
let mask = 1 << irq;
|
||||
|
||||
if ioapic.irr & mask == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let config = ioapic.ioredtbl_config[irq as usize];
|
||||
|
||||
if config & IOAPIC_CONFIG_MASKED == 0 {
|
||||
let delivery_mode = ((config >> 8) & 7) as u8;
|
||||
let destination_mode = ((config >> 11) & 1) as u8;
|
||||
let vector = (config & 0xFF) as u8;
|
||||
let destination = (ioapic.ioredtbl_destination[irq as usize] >> 24) as u8;
|
||||
let is_level =
|
||||
config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL == IOAPIC_CONFIG_TRIGGER_MODE_LEVEL;
|
||||
|
||||
if config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL == 0 {
|
||||
ioapic.irr &= !mask;
|
||||
}
|
||||
else {
|
||||
ioapic.ioredtbl_config[irq as usize] |= IOAPIC_CONFIG_REMOTE_IRR;
|
||||
|
||||
if config & IOAPIC_CONFIG_REMOTE_IRR != 0 {
|
||||
dbg_log!("No route: level interrupt and remote IRR still set");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if delivery_mode == IOAPIC_DELIVERY_FIXED
|
||||
|| delivery_mode == IOAPIC_DELIVERY_LOWEST_PRIORITY
|
||||
{
|
||||
apic::route(
|
||||
apic,
|
||||
vector,
|
||||
delivery_mode,
|
||||
is_level,
|
||||
destination,
|
||||
destination_mode,
|
||||
);
|
||||
}
|
||||
else {
|
||||
dbg_assert!(false, "TODO");
|
||||
}
|
||||
|
||||
ioapic.ioredtbl_config[irq as usize] &= !IOAPIC_CONFIG_DELIVS;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_irq(i: u8) { set_irq_internal(&mut get_ioapic(), &mut apic::get_apic(), i) }
|
||||
|
||||
fn set_irq_internal(ioapic: &mut Ioapic, apic: &mut apic::Apic, i: u8) {
|
||||
if i as usize >= IOAPIC_IRQ_COUNT {
|
||||
dbg_assert!(false, "Bad irq: {}", i);
|
||||
return;
|
||||
}
|
||||
|
||||
let mask = 1 << i;
|
||||
|
||||
if ioapic.irq_value & mask == 0 {
|
||||
if IOAPIC_LOG_VERBOSE {
|
||||
dbg_log!("apic set irq {}", i);
|
||||
}
|
||||
|
||||
ioapic.irq_value |= mask;
|
||||
|
||||
let config = ioapic.ioredtbl_config[i as usize];
|
||||
if config & (IOAPIC_CONFIG_TRIGGER_MODE_LEVEL | IOAPIC_CONFIG_MASKED)
|
||||
== IOAPIC_CONFIG_MASKED
|
||||
{
|
||||
// edge triggered and masked
|
||||
return;
|
||||
}
|
||||
|
||||
ioapic.irr |= mask;
|
||||
|
||||
check_irq(ioapic, apic, i);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_irq(i: u8) { clear_irq_internal(&mut get_ioapic(), i) }
|
||||
|
||||
fn clear_irq_internal(ioapic: &mut Ioapic, i: u8) {
|
||||
if i as usize >= IOAPIC_IRQ_COUNT {
|
||||
dbg_assert!(false, "Bad irq: {}", i);
|
||||
return;
|
||||
}
|
||||
|
||||
let mask = 1 << i;
|
||||
|
||||
if ioapic.irq_value & mask == mask {
|
||||
ioapic.irq_value &= !mask;
|
||||
|
||||
let config = ioapic.ioredtbl_config[i as usize];
|
||||
if config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL != 0 {
|
||||
ioapic.irr &= !mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read32(addr: u32) -> u32 {
|
||||
if unsafe { !*acpi_enabled } {
|
||||
return 0;
|
||||
}
|
||||
read32_internal(&mut get_ioapic(), addr)
|
||||
}
|
||||
|
||||
fn read32_internal(ioapic: &mut Ioapic, addr: u32) -> u32 {
|
||||
match addr {
|
||||
IOREGSEL => ioapic.ioregsel,
|
||||
IOWIN => match ioapic.ioregsel {
|
||||
0 => {
|
||||
dbg_log!("IOAPIC Read id");
|
||||
ioapic.ioapic_id << 24
|
||||
},
|
||||
1 => {
|
||||
dbg_log!("IOAPIC Read version");
|
||||
0x11 | (IOAPIC_IRQ_COUNT as u32 - 1) << 16
|
||||
},
|
||||
2 => {
|
||||
dbg_log!("IOAPIC Read arbitration id");
|
||||
ioapic.ioapic_id << 24
|
||||
},
|
||||
IOAPIC_FIRST_IRQ_REG..IOAPIC_LAST_IRQ_REG => {
|
||||
let irq = ((ioapic.ioregsel - IOAPIC_FIRST_IRQ_REG) >> 1) as u8;
|
||||
let index = ioapic.ioregsel & 1;
|
||||
|
||||
if index != 0 {
|
||||
let value = ioapic.ioredtbl_destination[irq as usize];
|
||||
dbg_log!("IOAPIC Read destination irq={:x} -> {:08x}", irq, value);
|
||||
value
|
||||
}
|
||||
else {
|
||||
let value = ioapic.ioredtbl_config[irq as usize];
|
||||
dbg_log!("IOAPIC Read config irq={:x} -> {:08x}", irq, value);
|
||||
value
|
||||
}
|
||||
},
|
||||
reg => {
|
||||
dbg_assert!(false, "IOAPIC register read outside of range {:x}", reg);
|
||||
0
|
||||
},
|
||||
},
|
||||
_ => {
|
||||
dbg_assert!(false, "Unaligned or oob IOAPIC memory read: {:x}", addr);
|
||||
0
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write32(addr: u32, value: u32) {
|
||||
if unsafe { !*acpi_enabled } {
|
||||
return;
|
||||
}
|
||||
write32_internal(&mut get_ioapic(), &mut apic::get_apic(), addr, value)
|
||||
}
|
||||
|
||||
fn write32_internal(ioapic: &mut Ioapic, apic: &mut apic::Apic, addr: u32, value: u32) {
|
||||
//dbg_log!("IOAPIC write {:x} <- {:08x}", reg, value);
|
||||
|
||||
match addr {
|
||||
IOREGSEL => ioapic.ioregsel = value,
|
||||
IOWIN => match ioapic.ioregsel {
|
||||
0 => ioapic.ioapic_id = (value >> 24) & 0x0F,
|
||||
1 | 2 => {
|
||||
dbg_log!("IOAPIC Invalid write: {}", ioapic.ioregsel);
|
||||
},
|
||||
IOAPIC_FIRST_IRQ_REG..IOAPIC_LAST_IRQ_REG => {
|
||||
let irq = ((ioapic.ioregsel - IOAPIC_FIRST_IRQ_REG) >> 1) as u8;
|
||||
let index = ioapic.ioregsel & 1;
|
||||
|
||||
if index != 0 {
|
||||
dbg_log!(
|
||||
"Write destination {:08x} irq={:x} dest={:02x}",
|
||||
value,
|
||||
irq,
|
||||
value >> 24
|
||||
);
|
||||
ioapic.ioredtbl_destination[irq as usize] = value & 0xFF000000;
|
||||
}
|
||||
else {
|
||||
let old_value = ioapic.ioredtbl_config[irq as usize] as u32;
|
||||
ioapic.ioredtbl_config[irq as usize] = (value & !IOAPIC_CONFIG_READONLY_MASK)
|
||||
| (old_value & IOAPIC_CONFIG_READONLY_MASK);
|
||||
|
||||
let vector = value & 0xFF;
|
||||
let delivery_mode = (value >> 8) & 7;
|
||||
let destination_mode = (value >> 11) & 1;
|
||||
let is_level = (value >> 15) & 1;
|
||||
let disabled = (value >> 16) & 1;
|
||||
|
||||
dbg_log!(
|
||||
"Write config {:08x} irq={:x} vector={:02x} deliverymode={} destmode={} is_level={} disabled={}",
|
||||
value,
|
||||
irq,
|
||||
vector,
|
||||
DELIVERY_MODES[delivery_mode as usize],
|
||||
DESTINATION_MODES[destination_mode as usize],
|
||||
is_level,
|
||||
disabled
|
||||
);
|
||||
|
||||
check_irq(ioapic, apic, irq);
|
||||
}
|
||||
},
|
||||
reg => {
|
||||
dbg_assert!(
|
||||
false,
|
||||
"IOAPIC register write outside of range {:x} <- {:x}",
|
||||
reg,
|
||||
value
|
||||
)
|
||||
},
|
||||
},
|
||||
_ => {
|
||||
dbg_assert!(
|
||||
false,
|
||||
"Unaligned or oob IOAPIC memory write: {:x} <- {:x}",
|
||||
addr,
|
||||
value
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -12,8 +12,12 @@ mod ext {
|
|||
}
|
||||
}
|
||||
|
||||
use crate::cpu::cpu::reg128;
|
||||
use crate::cpu::apic;
|
||||
use crate::cpu::cpu::{
|
||||
handle_irqs, reg128, APIC_MEM_ADDRESS, APIC_MEM_SIZE, IOAPIC_MEM_ADDRESS, IOAPIC_MEM_SIZE,
|
||||
};
|
||||
use crate::cpu::global_pointers::memory_size;
|
||||
use crate::cpu::ioapic;
|
||||
use crate::cpu::vga;
|
||||
use crate::jit;
|
||||
use crate::page::Page;
|
||||
|
|
@ -122,6 +126,12 @@ pub fn read32s(addr: u32) -> i32 {
|
|||
ptr::read_unaligned(vga_mem8.offset((addr - VGA_LFB_ADDRESS) as isize) as *const i32)
|
||||
} // XXX
|
||||
}
|
||||
else if addr >= APIC_MEM_ADDRESS && addr < APIC_MEM_ADDRESS + APIC_MEM_SIZE {
|
||||
apic::read32(addr - APIC_MEM_ADDRESS) as i32
|
||||
}
|
||||
else if addr >= IOAPIC_MEM_ADDRESS && addr < IOAPIC_MEM_ADDRESS + IOAPIC_MEM_SIZE {
|
||||
ioapic::read32(addr - IOAPIC_MEM_ADDRESS) as i32
|
||||
}
|
||||
else {
|
||||
unsafe { ext::mmap_read32(addr) }
|
||||
}
|
||||
|
|
@ -206,7 +216,7 @@ pub unsafe fn write32(addr: u32, value: i32) {
|
|||
else {
|
||||
jit::jit_dirty_cache_small(addr, addr + 4);
|
||||
write32_no_mmap_or_dirty_check(addr, value);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn write32_no_mmap_or_dirty_check(addr: u32, value: i32) {
|
||||
|
|
@ -238,6 +248,8 @@ pub unsafe fn memcpy_no_mmap_or_dirty_check(src_addr: u32, dst_addr: u32, count:
|
|||
pub unsafe fn memcpy_into_svga_lfb(src_addr: u32, dst_addr: u32, count: u32) {
|
||||
dbg_assert!(src_addr < *memory_size);
|
||||
dbg_assert!(in_svga_lfb(dst_addr));
|
||||
dbg_assert!(Page::page_of(dst_addr) == Page::page_of(dst_addr + count - 1));
|
||||
vga::mark_dirty(dst_addr);
|
||||
ptr::copy_nonoverlapping(
|
||||
mem8.offset(src_addr as isize),
|
||||
vga_mem8.offset((dst_addr - VGA_LFB_ADDRESS) as isize),
|
||||
|
|
@ -274,6 +286,14 @@ pub unsafe fn mmap_write32(addr: u32, value: i32) {
|
|||
value,
|
||||
)
|
||||
}
|
||||
else if addr >= APIC_MEM_ADDRESS && addr < APIC_MEM_ADDRESS + APIC_MEM_SIZE {
|
||||
apic::write32(addr - APIC_MEM_ADDRESS, value as u32);
|
||||
handle_irqs();
|
||||
}
|
||||
else if addr >= IOAPIC_MEM_ADDRESS && addr < IOAPIC_MEM_ADDRESS + IOAPIC_MEM_SIZE {
|
||||
ioapic::write32(addr - IOAPIC_MEM_ADDRESS, value as u32);
|
||||
handle_irqs();
|
||||
}
|
||||
else {
|
||||
ext::mmap_write32(addr, value)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
pub mod apic;
|
||||
pub mod arith;
|
||||
pub mod call_indirect;
|
||||
pub mod cpu;
|
||||
|
|
@ -5,6 +6,7 @@ pub mod fpu;
|
|||
pub mod global_pointers;
|
||||
pub mod instructions;
|
||||
pub mod instructions_0f;
|
||||
pub mod ioapic;
|
||||
pub mod memory;
|
||||
pub mod misc_instr;
|
||||
pub mod modrm;
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@
|
|||
// Programmable Interrupt Controller
|
||||
// http://stanislavs.org/helppc/8259.html
|
||||
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
pub const PIC_LOG: bool = false;
|
||||
pub const PIC_LOG_VERBOSE: bool = false;
|
||||
|
||||
use crate::cpu::cpu;
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
// Note: This layout is deliberately chosen to match the old JavaScript pic state
|
||||
// (cpu.get_state_pic depens on this layout)
|
||||
#[repr(C, packed)]
|
||||
const _: () = assert!(std::mem::offset_of!(Pic0, special_mask_mode) == 12);
|
||||
#[repr(C)]
|
||||
struct Pic0 {
|
||||
irq_mask: u8,
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ struct Pic0 {
|
|||
special_mask_mode: bool,
|
||||
}
|
||||
|
||||
pub struct Pic {
|
||||
struct Pic {
|
||||
master: Pic0,
|
||||
slave: Pic0,
|
||||
}
|
||||
|
|
@ -91,17 +91,16 @@ static PIC: Mutex<Pic> = Mutex::new(Pic {
|
|||
},
|
||||
});
|
||||
|
||||
pub fn get_pic() -> MutexGuard<'static, Pic> { PIC.try_lock().unwrap() }
|
||||
fn get_pic() -> MutexGuard<'static, Pic> { PIC.try_lock().unwrap() }
|
||||
|
||||
// Checking for callable interrupts:
|
||||
// (cpu changes interrupt flag) -> cpu.handle_irqs -> pic_acknowledge_irq
|
||||
// (pic changes isr/irr) -> pic.check_irqs -> cpu.handle_irqs -> ...
|
||||
|
||||
// triggering irqs:
|
||||
// (io device has irq) -> cpu.device_raise_irq -> pic.set_irq -> pic.check_irqs -> cpu.handle_irqs -> (see above)
|
||||
// called from javascript for saving/restoring state
|
||||
#[no_mangle]
|
||||
pub fn get_pic_addr_master() -> u32 { &raw mut get_pic().master as u32 }
|
||||
#[no_mangle]
|
||||
pub fn get_pic_addr_slave() -> u32 { &raw mut get_pic().slave as u32 }
|
||||
|
||||
impl Pic0 {
|
||||
unsafe fn get_irq(&mut self) -> Option<u8> {
|
||||
fn get_irq(&mut self) -> Option<u8> {
|
||||
let enabled_irr = self.irr & self.irq_mask;
|
||||
|
||||
if enabled_irr == 0 {
|
||||
|
|
@ -144,44 +143,36 @@ impl Pic0 {
|
|||
Some(irq_number)
|
||||
}
|
||||
|
||||
unsafe fn port0_read(self: &Pic0) -> u32 {
|
||||
(if self.read_isr { self.isr } else { self.irr }) as u32
|
||||
}
|
||||
unsafe fn port1_read(self: &Pic0) -> u32 { !self.irq_mask as u32 }
|
||||
fn port0_read(self: &Pic0) -> u32 { (if self.read_isr { self.isr } else { self.irr }) as u32 }
|
||||
fn port1_read(self: &Pic0) -> u32 { !self.irq_mask as u32 }
|
||||
}
|
||||
|
||||
impl Pic {
|
||||
unsafe fn set_irq(self: &mut Pic, i: u8) {
|
||||
fn set_irq(self: &mut Pic, i: u8) {
|
||||
let mask = 1 << (i & 7);
|
||||
let dev = if i < 8 { &mut self.master } else { &mut self.slave };
|
||||
if dev.irq_value & mask == 0 || dev.elcr & mask != 0 {
|
||||
dev.irr |= mask;
|
||||
dev.irq_value |= mask;
|
||||
if i < 8 {
|
||||
self.check_irqs_master()
|
||||
}
|
||||
else {
|
||||
if i >= 8 {
|
||||
self.check_irqs_slave()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn clear_irq(self: &mut Pic, i: u8) {
|
||||
fn clear_irq(self: &mut Pic, i: u8) {
|
||||
let mask = 1 << (i & 7);
|
||||
let dev = if i < 8 { &mut self.master } else { &mut self.slave };
|
||||
dev.irq_value &= !mask;
|
||||
if dev.elcr & mask != 0 {
|
||||
dev.irr &= !mask;
|
||||
if i < 8 {
|
||||
self.check_irqs_master()
|
||||
}
|
||||
else {
|
||||
if i >= 8 {
|
||||
self.check_irqs_slave()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn port0_write(&mut self, index: u8, v: u8) {
|
||||
fn port0_write(&mut self, index: u8, v: u8) {
|
||||
let dev = if index == 0 { &mut self.master } else { &mut self.slave };
|
||||
if v & 0x10 != 0 {
|
||||
// xxxx1xxx
|
||||
|
|
@ -245,16 +236,13 @@ impl Pic {
|
|||
dev.isr &= dev.isr - 1;
|
||||
}
|
||||
|
||||
if index == 0 {
|
||||
self.check_irqs_master()
|
||||
}
|
||||
else {
|
||||
if index == 1 {
|
||||
self.check_irqs_slave()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn port1_write(&mut self, index: u8, v: u8) {
|
||||
fn port1_write(&mut self, index: u8, v: u8) {
|
||||
let dev = if index == 0 { &mut self.master } else { &mut self.slave };
|
||||
if dev.state == 0 {
|
||||
if dev.expect_icw4 {
|
||||
|
|
@ -273,10 +261,7 @@ impl Pic {
|
|||
dbg_log!("interrupt mask: {:x}", dev.irq_mask);
|
||||
}
|
||||
|
||||
if index == 0 {
|
||||
self.check_irqs_master()
|
||||
}
|
||||
else {
|
||||
if index == 1 {
|
||||
self.check_irqs_slave()
|
||||
}
|
||||
}
|
||||
|
|
@ -294,13 +279,7 @@ impl Pic {
|
|||
}
|
||||
}
|
||||
|
||||
unsafe fn check_irqs_master(&mut self) {
|
||||
let is_set = self.master.get_irq().is_some();
|
||||
if is_set {
|
||||
cpu::handle_irqs_internal(self);
|
||||
}
|
||||
}
|
||||
unsafe fn check_irqs_slave(&mut self) {
|
||||
fn check_irqs_slave(&mut self) {
|
||||
let is_set = self.slave.get_irq().is_some();
|
||||
if is_set {
|
||||
self.set_irq(2)
|
||||
|
|
@ -312,7 +291,8 @@ impl Pic {
|
|||
}
|
||||
|
||||
// called by the cpu
|
||||
pub unsafe fn pic_acknowledge_irq(pic: &mut Pic) -> Option<u8> {
|
||||
pub fn pic_acknowledge_irq() -> Option<u8> {
|
||||
let mut pic = get_pic();
|
||||
let irq = match pic.master.get_irq() {
|
||||
Some(i) => i,
|
||||
None => return None,
|
||||
|
|
@ -343,14 +323,14 @@ pub unsafe fn pic_acknowledge_irq(pic: &mut Pic) -> Option<u8> {
|
|||
dbg_assert!(pic.master.get_irq().is_none());
|
||||
|
||||
if irq == 2 {
|
||||
acknowledge_irq_slave(pic)
|
||||
acknowledge_irq_slave(&mut pic)
|
||||
}
|
||||
else {
|
||||
Some(pic.master.irq_map | irq)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn acknowledge_irq_slave(pic: &mut Pic) -> Option<u8> {
|
||||
fn acknowledge_irq_slave(pic: &mut Pic) -> Option<u8> {
|
||||
let irq = match pic.slave.get_irq() {
|
||||
Some(i) => i,
|
||||
None => return None,
|
||||
|
|
@ -384,9 +364,7 @@ unsafe fn acknowledge_irq_slave(pic: &mut Pic) -> Option<u8> {
|
|||
Some(pic.slave.irq_map | irq)
|
||||
}
|
||||
|
||||
// called by javascript
|
||||
#[no_mangle]
|
||||
pub unsafe fn pic_set_irq(i: u8) {
|
||||
pub fn set_irq(i: u8) {
|
||||
dbg_assert!(i < 16);
|
||||
|
||||
if PIC_LOG_VERBOSE {
|
||||
|
|
@ -396,9 +374,7 @@ pub unsafe fn pic_set_irq(i: u8) {
|
|||
get_pic().set_irq(i)
|
||||
}
|
||||
|
||||
// called by javascript
|
||||
#[no_mangle]
|
||||
pub unsafe fn pic_clear_irq(i: u8) {
|
||||
pub fn clear_irq(i: u8) {
|
||||
dbg_assert!(i < 16);
|
||||
|
||||
if PIC_LOG_VERBOSE {
|
||||
|
|
@ -408,36 +384,19 @@ pub unsafe fn pic_clear_irq(i: u8) {
|
|||
get_pic().clear_irq(i)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe fn port20_read() -> u32 { get_pic().master.port0_read() }
|
||||
#[no_mangle]
|
||||
pub unsafe fn port21_read() -> u32 { get_pic().master.port1_read() }
|
||||
pub fn port20_read() -> u32 { get_pic().master.port0_read() }
|
||||
pub fn port21_read() -> u32 { get_pic().master.port1_read() }
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe fn portA0_read() -> u32 { get_pic().slave.port0_read() }
|
||||
#[no_mangle]
|
||||
pub unsafe fn portA1_read() -> u32 { get_pic().slave.port1_read() }
|
||||
pub fn portA0_read() -> u32 { get_pic().slave.port0_read() }
|
||||
pub fn portA1_read() -> u32 { get_pic().slave.port1_read() }
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe fn port20_write(v: u8) { get_pic().port0_write(0, v) }
|
||||
#[no_mangle]
|
||||
pub unsafe fn port21_write(v: u8) { get_pic().port1_write(0, v) }
|
||||
pub fn port20_write(v: u8) { get_pic().port0_write(0, v) }
|
||||
pub fn port21_write(v: u8) { get_pic().port1_write(0, v) }
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe fn portA0_write(v: u8) { get_pic().port0_write(1, v) }
|
||||
#[no_mangle]
|
||||
pub unsafe fn portA1_write(v: u8) { get_pic().port1_write(1, v) }
|
||||
pub fn portA0_write(v: u8) { get_pic().port0_write(1, v) }
|
||||
pub fn portA1_write(v: u8) { get_pic().port1_write(1, v) }
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe fn port4D0_read() -> u32 { get_pic().master.elcr as u32 }
|
||||
#[no_mangle]
|
||||
pub unsafe fn port4D1_read() -> u32 { get_pic().slave.elcr as u32 }
|
||||
#[no_mangle]
|
||||
pub unsafe fn port4D0_write(v: u8) { get_pic().master.elcr = v }
|
||||
#[no_mangle]
|
||||
pub unsafe fn port4D1_write(v: u8) { get_pic().slave.elcr = v }
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe fn get_pic_addr_master() -> u32 { &raw const get_pic().master as u32 }
|
||||
#[no_mangle]
|
||||
pub unsafe fn get_pic_addr_slave() -> u32 { &raw const get_pic().slave as u32 }
|
||||
pub fn port4D0_read() -> u32 { get_pic().master.elcr as u32 }
|
||||
pub fn port4D1_read() -> u32 { get_pic().slave.elcr as u32 }
|
||||
pub fn port4D0_write(v: u8) { get_pic().master.elcr = v }
|
||||
pub fn port4D1_write(v: u8) { get_pic().slave.elcr = v }
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
// ins 0 0 1/w
|
||||
// outs 0 1 0
|
||||
|
||||
use crate::cpu;
|
||||
use crate::cpu::arith::{cmp16, cmp32, cmp8};
|
||||
use crate::cpu::cpu::{
|
||||
get_seg, io_port_read16, io_port_read32, io_port_read8, io_port_write16, io_port_write32,
|
||||
|
|
@ -300,7 +299,6 @@ unsafe fn string_instruction(
|
|||
phys_dst -= (count_until_end_of_page - 1) * size_bytes as u32;
|
||||
}
|
||||
if movs_into_svga_lfb {
|
||||
cpu::vga::mark_dirty(phys_dst);
|
||||
memory::memcpy_into_svga_lfb(
|
||||
phys_src,
|
||||
phys_dst,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,43 @@
|
|||
#[allow(dead_code)]
|
||||
pub const DEBUG: bool = cfg!(debug_assertions);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
extern "C" {
|
||||
pub fn log_from_wasm(ptr: *const u8, len: usize);
|
||||
pub fn console_log_from_wasm(ptr: *const u8, len: usize);
|
||||
pub fn dbg_trace_from_wasm();
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn log_to_js_console<T: std::string::ToString>(s: T) {
|
||||
let s = s.to_string();
|
||||
let len = s.len();
|
||||
unsafe {
|
||||
log_from_wasm(s.as_bytes().as_ptr(), len);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn console_log_to_js_console<T: std::string::ToString>(s: T) {
|
||||
let s = s.to_string();
|
||||
let len = s.len();
|
||||
unsafe {
|
||||
console_log_from_wasm(s.as_bytes().as_ptr(), len);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn dbg_trace() {
|
||||
if DEBUG {
|
||||
unsafe {
|
||||
dbg_trace_from_wasm();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn dbg_trace() {}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! dbg_log {
|
||||
($fmt:expr) => {
|
||||
|
|
@ -30,14 +70,12 @@ macro_rules! dbg_assert {
|
|||
macro_rules! console_log {
|
||||
($fmt:expr) => {
|
||||
{
|
||||
use crate::util::{ console_log_to_js_console };
|
||||
console_log_to_js_console($fmt);
|
||||
crate::dbg::console_log_to_js_console($fmt);
|
||||
}
|
||||
};
|
||||
($fmt:expr, $($arg:tt)*) => {
|
||||
{
|
||||
use crate::util::{ console_log_to_js_console };
|
||||
console_log_to_js_console(format!($fmt, $($arg)*));
|
||||
crate::dbg::console_log_to_js_console(format!($fmt, $($arg)*));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -47,52 +85,14 @@ macro_rules! console_log {
|
|||
macro_rules! dbg_log {
|
||||
($fmt:expr) => {
|
||||
{
|
||||
use crate::util::{ DEBUG, log_to_js_console };
|
||||
use crate::dbg::{ DEBUG, log_to_js_console };
|
||||
if DEBUG { log_to_js_console($fmt); }
|
||||
}
|
||||
};
|
||||
($fmt:expr, $($arg:tt)*) => {
|
||||
{
|
||||
use crate::util::{ DEBUG, log_to_js_console };
|
||||
use crate::dbg::{ DEBUG, log_to_js_console };
|
||||
if DEBUG { log_to_js_console(format!($fmt, $($arg)*)); }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! dbg_assert {
|
||||
($cond:expr) => {{
|
||||
use crate::util::{abort, log_to_js_console, DEBUG};
|
||||
if DEBUG && !$cond {
|
||||
log_to_js_console(format!(
|
||||
"Assertion failed at {}:{}:{}: '{}'",
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
stringify!($cond),
|
||||
));
|
||||
#[allow(unused_unsafe)]
|
||||
unsafe {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}};
|
||||
($cond:expr, $desc:expr) => {{
|
||||
use crate::util::{abort, log_to_js_console, DEBUG};
|
||||
if DEBUG && !$cond {
|
||||
log_to_js_console(format!(
|
||||
"Assertion failed at {}:{}:{}: '{}' - '{}'",
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
stringify!($cond),
|
||||
$desc,
|
||||
));
|
||||
#[allow(unused_unsafe)]
|
||||
unsafe {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ use crate::page::Page;
|
|||
use crate::profiler;
|
||||
use crate::profiler::stat;
|
||||
use crate::state_flags::CachedStateFlags;
|
||||
use crate::util::SafeToU16;
|
||||
use crate::wasmgen::wasm_builder::{Label, WasmBuilder, WasmLocal};
|
||||
|
||||
#[derive(Copy, Clone, Eq, Hash, PartialEq)]
|
||||
|
|
@ -486,7 +485,7 @@ fn jit_find_basic_blocks(
|
|||
let mut pages: HashSet<Page> = 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 {
|
||||
|
|
@ -540,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);
|
||||
|
|
@ -558,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),
|
||||
|
|
@ -630,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 {
|
||||
|
|
@ -670,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;
|
||||
},
|
||||
|
|
@ -691,24 +690,25 @@ 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()
|
||||
.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 {
|
||||
|
|
@ -1230,28 +1230,30 @@ fn jit_generate_module(
|
|||
|
||||
let mut index_for_addr = HashMap::new();
|
||||
for (i, &addr) in entry_blocks.iter().enumerate() {
|
||||
index_for_addr.insert(addr, i as i32);
|
||||
dbg_assert!(i < 0x10000);
|
||||
index_for_addr.insert(addr, i as u16);
|
||||
}
|
||||
for b in basic_blocks.values() {
|
||||
if !index_for_addr.contains_key(&b.addr) {
|
||||
let i = index_for_addr.len();
|
||||
index_for_addr.insert(b.addr, i as i32);
|
||||
dbg_assert!(i < 0x10000);
|
||||
index_for_addr.insert(b.addr, i as u16);
|
||||
}
|
||||
}
|
||||
|
||||
let mut label_for_addr: HashMap<u32, (Label, Option<i32>)> = HashMap::new();
|
||||
let mut label_for_addr: HashMap<u32, (Label, Option<u16>)> = HashMap::new();
|
||||
|
||||
enum Work {
|
||||
WasmStructure(WasmStructure),
|
||||
BlockEnd {
|
||||
label: Label,
|
||||
targets: Vec<u32>,
|
||||
olds: HashMap<u32, (Label, Option<i32>)>,
|
||||
olds: HashMap<u32, (Label, Option<u16>)>,
|
||||
},
|
||||
LoopEnd {
|
||||
label: Label,
|
||||
entries: Vec<u32>,
|
||||
olds: HashMap<u32, (Label, Option<i32>)>,
|
||||
olds: HashMap<u32, (Label, Option<u16>)>,
|
||||
},
|
||||
}
|
||||
let mut work: VecDeque<Work> = structure
|
||||
|
|
@ -1423,10 +1425,10 @@ fn jit_generate_module(
|
|||
if next_addr.unwrap().len() > 1 {
|
||||
let target_index = *index_for_addr.get(&next_block_addr).unwrap();
|
||||
if cfg!(feature = "profiler") {
|
||||
ctx.builder.const_i32(target_index);
|
||||
ctx.builder.const_i32(target_index.into());
|
||||
ctx.builder.call_fn1("debug_set_dispatcher_target");
|
||||
}
|
||||
ctx.builder.const_i32(target_index);
|
||||
ctx.builder.const_i32(target_index.into());
|
||||
ctx.builder.set_local(target_block);
|
||||
codegen::gen_profiler_stat_increment(
|
||||
ctx.builder,
|
||||
|
|
@ -1444,10 +1446,10 @@ fn jit_generate_module(
|
|||
let &(br, target_index) = label_for_addr.get(&next_block_addr).unwrap();
|
||||
if let Some(target_index) = target_index {
|
||||
if cfg!(feature = "profiler") {
|
||||
ctx.builder.const_i32(target_index);
|
||||
ctx.builder.const_i32(target_index.into());
|
||||
ctx.builder.call_fn1("debug_set_dispatcher_target");
|
||||
}
|
||||
ctx.builder.const_i32(target_index);
|
||||
ctx.builder.const_i32(target_index.into());
|
||||
ctx.builder.set_local(target_block);
|
||||
codegen::gen_profiler_stat_increment(
|
||||
ctx.builder,
|
||||
|
|
@ -1571,10 +1573,10 @@ fn jit_generate_module(
|
|||
let target_index =
|
||||
*index_for_addr.get(&next_block_addr).unwrap();
|
||||
if cfg!(feature = "profiler") {
|
||||
ctx.builder.const_i32(target_index);
|
||||
ctx.builder.const_i32(target_index.into());
|
||||
ctx.builder.call_fn1("debug_set_dispatcher_target");
|
||||
}
|
||||
ctx.builder.const_i32(target_index);
|
||||
ctx.builder.const_i32(target_index.into());
|
||||
ctx.builder.set_local(target_block);
|
||||
codegen::gen_profiler_stat_increment(
|
||||
ctx.builder,
|
||||
|
|
@ -1595,10 +1597,10 @@ fn jit_generate_module(
|
|||
if cfg!(feature = "profiler") {
|
||||
// Note: Currently called unconditionally, even if the
|
||||
// br_if below doesn't branch
|
||||
ctx.builder.const_i32(target_index);
|
||||
ctx.builder.const_i32(target_index.into());
|
||||
ctx.builder.call_fn1("debug_set_dispatcher_target");
|
||||
}
|
||||
ctx.builder.const_i32(target_index);
|
||||
ctx.builder.const_i32(target_index.into());
|
||||
ctx.builder.set_local(target_block);
|
||||
}
|
||||
|
||||
|
|
@ -1751,11 +1753,11 @@ fn jit_generate_module(
|
|||
let target_index_not_taken =
|
||||
*index_for_addr.get(&next_block_addr).unwrap();
|
||||
|
||||
ctx.builder.const_i32(target_index_taken);
|
||||
ctx.builder.const_i32(target_index_taken.into());
|
||||
ctx.builder.set_local(target_block);
|
||||
|
||||
ctx.builder.else_();
|
||||
ctx.builder.const_i32(target_index_not_taken);
|
||||
ctx.builder.const_i32(target_index_not_taken.into());
|
||||
ctx.builder.set_local(target_block);
|
||||
|
||||
ctx.builder.block_end();
|
||||
|
|
@ -1768,9 +1770,9 @@ fn jit_generate_module(
|
|||
|
||||
codegen::gen_condition_fn(ctx, condition);
|
||||
ctx.builder.if_i32();
|
||||
ctx.builder.const_i32(target_index_taken);
|
||||
ctx.builder.const_i32(target_index_taken.into());
|
||||
ctx.builder.else_();
|
||||
ctx.builder.const_i32(target_index_not_taken);
|
||||
ctx.builder.const_i32(target_index_not_taken.into());
|
||||
ctx.builder.block_end();
|
||||
ctx.builder.set_local(target_block);
|
||||
}
|
||||
|
|
@ -1824,7 +1826,7 @@ fn jit_generate_module(
|
|||
let index = *index_for_addr.get(&addr).unwrap();
|
||||
let &(label, _) = label_for_addr.get(&addr).unwrap();
|
||||
ctx.builder.get_local(target_block);
|
||||
ctx.builder.const_i32(index);
|
||||
ctx.builder.const_i32(index.into());
|
||||
ctx.builder.eq_i32();
|
||||
ctx.builder.br_if(label);
|
||||
}
|
||||
|
|
@ -1986,8 +1988,7 @@ fn jit_generate_module(
|
|||
// Note: We also insert blocks that weren't originally marked as entries here
|
||||
// This doesn't have any downside, besides making the hash table slightly larger
|
||||
|
||||
let initial_state = index.safe_to_u16();
|
||||
(block.addr, initial_state)
|
||||
(block.addr, index)
|
||||
}));
|
||||
|
||||
for b in basic_blocks.values() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
#![allow(const_item_mutation)]
|
||||
|
||||
#[macro_use]
|
||||
mod dbg;
|
||||
|
||||
|
|
@ -27,6 +25,5 @@ mod prefix;
|
|||
mod regs;
|
||||
mod softfloat;
|
||||
mod state_flags;
|
||||
mod util;
|
||||
mod wasmgen;
|
||||
mod zstd;
|
||||
|
|
|
|||
|
|
@ -112,11 +112,11 @@ impl F80 {
|
|||
unsafe { f64_to_extF80M(src, &mut x) };
|
||||
x
|
||||
}
|
||||
fn of_f64x(src: f64) -> F80 { F80::of_f64(unsafe { std::mem::transmute(src) }) }
|
||||
fn of_f64x(src: f64) -> F80 { F80::of_f64(f64::to_bits(src)) }
|
||||
|
||||
pub fn to_f32(&self) -> i32 { unsafe { extF80M_to_f32(self) } }
|
||||
pub fn to_f64(&self) -> u64 { unsafe { extF80M_to_f64(self) } }
|
||||
fn to_f64x(&self) -> f64 { unsafe { std::mem::transmute(extF80M_to_f64(self)) } }
|
||||
fn to_f64x(&self) -> f64 { f64::from_bits(self.to_f64()) }
|
||||
|
||||
pub fn to_i32(&self) -> i32 { unsafe { extF80M_to_i32(self, softfloat_roundingMode, false) } }
|
||||
pub fn to_i64(&self) -> i64 { unsafe { extF80M_to_i64(self, softfloat_roundingMode, false) } }
|
||||
|
|
|
|||
|
|
@ -1,98 +0,0 @@
|
|||
pub trait SafeToU8 {
|
||||
fn safe_to_u8(self) -> u8;
|
||||
}
|
||||
pub trait SafeToU16 {
|
||||
fn safe_to_u16(self) -> u16;
|
||||
}
|
||||
|
||||
impl SafeToU8 for u16 {
|
||||
fn safe_to_u8(self) -> u8 {
|
||||
dbg_assert!(self <= ::std::u8::MAX as u16);
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl SafeToU8 for u32 {
|
||||
fn safe_to_u8(self) -> u8 {
|
||||
dbg_assert!(self <= ::std::u8::MAX as u32);
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl SafeToU8 for i32 {
|
||||
fn safe_to_u8(self) -> u8 {
|
||||
dbg_assert!(self >= 0 && self <= ::std::u8::MAX as i32);
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl SafeToU8 for usize {
|
||||
fn safe_to_u8(self) -> u8 {
|
||||
dbg_assert!(self <= ::std::u8::MAX as usize);
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl SafeToU16 for u32 {
|
||||
fn safe_to_u16(self) -> u16 {
|
||||
dbg_assert!(self <= ::std::u16::MAX as u32);
|
||||
self as u16
|
||||
}
|
||||
}
|
||||
|
||||
impl SafeToU16 for i32 {
|
||||
fn safe_to_u16(self) -> u16 {
|
||||
dbg_assert!(self >= 0 && self <= ::std::u16::MAX as i32);
|
||||
self as u16
|
||||
}
|
||||
}
|
||||
|
||||
impl SafeToU16 for usize {
|
||||
fn safe_to_u16(self) -> u16 {
|
||||
dbg_assert!(self <= ::std::u16::MAX as usize);
|
||||
self as u16
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const DEBUG: bool = cfg!(debug_assertions);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
extern "C" {
|
||||
pub fn log_from_wasm(ptr: *const u8, len: usize);
|
||||
pub fn console_log_from_wasm(ptr: *const u8, len: usize);
|
||||
pub fn abort();
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
pub fn dbg_trace_from_wasm();
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use std::string::ToString;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn log_to_js_console<T: ToString>(s: T) {
|
||||
let s = s.to_string();
|
||||
let len = s.len();
|
||||
unsafe {
|
||||
log_from_wasm(s.as_bytes().as_ptr(), len);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn console_log_to_js_console<T: ToString>(s: T) {
|
||||
let s = s.to_string();
|
||||
let len = s.len();
|
||||
unsafe {
|
||||
console_log_from_wasm(s.as_bytes().as_ptr(), len);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dbg_trace() {
|
||||
if DEBUG {
|
||||
unsafe {
|
||||
dbg_trace_from_wasm();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,9 +4,28 @@ use std::mem::transmute;
|
|||
use crate::leb::{
|
||||
write_fixed_leb16_at_idx, write_fixed_leb32_at_idx, write_leb_i32, write_leb_i64, write_leb_u32,
|
||||
};
|
||||
use crate::util::{SafeToU16, SafeToU8};
|
||||
use crate::wasmgen::wasm_opcodes as op;
|
||||
|
||||
pub trait SafeToU8 {
|
||||
fn safe_to_u8(self) -> u8;
|
||||
}
|
||||
impl SafeToU8 for usize {
|
||||
fn safe_to_u8(self) -> u8 {
|
||||
dbg_assert!(self <= ::std::u8::MAX as usize);
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SafeToU16 {
|
||||
fn safe_to_u16(self) -> u16;
|
||||
}
|
||||
impl SafeToU16 for usize {
|
||||
fn safe_to_u16(self) -> u16 {
|
||||
dbg_assert!(self <= ::std::u16::MAX as usize);
|
||||
self as u16
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[allow(non_camel_case_types)]
|
||||
enum FunctionType {
|
||||
|
|
|
|||
73
src/sb16.js
73
src/sb16.js
|
|
@ -1,4 +1,18 @@
|
|||
"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:
|
||||
// ===============================================================
|
||||
|
|
@ -25,55 +39,54 @@
|
|||
// -> 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
|
||||
|
|
@ -92,7 +105,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 +163,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 +412,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)
|
||||
{
|
||||
|
|
@ -1526,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;
|
||||
|
|
|
|||
99
src/state.js
99
src/state.js
|
|
@ -1,25 +1,14 @@
|
|||
"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;
|
||||
|
||||
/** @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;
|
||||
|
||||
|
|
@ -31,6 +20,7 @@ function StateLoadError(msg)
|
|||
StateLoadError.prototype = new Error;
|
||||
|
||||
const CONSTRUCTOR_TABLE = {
|
||||
"Map": Map,
|
||||
"Uint8Array": Uint8Array,
|
||||
"Int8Array": Int8Array,
|
||||
"Uint16Array": Uint16Array,
|
||||
|
|
@ -54,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);
|
||||
|
|
@ -119,14 +120,19 @@ 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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
@ -188,9 +194,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);
|
||||
|
||||
|
|
@ -235,19 +242,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) >>> 0, 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 >>> 0, 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 >>> 0, 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"];
|
||||
|
|
@ -262,8 +269,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) >>> 0;
|
||||
cpu.zstd_read_free(ptr, front_padding);
|
||||
|
||||
const buffer = new Uint8Array(buffer_info.length);
|
||||
buffers.push(buffer.buffer);
|
||||
|
|
@ -275,28 +282,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 >>> 0, 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 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);
|
||||
const ptr = cpu.zstd_read(ctx, front_padding + buffer_info.length);
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
|
|
@ -320,6 +327,6 @@ CPU.prototype.restore_state = function(state)
|
|||
});
|
||||
|
||||
state_object = restore_buffers(state_object, buffers);
|
||||
this.set_state(state_object);
|
||||
cpu.set_state(state_object);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
61
src/uart.js
61
src/uart.js
|
|
@ -1,4 +1,10 @@
|
|||
"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
|
||||
|
|
@ -7,35 +13,36 @@
|
|||
* 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 */
|
||||
// Modem control register
|
||||
const UART_MCR_LOOPBACK = 0x10;
|
||||
|
||||
/** @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
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -44,7 +51,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;
|
||||
|
|
@ -395,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)
|
||||
{
|
||||
|
|
|
|||
42
src/vga.js
42
src/vga.js
|
|
@ -1,4 +1,13 @@
|
|||
"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;
|
||||
|
|
@ -34,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([
|
||||
|
|
@ -51,7 +59,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 +224,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 +364,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;
|
||||
|
|
@ -566,7 +574,7 @@ VGAScreen.prototype.vga_memory_read = function(addr)
|
|||
// VGA chip only decodes addresses within the selected memory space.
|
||||
if(addr < 0 || addr >= VGA_HOST_MEMORY_SPACE_SIZE[memory_space_select])
|
||||
{
|
||||
dbg_log("vga read outside memory space: addr:" + h(addr), LOG_VGA);
|
||||
dbg_log("vga read outside memory space: addr:" + h(addr >>> 0), LOG_VGA);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -640,7 +648,7 @@ VGAScreen.prototype.vga_memory_write = function(addr, value)
|
|||
|
||||
if(addr < 0 || addr >= VGA_HOST_MEMORY_SPACE_SIZE[memory_space_select])
|
||||
{
|
||||
dbg_log("vga write outside memory space: addr:" + h(addr) + ", value:" + h(value), LOG_VGA);
|
||||
dbg_log("vga write outside memory space: addr:" + h(addr >>> 0) + ", value:" + h(value), LOG_VGA);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1210,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);
|
||||
|
|
@ -1231,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();
|
||||
|
|
@ -2198,7 +2212,7 @@ VGAScreen.prototype.port1CF_write = function(value)
|
|||
}
|
||||
else
|
||||
{
|
||||
dbg_log("SVGA: disabled");
|
||||
dbg_log("SVGA: disabled", LOG_VGA);
|
||||
}
|
||||
|
||||
if(this.svga_enabled && !was_enabled)
|
||||
|
|
@ -2212,6 +2226,16 @@ VGAScreen.prototype.port1CF_write = function(value)
|
|||
this.set_size_graphical(this.svga_width, this.svga_height, this.svga_width, this.svga_height, this.svga_bpp);
|
||||
}
|
||||
|
||||
if(was_enabled && !this.svga_enabled)
|
||||
{
|
||||
const is_graphical = (this.attribute_mode & 0x1) !== 0;
|
||||
this.graphical_mode = is_graphical;
|
||||
this.screen.set_mode(is_graphical);
|
||||
this.update_vga_size();
|
||||
this.set_font_bitmap(false);
|
||||
this.complete_redraw();
|
||||
}
|
||||
|
||||
if(!this.svga_enabled)
|
||||
{
|
||||
this.svga_bank_offset = 0;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
"use strict";
|
||||
import { LOG_VIRTIO } from "./const.js";
|
||||
import { h, 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
|
||||
|
||||
|
|
@ -32,9 +38,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 +159,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 +230,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(Array(256 - this.pci_space.length).fill(0));
|
||||
// Remaining PCI space is appended by capabilities further below.
|
||||
|
||||
this.pci_id = options.pci_id;
|
||||
|
|
@ -480,7 +486,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 +733,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");
|
||||
|
|
@ -819,6 +825,13 @@ VirtIO.prototype.init_capabilities = function(capabilities)
|
|||
return read(addr & ~3) >> ((addr & 3) << 3) & 0xFF;
|
||||
};
|
||||
|
||||
// archhurd does these reads
|
||||
const shim_read32_on_16 = function(addr)
|
||||
{
|
||||
dbg_log("Warning: 32-bit read from 16-bit virtio port", LOG_VIRTIO);
|
||||
return read(addr);
|
||||
};
|
||||
|
||||
switch(field.bytes)
|
||||
{
|
||||
case 4:
|
||||
|
|
@ -829,7 +842,7 @@ VirtIO.prototype.init_capabilities = function(capabilities)
|
|||
this.cpu.io.register_write(port, this, undefined, undefined, write);
|
||||
break;
|
||||
case 2:
|
||||
this.cpu.io.register_read(port, this, shim_read8_on_16, read);
|
||||
this.cpu.io.register_read(port, this, shim_read8_on_16, read, shim_read32_on_16);
|
||||
this.cpu.io.register_read(port + 1, this, shim_read8_on_16);
|
||||
this.cpu.io.register_write(port, this, undefined, write);
|
||||
break;
|
||||
|
|
@ -1071,6 +1084,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 +1102,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 +1141,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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
"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";
|
||||
import { dbg_log } from "./log.js";
|
||||
import { VirtIO, VIRTIO_F_VERSION_1 } from "./virtio.js";
|
||||
import * as 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 +32,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;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
"use strict";
|
||||
import { dbg_assert } from "./log.js";
|
||||
import { VirtIO, VIRTIO_F_VERSION_1 } from "./virtio.js";
|
||||
import * as 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
|
||||
|
||||
|
|
@ -20,7 +26,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;
|
||||
|
|
@ -101,11 +107,9 @@ 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) =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
"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";
|
||||
import { VirtIO, VIRTIO_F_VERSION_1 } from "./virtio.js";
|
||||
import { format_mac } from "./ne2k.js";
|
||||
import * as marshall from "../lib/marshall.js";
|
||||
|
||||
// For Types Only
|
||||
import { CPU } from "./cpu.js";
|
||||
import { BusConnector } from "./bus.js";
|
||||
|
||||
const MTU_DEFAULT = 1500;
|
||||
|
||||
const VIRTIO_NET_F_MAC = 5;
|
||||
const VIRTIO_NET_F_CTRL_VQ = 17;
|
||||
|
|
@ -18,8 +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
|
||||
*/
|
||||
function VirtioNet(cpu, bus, preserve_mac_from_state_image)
|
||||
export function VirtioNet(cpu, bus, preserve_mac_from_state_image, mtu = MTU_DEFAULT)
|
||||
{
|
||||
/** @const @type {BusConnector} */
|
||||
this.bus = bus;
|
||||
|
|
@ -171,7 +180,7 @@ function VirtioNet(cpu, bus, preserve_mac_from_state_image)
|
|||
{
|
||||
bytes: 2,
|
||||
name: "mtu",
|
||||
read: () => 1500,
|
||||
read: () => mtu,
|
||||
write: data => {},
|
||||
}
|
||||
])
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
87
tests/api/2g-mem.js
Executable file
87
tests/api/2g-mem.js
Executable file
|
|
@ -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: "<UNUSED>",
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
|
@ -1,23 +1,24 @@
|
|||
#!/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;
|
||||
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/msdos.img" },
|
||||
hda: { url: __dirname + "/../../images/msdos622.img" },
|
||||
network_relay_url: "<UNUSED>",
|
||||
autostart: true,
|
||||
memory_size: 32 * 1024 * 1024,
|
||||
filesystem: {},
|
||||
log_level: 3,
|
||||
log_level: 0,
|
||||
disable_jit: +process.env.DISABLE_JIT,
|
||||
});
|
||||
|
||||
|
|
@ -35,15 +36,16 @@ setTimeout(async () =>
|
|||
await emulator.wait_until_vga_screen_contains("C:\\> ");
|
||||
console.log("Got C:\\>");
|
||||
await pause(1000);
|
||||
emulator.keyboard_send_text("dir A:\n");
|
||||
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("F");
|
||||
emulator.set_fda({ url: __dirname + "/../../images/freedos722.img" });
|
||||
emulator.keyboard_send_text("dir A:\n");
|
||||
await emulator.wait_until_vga_screen_contains("FDOS <DIR>");
|
||||
console.log("Got FDOS");
|
||||
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 <DIR>");
|
||||
console.log("Got BOOT");
|
||||
emulator.destroy();
|
||||
clearTimeout(timeout);
|
||||
//clearInterval(interval);
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
#!/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;
|
||||
const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js");
|
||||
|
||||
process.on("unhandledRejection", exn => { throw exn; });
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue