Compare commits

..

No commits in common. "master" and "0.5" have entirely different histories.
master ... 0.5

137 changed files with 10173 additions and 14348 deletions

View file

@ -22,9 +22,6 @@ 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
@ -68,7 +65,7 @@ jobs:
run: (cd tests/kvm-unit-tests && ./configure && make x86/realmode.flat)
- name: Run kvm-unit-test
run: tests/kvm-unit-tests/run.mjs tests/kvm-unit-tests/x86/realmode.flat
run: tests/kvm-unit-tests/run.js tests/kvm-unit-tests/x86/realmode.flat
- name: Fetch namsmtests cache
uses: actions/cache@v4
@ -95,7 +92,7 @@ jobs:
- name: Download uncached images
if: steps.cache-images.outputs.cache-hit != 'true'
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}
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}
- name: Run api-tests
run: make api-tests
@ -118,9 +115,6 @@ 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:
@ -140,10 +134,6 @@ 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
@ -156,14 +146,20 @@ jobs:
uses: actions/download-artifact@v4
with:
name: v86
path: build
- name: Display structure of downloaded files
run: ls -R
- uses: actions/setup-node@v4
- name: Set package.json version
uses: actions/github-script@v7
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
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");
- name: Release to GitHub
uses: ncipollo/release-action@v1
@ -175,6 +171,5 @@ 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
View file

@ -25,4 +25,3 @@ src/rust/gen/analyzer0f.rs
src/rust/gen/jit.rs
src/rust/gen/jit0f.rs
bios/seabios
bench-results

View file

@ -1,22 +0,0 @@
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.

View file

@ -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=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 \
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 \
state.js ne2k.js sb16.js virtio.js virtio_console.js virtio_net.js virtio_balloon.js \
bus.js log.js cpu.js \
bus.js log.js cpu.js debug.js \
elf.js kernel.js
LIB_FILES=9p.js filesystem.js marshall.js
LIB_FILES=9p.js filesystem.js jor1k.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; export let {V86, CPU} = module.exports;'\
--output_wrapper ';let module = {exports:{}}; %output%; export default module.exports.V86;'\
--js $(CORE_FILES)\
--js $(BROWSER_FILES)\
--js $(LIB_FILES)\
@ -172,7 +172,6 @@ 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
@ -180,7 +179,6 @@ 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;'\
@ -259,7 +257,6 @@ 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
@ -276,11 +273,8 @@ 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 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>' ;\
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>' ;\
sed -i "s@$$SEARCH@$$REPLACE@g" index.html ;\
grep $$COMMIT index.html
@ -299,51 +293,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/v86-debug.wasm build/integration-test-fs/fs.json
tests: build/libv86-debug.js 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/v86-debug.wasm
nasmtests: build/libv86-debug.js 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/v86-debug.wasm
nasmtests-force-jit: build/libv86-debug.js 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/v86-debug.wasm
jitpagingtests: build/libv86-debug.js build/v86-debug.wasm
$(MAKE) -C tests/jit-paging test-jit
./tests/jit-paging/run.js
qemutests: build/v86-debug.wasm
qemutests: build/libv86-debug.js 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.mjs build/v86.wasm
qemutests-release: build/libv86.js 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/v86-debug.wasm
kvm-unit-test: build/libv86-debug.js build/v86-debug.wasm
(cd tests/kvm-unit-tests && ./configure && make x86/realmode.flat)
tests/kvm-unit-tests/run.mjs tests/kvm-unit-tests/x86/realmode.flat
tests/kvm-unit-tests/run.js tests/kvm-unit-tests/x86/realmode.flat
kvm-unit-test-release: build/libv86.mjs build/v86.wasm
kvm-unit-test-release: build/libv86.js build/v86.wasm
(cd tests/kvm-unit-tests && ./configure && make x86/realmode.flat)
TEST_RELEASE_BUILD=1 tests/kvm-unit-tests/run.mjs tests/kvm-unit-tests/x86/realmode.flat
TEST_RELEASE_BUILD=1 tests/kvm-unit-tests/run.js tests/kvm-unit-tests/x86/realmode.flat
expect-tests: build/v86-debug.wasm build/libwabt.cjs
expect-tests: build/libv86-debug.js build/v86-debug.wasm build/libwabt.js
make -C tests/expect/tests
./tests/expect/run.js
devices-test: build/v86-debug.wasm
devices-test: build/libv86-debug.js build/v86-debug.wasm
./tests/devices/virtio_9p.js
./tests/devices/virtio_console.js
./tests/devices/fetch_network.js
@ -358,12 +352,11 @@ rust-test: $(RUST_FILES)
rust-test-intensive:
QUICKCHECK_TESTS=100000000 make rust-test
api-tests: build/v86-debug.wasm
api-tests: build/libv86-debug.js build/v86-debug.wasm
./tests/api/clean-shutdown.js
./tests/api/state.js
./tests/api/reset.js
./tests/api/floppy.js
./tests/api/cdrom-insert-eject.js
#./tests/api/floppy-insert-eject.js # disabled for now, sometimes hangs
./tests/api/serial.js
./tests/api/reboot.js
./tests/api/pic.js
@ -382,19 +375,13 @@ 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.cjs:
build/libwabt.js:
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

View file

@ -25,7 +25,6 @@ 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.
@ -149,7 +148,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:
`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}`
`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}`
Run integration tests: `make tests`
@ -197,7 +196,6 @@ 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

View file

@ -2,11 +2,60 @@
<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,interactive-widget=resizes-content">
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<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.cjs"></script>
<script type="module" src="src/browser/main.js"></script>
<script src="build/libwabt.js"></script>
<link rel="stylesheet" href="v86.css">
@ -87,22 +136,17 @@
<tr>
<td><label for="floppy_image">Floppy disk image</label></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>
<td> <input type="file" id="floppy_image"><br></td>
</tr>
<tr>
<td><label for="hda_image">Hard disk image</label></td>
<td><input type="file" id="hda_image"> or <a id="hda_toggle_empty_disk">create empty hard disk</a><br></td>
<td><input type="file" id="hda_image"><br></td>
</tr>
<tr>
<td>Second hard disk image</td>
<td><input type="file" id="hdb_image"> or <a id="hdb_toggle_empty_disk">create empty hard disk</a><br></td>
<td><input type="file" id="hdb_image"><br></td>
</tr>
<tr>
@ -124,11 +168,6 @@
<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>
@ -147,10 +186,6 @@
</td>
</tr>
<tr>
<td colspan="2"><hr></td>
</tr>
<tr>
<td><label for="relay_url">Networking proxy (leave blank to disable)</label></td>
<td>
@ -158,24 +193,6 @@
</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>
@ -239,8 +256,6 @@
<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">
@ -250,13 +265,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>
@ -330,6 +345,3 @@
</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>

View file

@ -1,34 +1,7 @@
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`:
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`:
```javascript
filesystem: {
@ -38,60 +11,15 @@ filesystem: {
```
Here, `basefs` is a json file created using
[fs2json.py](tools/fs2json.py) and the `baseurl` directory is created using
[copy-to-sha256.py](tools/copy-to-sha256.py).
[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.
If `basefs` and `baseurl` are omitted, an empty 9p filesystem is created. Unless
you configure one of the alternative modes.
The `mount_tag` of the 9p device is `host9p`. In order to mount it in the
guest, use:
### 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)
}
}
```sh
mount -t 9p host9p /mnt/9p/
```
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

View file

@ -62,7 +62,6 @@ Backends `fetch` and `wisp` support a couple of special settings in `config.net_
| **dns_method** | str | DNS method to use, either `static` or `doh`. `static`: use built-in DNS server, `doh`: use [DNS-over-HTTPS](https://en.wikipedia.org/wiki/DNS_over_HTTPS) (DoH). Defaults to `static` for `fetch` and to `doh` for `wisp` backend. |
| **doh_server** | str | Host name or IP address (and optional port number) of the DoH server if `dns_method` is `doh`. The value is expanded to the URL `https://DOH_SERVER/dns-query`. Default: `cloudflare-dns.com`. |
| **cors_proxy** | str | CORS proxy server URL, do not use a proxy if undefined. Default: undefined (`fetch` backend only). |
| **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
@ -129,7 +128,6 @@ 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.
@ -164,8 +162,6 @@ 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

View file

@ -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 -drive file=hdd.img,format=raw
qemu-system-i386 -m 128 -M pc,acpi=off -hda hdd.img
```
- 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,46 +23,16 @@ qemu-system-i386 -m 128 -M pc,acpi=off -drive file=hdd.img,format=raw
4. To change floppy disk, press *Ctrl+Alt+2* to switch to the QEMU Monitor, run `change floppy0 /path/to/new_floppy_image` and press *Ctrl+Alt+1* to switch to VGA.
5. Follow the installation guide on the screen.
6. (optionally) If "Windows protection" errors appears on startup, apply [FIX95CPU](http://lonecrusader.x10host.com/fix95cpu.html) or [patcher9x](https://github.com/JHRobotics/patcher9x#installation).
> [!TIP]
> For transfer files from host to guest, use [genisoimage](https://wiki.debian.org/genisoimage) ([UltraISO](https://www.ultraiso.com/) and [PowerISO](https://www.poweriso.com/) for Windows and Mac) for creating CD-ISO image or [dosfstools](https://github.com/dosfstools/dosfstools) ([WinImage](https://www.winimage.com/download.htm) for Windows) for creating floppy disk images, then mount the created image to QEMU.
## Troubleshooting
### "Windows protection" errors during startup
Apply [FIX95CPU](http://lonecrusader.x10host.com/fix95cpu.html) or [patcher9x](https://github.com/JHRobotics/patcher9x#installation).
### "VFBACKUP could no load VFD.VXD" on startup (Windows 95)
**Workaround #1**:
*Source: [#1185](https://github.com/copy/v86/issues/1185)*
1. Mount the installation CD (or `Disk 3` for the RTM version on floppy disks).
2. Open the "MS-DOS prompt" and run:
For the CD version:
```bat
extract /a /l C:\Windows\System <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".
@ -71,12 +41,10 @@ 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 640x480x4 video mode, to fix this, you can install **Universal VBE9x Video Display Driver** or **VMDisp9x**.
### Universal VBE9x Video Display Driver
The default VGA display driver only supports 640x480x8 video mode, to fix this, install **Universal VBE9x Video Display Driver**.
> [!WARNING]
> After installing, DOS Mode (and other programs and games that require it) may not work properly.
> After installing, DOS Mode (and other programs and games that require it) may not work properly.
> This is a problem in VBE9x, not v86, see [#110](https://github.com/copy/v86/issues/110).
> Also, this driver doesn't support DirectX, DirectDraw and OpenGL.
@ -88,19 +56,6 @@ The default VGA display driver only supports 640x480x4 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).
@ -109,43 +64,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:
| Option | Value |
|:--------------|:------------------|
| Hardware type | Network adapters |
| Manufacturers | Novell |
| Models | NE2000 Compatible |
```
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":
| Option | Value |
|:-------------------|:------------|
| Interrupt Request | 10 |
| Input/Output Range | 0300 - 031F |
```
Interrupt Request: 10
Input/Output Range: 0300 - 031F
```
7. In "Control Panel", open "Network", click on "Add", choose "Protocol" and select the following options:
| Option | Value |
|:------------------|:----------|
| Manufacturers | Microsoft |
| Network Protocols | TCP/IP |
```
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://web.archive.org/web/20210814023225/https://www.claunia.com/qemu/drivers/index.html (unpack `sbw9xup.exe` as a zip archive).
> If you don't have an install CD, use the Sound Blaster 16 driver from https://www.claunia.com/qemu/drivers/index.html.
1. Open "Start" menu, click on "Control Panel" and "Add New Hardware".
2. Press "Next", select "No" and select the following options:
| Option | Value |
|:--------------|:-----------------------------------------|
| Hardware type | Sound, video and game cotrollers |
| Manufacturers | Creative Labs |
| Models | Creative Labs Sound Blaster 16 or AWE-32 |
```
Hardware type: Sound, video and game cotrollers
Manufacturers: Creative Labs
Models: Creative Labs Sound Blaster 16 or AWE-32
```
3. Restart Windows.

View file

@ -17,14 +17,14 @@
3. Run QEMU with the following settings for installation:
```sh
qemu-system-i386 -m 64 -drive file=hdd.img,format=raw -cpu pentium -M pc,acpi=off -cdrom InstallCD.iso
qemu-system-i386 -m 64 -hda hdd.img -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 -drive file=hdd.img,format=raw -cpu pentium -M pc,acpi=off
qemu-system-i386 -m 64 -hda hdd.img -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 -drive file=hdd.img,format=raw -cpu pentium -M pc,acpi=of
## 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 -drive file=hdd.img,format=raw -cdrom InstallCD.iso -cpu pentium -M pc,acpi=off
qemu-system-i386 -m 64 -hda hdd.img -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 -drive file=hdd.img,format=raw -cdrom InstallCD.iso
qemu-system-i386 -m 512 -hda hdd.img -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 -drive file=hdd.img,format=raw -cdrom InstallCD.iso
qemu-system-i386 -m 1024 -hda hdd.img -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.

View file

@ -1,54 +1,5 @@
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",
@ -118,7 +69,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",
@ -133,8 +84,7 @@ export default [
"no-with": "error",
"require-yield": "error",
"use-isnan": "error",
"valid-typeof": "error",
"strict": "error"
"valid-typeof": "error"
}
}
];

View file

@ -26,7 +26,7 @@ window.onload = function()
url: "../bios/vgabios.bin",
},
initial_state: {
url: "../images/arch_state-v2.bin.zst",
url: "../images/arch_state.bin.zst",
},
filesystem: {
baseurl: "../images/arch/",

View file

@ -1,11 +1,16 @@
#!/usr/bin/env node
"use strict";
import path from "node:path";
import fs from "node:fs";
import url from "node:url";
import { V86 } from "../build/libv86.mjs";
var fs = require("fs");
var V86 = require("../build/libv86.js").V86;
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
function readfile(path)
{
return new Uint8Array(fs.readFileSync(path)).buffer;
}
var bios = readfile(__dirname + "/../bios/seabios.bin");
var linux = readfile(__dirname + "/../images/linux4.iso");
process.stdin.setRawMode(true);
process.stdin.resume();
@ -14,14 +19,9 @@ process.stdin.setEncoding("utf8");
console.log("Now booting, please stand by ...");
var emulator = new V86({
bios: { url: __dirname + "/../bios/seabios.bin" },
vga_bios: { url: __dirname + "/../bios/vgabios.bin" },
cdrom: { url: __dirname + "/../images/linux4.iso" },
bios: { buffer: bios },
cdrom: { buffer: linux },
autostart: true,
net_device: {
type: "virtio",
relay_url: "fetch",
},
});
emulator.add_listener("serial0-output-byte", function(byte)

View file

@ -1,13 +1,19 @@
#!/usr/bin/env node
"use strict";
import fs from "node:fs";
import url from "node:url";
import { V86 } from "../build/libv86.mjs";
var fs = require("fs");
var V86 = require("../build/libv86.js").V86;
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
function readfile(path)
{
return new Uint8Array(fs.readFileSync(path)).buffer;
}
console.log("Use F2 to save the state and F3 to restore.");
var bios = readfile(__dirname + "/../bios/seabios.bin");
var linux = readfile(__dirname + "/../images/linux4.iso");
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.setEncoding("utf8");
@ -15,8 +21,8 @@ process.stdin.setEncoding("utf8");
console.log("Now booting, please stand by ...");
var emulator = new V86({
bios: { url: __dirname + "/../bios/seabios.bin" },
cdrom: { url: __dirname + "/../images/linux4.iso" },
bios: { buffer: bios },
cdrom: { buffer: linux },
autostart: true,
});

View file

@ -1,7 +1,5 @@
importScripts("../build/libv86.js");
/* global V86 */
var emulator = new V86({
wasm_path: "../build/v86.wasm",
memory_size: 32 * 1024 * 1024,

View file

@ -1,19 +1,16 @@
#!/usr/bin/env node
"use strict";
const assert = require("assert").strict;
const fs = require("fs");
const path = require("path");
const x86_table = require("./x86_table");
const rust_ast = require("./rust_ast");
const { hex, mkdirpSync, get_switch_value, get_switch_exist, finalize_table_rust } = require("./util");
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import url from "node:url";
import x86_table from "./x86_table.js";
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/");
fs.mkdirSync(OUT_DIR, { recursive: true });
mkdirpSync(OUT_DIR);
const table_arg = get_switch_value("--table");
const gen_all = get_switch_exist("--all");

View file

@ -1,19 +1,16 @@
#!/usr/bin/env node
"use strict";
const assert = require("assert").strict;
const fs = require("fs");
const path = require("path");
const x86_table = require("./x86_table");
const rust_ast = require("./rust_ast");
const { hex, mkdirpSync, get_switch_value, get_switch_exist, finalize_table_rust } = require("./util");
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import url from "node:url";
import x86_table from "./x86_table.js";
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/");
fs.mkdirSync(OUT_DIR, { recursive: true });
mkdirpSync(OUT_DIR);
const table_arg = get_switch_value("--table");
const gen_all = get_switch_exist("--all");
@ -213,9 +210,8 @@ function gen_instruction_body_after_prefix(encodings, size)
}),
default_case: {
varname: "x",
body: [
`dbg_log!("#ud ${encoding.opcode.toString(16).toUpperCase()}/{} at {:x}", x, *instruction_pointer);`,
`if DEBUG { panic!("Bad instruction at {:x}", *instruction_pointer); }`,
"trigger_ud();",
],
}
@ -414,7 +410,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};",
"use crate::cpu::cpu::{task_switch_test, trigger_ud, DEBUG};",
"use crate::cpu::instructions;",
"use crate::cpu::global_pointers::{instruction_pointer, prefixes};",
"use crate::prefix;",
@ -479,6 +475,7 @@ 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;",

View file

@ -1,19 +1,16 @@
#!/usr/bin/env node
"use strict";
const assert = require("assert").strict;
const fs = require("fs");
const path = require("path");
const x86_table = require("./x86_table");
const rust_ast = require("./rust_ast");
const { hex, mkdirpSync, get_switch_value, get_switch_exist, finalize_table_rust } = require("./util");
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import url from "node:url";
import x86_table from "./x86_table.js";
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/");
fs.mkdirSync(OUT_DIR, { recursive: true });
mkdirpSync(OUT_DIR);
const table_arg = get_switch_value("--table");
const gen_all = get_switch_exist("--all");

View file

@ -1,11 +1,13 @@
import assert from "node:assert/strict";
"use strict";
const assert = require("assert").strict;
function indent(lines, how_much)
{
return lines.map(line => " ".repeat(how_much) + line);
}
export function print_syntax_tree(statements)
function print_syntax_tree(statements)
{
let code = [];
@ -32,8 +34,7 @@ export function print_syntax_tree(statements)
if(statement.default_case)
{
const varname = statement.default_case.varname || "_";
cases.push(`${varname} => {`);
cases.push(`_ => {`);
cases.push.apply(cases, indent(print_syntax_tree(statement.default_case.body), 4));
cases.push(`}`);
}
@ -76,3 +77,7 @@ export function print_syntax_tree(statements)
return code;
}
module.exports = {
print_syntax_tree,
};

View file

@ -1,10 +1,14 @@
import fs from "node:fs";
import path from "node:path";
import process from "node:process";
"use strict";
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const process = require("process");
const child_process = require("child_process");
const CYAN_FMT = "\x1b[36m%s\x1b[0m";
export function hex(n, pad)
function hex(n, pad)
{
pad = pad || 0;
let s = n.toString(16).toUpperCase();
@ -12,7 +16,12 @@ export function hex(n, pad)
return s;
}
export function get_switch_value(arg_switch)
function mkdirpSync(dir)
{
fs.mkdirSync(dir, { recursive: true });
}
function get_switch_value(arg_switch)
{
const argv = process.argv;
const switch_i = argv.indexOf(arg_switch);
@ -24,14 +33,22 @@ export function get_switch_value(arg_switch)
return null;
}
export function get_switch_exist(arg_switch)
function get_switch_exist(arg_switch)
{
return process.argv.includes(arg_switch);
}
export function finalize_table_rust(out_dir, name, contents)
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,
};

View file

@ -1,3 +1,7 @@
"use strict";
const { hex } = require("./util");
// http://ref.x86asm.net/coder32.html
const zf = 1 << 6;
@ -871,5 +875,4 @@ encodings.sort((e1, e2) => {
return o1 - o2 || e1.fixed_g - e2.fixed_g;
});
const result = Object.freeze(encodings.map(entry => Object.freeze(entry)));
export default result;
module.exports = Object.freeze(encodings.map(entry => Object.freeze(entry)));

View file

@ -2,13 +2,10 @@
<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,interactive-widget=resizes-content">
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta name="description" content="Run KolibriOS, Linux or Windows 98 in your browser">
<link rel="manifest" href="manifest.json">
<link rel="apple-touch-icon" href="192.png">
<script src="build/v86_all.js?98e7110c2"></script>
<script src="build/v86_all.js"></script>
<link rel="stylesheet" href="v86.css">
<div>
@ -57,121 +54,66 @@
<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 id="reset_filters">Reset filters</a> / <a href="#setup">Skip to custom settings</a>
</div>
<div style="text-align: right; float: right"><a href="#setup">Skip to custom settings</a></div>
<br style="clear: both">
</div>
<hr>
<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>
<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>
<hr>
<h4 id="setup">Setup</h4>
@ -185,22 +127,12 @@
<tr>
<td><label for="floppy_image">Floppy disk image</label></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>
<td> <input type="file" id="floppy_image"><br></td>
</tr>
<tr>
<td><label for="hda_image">Hard disk image</label></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>
<td><input type="file" id="hda_image"><br></td>
</tr>
<!--
@ -221,12 +153,7 @@
<tr>
<td><label for="bios">BIOS</label></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>
<td> <input type="file" id="bios"><br></td>
</tr>
<tr>
@ -247,10 +174,6 @@
</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>
@ -259,23 +182,6 @@
</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>
@ -330,8 +236,6 @@
<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">
@ -341,13 +245,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>
@ -414,11 +318,8 @@
<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 id="version" href="https://github.com/copy/v86/commits/98e7110c2">98e7110c2</a> (Feb 16, 2021 12:02)</code>
<code>Version: <a 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
View file

@ -4,23 +4,7 @@
// Implementation of the 9p filesystem device following the
// 9P2000.L protocol ( https://code.google.com/p/diod/wiki/protocol )
import { LOG_9P } from "./../src/const.js";
import { VirtIO, VIRTIO_F_VERSION_1, VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_RING_INDIRECT_DESC } from "../src/virtio.js";
import { S_IFREG, S_IFDIR, STATUS_UNLINKED } from "./filesystem.js";
import * as marshall from "../lib/marshall.js";
import { 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;
"use strict";
// Feature bit (bit position) for mount tag.
const VIRTIO_9P_F_MOUNT_TAG = 0;
@ -32,13 +16,13 @@ const MAX_REPLYBUFFER_SIZE = 16 * 1024 * 1024;
// TODO
// flush
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 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 */
var P9_SETATTR_MODE = 0x00000001;
var P9_SETATTR_UID = 0x00000002;
@ -65,35 +49,50 @@ var P9_STAT_MODE_SETUID = 0x00080000;
var P9_STAT_MODE_SETGID = 0x00040000;
var P9_STAT_MODE_SETVTX = 0x00010000;
export const P9_LOCK_TYPE_RDLCK = 0;
export const P9_LOCK_TYPE_WRLCK = 1;
export const P9_LOCK_TYPE_UNLCK = 2;
const P9_LOCK_TYPE_RDLCK = 0;
const P9_LOCK_TYPE_WRLCK = 1;
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;
export const P9_LOCK_SUCCESS = 0;
export const P9_LOCK_BLOCKED = 1;
export const P9_LOCK_ERROR = 2;
export const P9_LOCK_GRACE = 3;
const P9_LOCK_SUCCESS = 0;
const P9_LOCK_BLOCKED = 1;
const P9_LOCK_ERROR = 2;
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 init_virtio(cpu, configspace_taglen, configspace_tagname, receive)
{
const virtio = new VirtIO(cpu,
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,
{
name: "virtio-9p",
pci_id: 0x06 << 3,
@ -132,13 +131,12 @@ function init_virtio(cpu, configspace_taglen, configspace_tagname, receive)
" (expected queue_id of 0)");
return;
}
const virtqueue = virtio.queues[0];
while(virtqueue.has_request())
while(this.virtqueue.has_request())
{
const bufchain = virtqueue.pop_request();
receive(bufchain);
const bufchain = this.virtqueue.pop_request();
this.ReceiveRequest(bufchain);
}
virtqueue.notify_me_after(0);
this.virtqueue.notify_me_after(0);
// Don't flush replies here: async replies are not completed yet.
},
],
@ -155,48 +153,21 @@ function init_virtio(cpu, configspace_taglen, configspace_tagname, receive)
{
bytes: 2,
name: "mount tag length",
read: () => configspace_taglen,
read: () => this.configspace_taglen,
write: data => { /* read only */ },
},
].concat(range(VIRTIO_9P_MAX_TAGLEN).map(index =>
].concat(v86util.range(VIRTIO_9P_MAX_TAGLEN).map(index =>
({
bytes: 1,
name: "mount tag name " + index,
// Note: configspace_tagname may have changed after set_state
read: () => configspace_tagname[index] || 0,
read: () => this.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()
@ -238,7 +209,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 to sync dbg_name during 9p renames.
// Set TRACK_FILENAMES = true (in config.js) to sync dbg_name during 9p renames.
Virtio9p.prototype.Createfid = function(inodeid, type, uid, dbg_name) {
return {inodeid, type, uid, dbg_name};
};
@ -251,17 +222,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) {
dbg_log("Error in 9p: payloadsize exceeds maximum length", LOG_9P);
message.Debug("Error in 9p: payloadsize exceeds maximum length");
}
//for(var i=0; i<payload.length; i++)
// this.replybuffer[7+i] = payload[i];
@ -291,7 +262,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var size = header[0];
var id = header[1];
var tag = header[2];
//dbg_log("size:" + size + " id:" + id + " tag:" + tag, LOG_9P);
//message.Debug("size:" + size + " id:" + id + " tag:" + tag);
switch(id)
{
@ -319,18 +290,24 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var req = marshall.Unmarshall(["w", "w"], buffer, state);
var fid = req[0];
var mode = req[1];
dbg_log("[open] fid=" + fid + ", mode=" + mode, LOG_9P);
message.Debug("[open] fid=" + fid + ", mode=" + mode);
var idx = this.fids[fid].inodeid;
var inode = this.fs.GetInode(idx);
dbg_log("file open " + this.fids[fid].dbg_name + " tag:"+tag, LOG_9P);
await this.fs.OpenInode(idx, mode);
message.Debug("file open " + this.fids[fid].dbg_name);
//if (inode.status === STATUS_LOADING) return;
var ret = this.fs.OpenInode(idx, mode);
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);
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)
);
break;
case 70: // link
@ -338,7 +315,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var dfid = req[0];
var fid = req[1];
var name = req[2];
dbg_log("[link] dfid=" + dfid + ", name=" + name, LOG_9P);
message.Debug("[link] dfid=" + dfid + ", name=" + name);
var ret = this.fs.Link(this.fids[dfid].inodeid, this.fids[fid].inodeid, name);
@ -366,7 +343,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var name = req[1];
var symgt = req[2];
var gid = req[3];
dbg_log("[symlink] fid=" + fid + ", name=" + name + ", symgt=" + symgt + ", gid=" + gid, LOG_9P);
message.Debug("[symlink] fid=" + fid + ", name=" + name + ", symgt=" + symgt + ", gid=" + gid);
var idx = this.fs.CreateSymlink(name, this.fids[fid].inodeid, symgt);
var inode = this.fs.GetInode(idx);
inode.uid = this.fids[fid].uid;
@ -384,7 +361,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var major = req[3];
var minor = req[4];
var gid = req[5];
dbg_log("[mknod] fid=" + fid + ", name=" + name + ", major=" + major + ", minor=" + minor+ "", LOG_9P);
message.Debug("[mknod] fid=" + fid + ", name=" + name + ", major=" + major + ", minor=" + minor+ "");
var idx = this.fs.CreateNode(name, this.fids[fid].inodeid, major, minor);
var inode = this.fs.GetInode(idx);
inode.mode = mode;
@ -401,7 +378,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);
dbg_log("[readlink] fid=" + fid + " name=" + this.fids[fid].dbg_name + " target=" + inode.symlink, LOG_9P);
message.Debug("[readlink] fid=" + fid + " name=" + this.fids[fid].dbg_name + " target=" + inode.symlink);
size = marshall.Marshall(["s"], [inode.symlink], this.replybuffer, 7);
this.BuildReply(id, tag, size);
this.SendReply(bufchain);
@ -414,7 +391,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var name = req[1];
var mode = req[2];
var gid = req[3];
dbg_log("[mkdir] fid=" + fid + ", name=" + name + ", mode=" + mode + ", gid=" + gid, LOG_9P);
message.Debug("[mkdir] fid=" + fid + ", name=" + name + ", mode=" + mode + ", gid=" + gid);
var idx = this.fs.CreateDirectory(name, this.fids[fid].inodeid);
var inode = this.fs.GetInode(idx);
inode.mode = mode | S_IFDIR;
@ -433,7 +410,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]);
dbg_log("[create] fid=" + fid + ", name=" + name + ", flags=" + flags + ", mode=" + mode + ", gid=" + gid, LOG_9P);
message.Debug("[create] fid=" + fid + ", name=" + name + ", flags=" + flags + ", mode=" + mode + ", gid=" + gid);
var idx = this.fs.CreateFile(name, this.fids[fid].inodeid);
this.fids[fid].inodeid = idx;
this.fids[fid].type = FID_INODE;
@ -453,7 +430,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]);
dbg_log("[lock] fid=" + fid +
message.Debug("[lock] fid=" + fid +
", type=" + P9_LOCK_TYPES[lock_request.type] + ", start=" + lock_request.start +
", length=" + lock_request.length + ", proc_id=" + lock_request.proc_id);
@ -469,7 +446,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]);
dbg_log("[getlock] fid=" + fid +
message.Debug("[getlock] fid=" + fid +
", type=" + P9_LOCK_TYPES[lock_request.type] + ", start=" + lock_request.start +
", length=" + lock_request.length + ", proc_id=" + lock_request.proc_id);
@ -495,10 +472,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);
dbg_log("[getattr]: fid=" + fid + " name=" + this.fids[fid].dbg_name + " request mask=" + req[1], LOG_9P);
message.Debug("[getattr]: fid=" + fid + " name=" + this.fids[fid].dbg_name + " request mask=" + req[1]);
if(!inode || inode.status === STATUS_UNLINKED)
{
dbg_log("getattr: unlinked", LOG_9P);
message.Debug("getattr: unlinked");
this.SendError(tag, "No such file or directory", ENOENT);
this.SendReply(bufchain);
break;
@ -551,7 +528,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
], buffer, state);
var fid = req[0];
var inode = this.fs.GetInode(this.fids[fid].inodeid);
dbg_log("[setattr]: fid=" + fid + " request mask=" + req[1] + " name=" + this.fids[fid].dbg_name, LOG_9P);
message.Debug("[setattr]: fid=" + fid + " request mask=" + req[1] + " name=" + this.fids[fid].dbg_name);
if(req[1] & P9_SETATTR_MODE) {
// XXX: check mode (S_IFREG or S_IFDIR or similar should be set)
inode.mode = req[2];
@ -598,11 +575,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) 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(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(!inode || inode.status === STATUS_UNLINKED)
{
dbg_log("read/treaddir: unlinked", LOG_9P);
message.Debug("read/treaddir: unlinked");
this.SendError(tag, "No such file or directory", ENOENT);
this.SendReply(bufchain);
break;
@ -615,7 +592,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
this.BuildReply(id, tag, 4 + count);
this.SendReply(bufchain);
} else {
await this.fs.OpenInode(this.fids[fid].inodeid, undefined);
this.fs.OpenInode(this.fids[fid].inodeid, undefined);
const inodeid = this.fids[fid].inodeid;
count = Math.min(count, this.replybuffer.length - (7 + 4));
@ -656,7 +633,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
const filename = this.fids[fid].dbg_name;
dbg_log("[write]: fid=" + fid + " (" + filename + ") offset=" + offset + " count=" + count + " fidtype=" + this.fids[fid].type, LOG_9P);
message.Debug("[write]: fid=" + fid + " (" + filename + ") offset=" + offset + " count=" + count + " fidtype=" + this.fids[fid].type);
if(this.fids[fid].type === FID_XATTR)
{
// XXX: xattr not supported yet. Ignore write.
@ -683,7 +660,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var oldname = req[1];
var newdirfid = req[2];
var newname = req[3];
dbg_log("[renameat]: oldname=" + oldname + " newname=" + newname, LOG_9P);
message.Debug("[renameat]: oldname=" + oldname + " newname=" + newname);
var ret = await this.fs.Rename(this.fids[olddirfid].inodeid, oldname, this.fids[newdirfid].inodeid, newname);
if(ret < 0) {
let error_message = "";
@ -713,7 +690,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var dirfd = req[0];
var name = req[1];
var flags = req[2];
dbg_log("[unlink]: dirfd=" + dirfd + " name=" + name + " flags=" + flags, LOG_9P);
message.Debug("[unlink]: dirfd=" + dirfd + " name=" + name + " flags=" + flags);
var fid = this.fs.Search(this.fids[dirfd].inodeid, name);
if(fid === -1) {
this.SendError(tag, "No such file or directory", ENOENT);
@ -740,7 +717,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
case 100: // version
var version = marshall.Unmarshall(["w", "s"], buffer, state);
dbg_log("[version]: msize=" + version[0] + " version=" + version[1], LOG_9P);
message.Debug("[version]: msize=" + version[0] + " version=" + version[1]);
if(this.msize !== version[0])
{
this.msize = version[0];
@ -756,7 +733,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];
dbg_log("[attach]: fid=" + fid + " afid=" + h(req[1]) + " uname=" + req[2] + " aname=" + req[3], LOG_9P);
message.Debug("[attach]: fid=" + fid + " afid=" + hex8(req[1]) + " uname=" + req[2] + " aname=" + req[3]);
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);
@ -768,7 +745,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
case 108: // tflush
var req = marshall.Unmarshall(["h"], buffer, state);
var oldtag = req[0];
dbg_log("[flush] " + tag, LOG_9P);
message.Debug("[flush] " + tag);
//marshall.Marshall(["Q"], [inode.qid], this.replybuffer, 7);
this.BuildReply(id, tag, 0);
this.SendReply(bufchain);
@ -780,7 +757,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var fid = req[0];
var nwfid = req[1];
var nwname = req[2];
dbg_log("[walk]: fid=" + req[0] + " nwfid=" + req[1] + " nwname=" + nwname, LOG_9P);
message.Debug("[walk]: fid=" + req[0] + " nwfid=" + req[1] + " nwname=" + nwname);
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;
@ -798,17 +775,17 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var offset = 7+2;
var nwidx = 0;
//console.log(idx, this.fs.GetInode(idx));
dbg_log("walk in dir " + this.fids[fid].dbg_name + " to: " + walk.toString(), LOG_9P);
message.Debug("walk in dir " + this.fids[fid].dbg_name + " to: " + walk.toString());
for(var i=0; i<nwname; i++) {
idx = this.fs.Search(idx, walk[i]);
if(idx === -1) {
dbg_log("Could not find: " + walk[i], LOG_9P);
message.Debug("Could not find: " + walk[i]);
break;
}
offset += marshall.Marshall(["Q"], [this.fs.GetInode(idx).qid], this.replybuffer, offset);
nwidx++;
//dbg_log(this.fids[nwfid].inodeid, LOG_9P);
//message.Debug(this.fids[nwfid].inodeid);
//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]);
@ -820,7 +797,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
case 120: // clunk
var req = marshall.Unmarshall(["w"], buffer, state);
dbg_log("[clunk]: fid=" + req[0], LOG_9P);
message.Debug("[clunk]: fid=" + req[0]);
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;
@ -836,7 +813,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var name = req[1];
var attr_size = req[2];
var flags = req[3];
dbg_log("[txattrcreate]: fid=" + fid + " name=" + name + " attr_size=" + attr_size + " flags=" + flags, LOG_9P);
message.Debug("[txattrcreate]: fid=" + fid + " name=" + name + " attr_size=" + attr_size + " flags=" + flags);
// XXX: xattr not supported yet. E.g. checks corresponding to the flags needed.
this.fids[fid].type = FID_XATTR;
@ -852,7 +829,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var fid = req[0];
var newfid = req[1];
var name = req[2];
dbg_log("[xattrwalk]: fid=" + req[0] + " newfid=" + req[1] + " name=" + req[2], LOG_9P);
message.Debug("[xattrwalk]: fid=" + req[0] + " newfid=" + req[1] + " name=" + req[2]);
// Workaround for Linux restarts writes until full blocksize
this.SendError(tag, "Setxattr not supported", EOPNOTSUPP);
@ -873,8 +850,8 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
break;
default:
dbg_log("Error in Virtio9p: Unknown id " + id + " received", LOG_9P);
dbg_assert(false);
message.Debug("Error in Virtio9p: Unknown id " + id + " received");
message.Abort();
//this.SendError(tag, "Operation i not supported", EOPNOTSUPP);
//this.SendReply(bufchain);
break;
@ -883,288 +860,3 @@ 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;
}
};

View file

@ -3,25 +3,16 @@
// -------------------------------------------------
// Implementation of a unix filesystem in memory.
"use strict";
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_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;
//var S_IFIFO 0010000
//var S_ISUID 0004000
@ -33,13 +24,12 @@ 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
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;
var STATUS_INVALID = -0x1;
var STATUS_OK = 0x0;
var STATUS_ON_STORAGE = 0x2;
var STATUS_UNLINKED = 0x4;
var STATUS_FORWARDING = 0x5;
const texten = new TextEncoder();
/** @const */ var JSONFS_VERSION = 3;
@ -59,9 +49,10 @@ const texten = new TextEncoder();
* @param {!FileStorageInterface} storage
* @param {{ last_qidnumber: number }=} qidcounter Another fs's qidcounter to synchronise with.
*/
export function FS(storage, qidcounter) {
function FS(storage, qidcounter) {
/** @type {Array.<!Inode>} */
this.inodes = [];
this.events = [];
this.storage = storage;
@ -138,13 +129,47 @@ 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 recreate the filesystem JSON.";
throw "The filesystem JSON format has changed. " +
"Please update your fs2json (https://github.com/copy/fs2json) and recreate the filesystem JSON.";
}
var fsroot = fs["fsroot"];
@ -198,7 +223,7 @@ FS.prototype.LoadRecursive = function(data, parentid)
}
else
{
dbg_log("Unexpected ifmt: " + h(ifmt) + " (" + name + ")", LOG_9P);
dbg_log("Unexpected ifmt: " + h(ifmt) + " (" + name + ")");
}
};
@ -314,7 +339,9 @@ FS.prototype.PushInode = function(inode, parentid, name) {
}
}
dbg_assert(false, "Error in Filesystem: Pushed inode with name = "+ name + " has no parent");
message.Debug("Error in Filesystem: Pushed inode with name = "+ name + " has no parent");
message.Abort();
};
/** @constructor */
@ -661,11 +688,11 @@ FS.prototype.CreateBinaryFile = async function(filename, parentid, buffer) {
};
FS.prototype.OpenInode = async function(id, mode) {
FS.prototype.OpenInode = function(id, mode) {
var inode = this.inodes[id];
if(this.is_forwarder(inode))
{
return await this.follow_fs(inode).OpenInode(inode.foreign_id, mode);
return this.follow_fs(inode).OpenInode(inode.foreign_id, mode);
}
if((inode.mode&S_IFMT) === S_IFDIR) {
this.FillDirectory(id);
@ -679,11 +706,12 @@ FS.prototype.OpenInode = async function(id, mode) {
case S_IFCHR: type = "Character Device"; break;
}
*/
//dbg_log("open:" + this.GetFullPath(id) + " type: " + inode.mode + " status:" + inode.status, LOG_9P);
//message.Debug("open:" + this.GetFullPath(id) + " type: " + inode.mode + " status:" + inode.status);
return true;
};
FS.prototype.CloseInode = async function(id) {
//dbg_log("close: " + this.GetFullPath(id), LOG_9P);
//message.Debug("close: " + this.GetFullPath(id));
var inode = this.inodes[id];
if(this.is_forwarder(inode))
{
@ -694,7 +722,7 @@ FS.prototype.CloseInode = async function(id) {
this.storage.uncache(inode.sha256sum);
}
if(inode.status === STATUS_UNLINKED) {
//dbg_log("Filesystem: Delete unlinked file", LOG_9P);
//message.Debug("Filesystem: Delete unlinked file");
inode.status = STATUS_INVALID;
await this.DeleteData(id);
}
@ -704,7 +732,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) {
// dbg_log("Rename " + oldname + " to " + newname, LOG_9P);
// message.Debug("Rename " + oldname + " to " + newname);
if((olddirid === newdirid) && (oldname === newname)) {
return 0;
}
@ -1008,7 +1036,7 @@ FS.prototype.Unlink = function(parentid, name) {
const idx = this.Search(parentid, name);
const inode = this.inodes[idx];
const parent_inode = this.inodes[parentid];
//dbg_log("Unlink " + inode.name, LOG_9P);
//message.Debug("Unlink " + inode.name);
// forward if necessary
if(this.is_forwarder(parent_inode))
@ -1068,7 +1096,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, inode.size);
return await this.storage.read(inode.sha256sum, 0, inode.size);
}
else
{
@ -1095,7 +1123,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, inode.size);
return await this.storage.read(inode.sha256sum, offset, count);
}
else
{
@ -1141,7 +1169,7 @@ FS.prototype.ChangeSize = async function(idx, newsize)
{
var inode = this.GetInode(idx);
var temp = await this.get_data(idx, 0, inode.size);
//dbg_log("change size to: " + newsize, LOG_9P);
//message.Debug("change size to: " + newsize);
if(newsize === inode.size) return;
var data = new Uint8Array(newsize);
inode.size = newsize;
@ -1268,24 +1296,24 @@ FS.prototype.Check = function() {
var inode = this.GetInode(i);
if(inode.nlinks < 0) {
dbg_log("Error in filesystem: negative nlinks=" + inode.nlinks + " at id =" + i, LOG_9P);
message.Debug("Error in filesystem: negative nlinks=" + inode.nlinks + " at id =" + i);
}
if(this.IsDirectory(i))
{
const inode = this.GetInode(i);
if(this.IsDirectory(i) && this.GetParent(i) < 0) {
dbg_log("Error in filesystem: negative parent id " + i, LOG_9P);
message.Debug("Error in filesystem: negative parent id " + i);
}
for(const [name, id] of inode.direntries)
{
if(name.length === 0) {
dbg_log("Error in filesystem: inode with no name and id " + id, LOG_9P);
message.Debug("Error in filesystem: inode with no name and id " + id);
}
for(const c of name) {
if(c < 32) {
dbg_log("Error in filesystem: Unallowed char in filename", LOG_9P);
message.Debug("Error in filesystem: Unallowed char in filename");
}
}
}

107
lib/jor1k.js Normal file
View file

@ -0,0 +1,107 @@
"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);
}
});
};
}

View file

@ -3,13 +3,15 @@
// -------------------------------------------------
// helper functions for virtio and 9p.
import { dbg_log } from "./../src/log.js";
"use strict";
var marshall = {};
const textde = new TextDecoder();
const texten = new TextEncoder();
// Inserts data from an array to a byte aligned struct in memory
export function Marshall(typelist, input, struct, offset) {
marshall.Marshall = function(typelist, input, struct, offset) {
var item;
var size = 0;
for(var i=0; i < typelist.length; i++) {
@ -59,21 +61,21 @@ export function Marshall(typelist, input, struct, offset) {
struct[lengthoffset+1] = (length >> 8) & 0xFF;
break;
case "Q":
Marshall(["b", "w", "d"], [item.type, item.version, item.path], struct, offset);
marshall.Marshall(["b", "w", "d"], [item.type, item.version, item.path], struct, offset);
offset += 13;
size += 13;
break;
default:
dbg_log("Marshall: Unknown type=" + typelist[i]);
message.Debug("Marshall: Unknown type=" + typelist[i]);
break;
}
}
return size;
}
};
// Extracts data from a byte aligned struct in memory to an array
export function Unmarshall(typelist, struct, state) {
marshall.Unmarshall = function(typelist, struct, state) {
let offset = state.offset;
var output = [];
for(var i=0; i < typelist.length; i++) {
@ -110,7 +112,7 @@ export function Unmarshall(typelist, struct, state) {
break;
case "Q":
state.offset = offset;
const qid = Unmarshall(["b", "w", "d"], struct, state);
const qid = marshall.Unmarshall(["b", "w", "d"], struct, state);
offset = state.offset;
output.push({
type: qid[0],
@ -119,10 +121,10 @@ export function Unmarshall(typelist, struct, state) {
});
break;
default:
dbg_log("Error in Unmarshall: Unknown type=" + typelist[i]);
message.Debug("Error in Unmarshall: Unknown type=" + typelist[i]);
break;
}
}
state.offset = offset;
return output;
}
};

View file

@ -1,13 +0,0 @@
{
"name": "v86",
"short_name": "v86",
"start_url": "/v86/",
"display": "standalone",
"icons": [
{
"src": "192.png",
"sizes": "192x192",
"type": "image/png"
}
]
}

97
nodejs-loader.mjs Normal file
View file

@ -0,0 +1,97 @@
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;

View file

@ -7,14 +7,11 @@
"files": [
"Readme.md",
"LICENSE",
"v86.d.ts",
"build/libv86*.js",
"build/libv86*.js.map",
"build/*.mjs",
"build/v86*.wasm"
],
"main": "build/libv86.mjs",
"types": "v86.d.ts",
"repository": "github:copy/v86",
"type": "module"
"main": "build/libv86.js",
"repository": "github:copy/v86"
}

View file

@ -1,20 +1,15 @@
"use strict";
// http://www.uefi.org/sites/default/files/resources/ACPI_6_1.pdf
import { v86 } from "./main.js";
import { LOG_ACPI } from "../src/const.js";
import { h } from "./lib.js";
import { dbg_log, dbg_assert } from "./log.js";
// For Types Only
import { CPU } from "./cpu.js";
const PMTIMER_FREQ_SECONDS = 3579545;
/** @const */
var PMTIMER_FREQ_SECONDS = 3579545;
/**
* @constructor
* @param {CPU} cpu
*/
export function ACPI(cpu)
function ACPI(cpu)
{
/** @type {CPU} */
this.cpu = cpu;
@ -69,11 +64,7 @@ export function ACPI(cpu)
});
// ACPI status
io.register_read(0xB004, this, function()
{
dbg_log("ACPI status read8", LOG_ACPI);
return this.status & 0xFF;
}, function()
io.register_read(0xB004, this, undefined, function()
{
dbg_log("ACPI status read", LOG_ACPI);
return this.status;

648
src/apic.js Normal file
View file

@ -0,0 +1,648 @@
"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;
};

View file

@ -1,11 +1,9 @@
import { dbg_assert } from "../log.js";
import { get_charmap } from "../lib.js";
"use strict";
/**
* @constructor
* @param {Object=} options
*/
export function DummyScreenAdapter(options)
function DummyScreenAdapter()
{
var
graphic_image_data,
@ -32,10 +30,7 @@ export function DummyScreenAdapter(options)
text_mode_width = 0,
// number of rows
text_mode_height = 0,
// 8-bit-text to Unicode character map
charmap = get_charmap(options?.encoding);
text_mode_height = 0;
this.put_char = function(row, col, chr, blinking, bg_color, fg_color)
{
@ -117,11 +112,10 @@ export function DummyScreenAdapter(options)
return screen;
};
this.get_text_row = function(y)
this.get_text_row = function(i)
{
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("");
const offset = i * text_mode_width;
return String.fromCharCode.apply(String, text_mode_data.subarray(offset, offset + text_mode_width));
};
this.set_size_text(80, 25);

View file

@ -1,6 +1,4 @@
import { LOG_FETCH } from "../const.js";
import { h } from "../lib.js";
import { dbg_assert, dbg_log } from "../log.js";
"use strict";
// https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
const ETHERTYPE_IPV4 = 0x0800;
@ -25,17 +23,17 @@ const V86_ASCII = [118, 56, 54];
*
* State TIME_WAIT is not needed, we can skip it and transition directly to CLOSED instead.
*/
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_CLOSED = "closed";
const TCP_STATE_SYN_RECEIVED = "syn-received";
const TCP_STATE_SYN_SENT = "syn-sent";
const TCP_STATE_SYN_PROBE = "syn-probe";
//const TCP_STATE_LISTEN = "listen";
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_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";
//const TCP_STATE_TIME_WAIT = "time-wait";
// source: RFC6335, 6. Port Number Ranges
@ -45,14 +43,18 @@ const TCP_DYNAMIC_PORT_RANGE = TCP_DYNAMIC_PORT_END - TCP_DYNAMIC_PORT_START;
const ETH_HEADER_SIZE = 14;
const ETH_PAYLOAD_OFFSET = ETH_HEADER_SIZE;
const MTU_DEFAULT = 1500;
const ETH_PAYLOAD_SIZE = 1500;
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";
@ -90,7 +92,6 @@ 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;
}
@ -157,19 +158,15 @@ class GrowableRingbuffer
}
}
export function create_eth_encoder_buf(mtu = MTU_DEFAULT)
function create_eth_encoder_buf()
{
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, mtu),
eth_payload_view: new DataView(buffer, offset + ETH_PAYLOAD_OFFSET, ETH_PAYLOAD_SIZE),
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()
@ -190,17 +187,6 @@ 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.
*
@ -233,8 +219,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;
}
@ -254,30 +240,13 @@ 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 && !packet.tcp.ack) {
if(packet.tcp.syn) {
if(adapter.tcp_conn[tuple]) {
dbg_log("SYN to already opened port", LOG_FETCH);
delete adapter.tcp_conn[tuple];
}
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.on_tcp_connection(packet, tuple)) {
return;
}
if(adapter.tcp_conn[tuple]) return;
}
if(!adapter.tcp_conn[tuple]) {
@ -476,7 +445,7 @@ function handle_fake_dhcp(packet, adapter) {
adapter.receive(make_packet(adapter.eth_encoder_buf, reply));
}
export function handle_fake_networking(data, adapter) {
function handle_fake_networking(data, adapter) {
let packet = {};
parse_eth(data, packet);
@ -977,31 +946,19 @@ function write_tcp(spec, out) {
if(tcp.ece) flags |= 0x40;
if(tcp.cwr) flags |= 0x80;
let doff = TCP_HEADER_SIZE;
if(tcp.options) {
if(tcp.options.mss) {
view.setUint8(doff, 0x02); //mss option type
view.setUint8(doff + 1, 0x04); //option length
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
}
const doff = TCP_HEADER_SIZE >> 2; // header length in 32-bit words
view.setUint16(0, tcp.sport);
view.setUint16(2, tcp.dport);
view.setUint32(4, tcp.seq);
view.setUint32(8, tcp.ackn);
view.setUint8(12, (total_length >> 2) << 4); // header length in 32-bit words
view.setUint8(12, doff << 4);
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);
}
@ -1017,7 +974,7 @@ function write_tcp(spec, out) {
return total_length;
}
export function fake_tcp_connect(dport, adapter)
function fake_tcp_connect(dport, adapter)
{
const vm_ip_str = adapter.vm_ip.join(".");
const router_ip_str = adapter.router_ip.join(".");
@ -1031,7 +988,7 @@ export function fake_tcp_connect(dport, adapter)
throw new Error("pool of dynamic TCP port numbers exhausted, connection aborted");
}
let conn = new TCPConnection(adapter);
let conn = new TCPConnection();
conn.tuple = tuple;
conn.hsrc = adapter.router_mac;
@ -1040,12 +997,13 @@ export function fake_tcp_connect(dport, adapter)
conn.hdest = adapter.vm_mac;
conn.dport = dport;
conn.pdest = adapter.vm_ip;
conn.net = adapter;
adapter.tcp_conn[tuple] = conn;
conn.connect();
return conn;
}
export function fake_tcp_probe(dport, adapter) {
function fake_tcp_probe(dport, adapter) {
return new Promise((res, rej) => {
let handle = fake_tcp_connect(dport, adapter);
handle.state = TCP_STATE_SYN_PROBE;
@ -1056,14 +1014,10 @@ export function fake_tcp_probe(dport, adapter) {
/**
* @constructor
*/
export function TCPConnection(adapter)
function TCPConnection()
{
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 = adapter; // The adapter is stored here
this.net = null; // 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;
@ -1126,9 +1080,7 @@ TCPConnection.prototype.connect = function() {
this.ack = 1;
this.start_seq = 0;
this.winsize = 64240;
if(this.state !== TCP_STATE_SYN_PROBE) {
this.state = TCP_STATE_SYN_SENT;
}
this.state = TCP_STATE_SYN_SENT;
let reply = this.ipv4_reply();
reply.ipv4.id = 2345;
@ -1144,12 +1096,16 @@ TCPConnection.prototype.connect = function() {
};
TCPConnection.prototype.accept = function(packet=undefined) {
packet = packet || this.last;
this.net.tcp_conn[this.tuple] = this;
TCPConnection.prototype.accept = function(packet) {
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();
@ -1160,10 +1116,7 @@ TCPConnection.prototype.accept = function(packet=undefined) {
ackn: this.ack,
winsize: packet.tcp.winsize,
syn: true,
ack: true,
options: {
mss: (this.mtu - TCP_HEADER_SIZE - IPV4_HEADER_SIZE)
}
ack: true
};
// dbg_log(`TCP[${this.tuple}]: accept(): sending SYN+ACK in state "${this.state}", next "${TCP_STATE_ESTABLISHED}"`, LOG_FETCH);
this.state = TCP_STATE_ESTABLISHED;
@ -1171,7 +1124,6 @@ TCPConnection.prototype.accept = function(packet=undefined) {
};
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});

View file

@ -1,25 +1,13 @@
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";
"use strict";
/**
* @constructor
*
* @param {BusConnector} bus
* @param {*=} config
* @export
*/
export function FetchNetworkAdapter(bus, config)
function FetchNetworkAdapter(bus, config)
{
config = config || {};
this.bus = bus;
@ -32,8 +20,7 @@ export function FetchNetworkAdapter(bus, config)
this.dns_method = config.dns_method || "static";
this.doh_server = config.doh_server;
this.tcp_conn = {};
this.mtu = config.mtu;
this.eth_encoder_buf = create_eth_encoder_buf(this.mtu);
this.eth_encoder_buf = create_eth_encoder_buf();
this.fetch = (...args) => fetch(...args);
// Ex: 'https://corsproxy.io/?'
@ -46,18 +33,27 @@ export function FetchNetworkAdapter(bus, config)
{
this.send(data);
}, this);
this.bus.register("tcp-connection", (conn) => {
if(conn.sport === 80) {
conn.on("data", on_data_http);
conn.accept();
}
}, this);
}
FetchNetworkAdapter.prototype.destroy = function()
{
};
FetchNetworkAdapter.prototype.on_tcp_connection = function(packet, tuple)
{
if(packet.tcp.dport === 80) {
let conn = new TCPConnection();
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);
@ -101,27 +97,13 @@ async function on_data_http(data)
const header = this.net.parse_http_header(headers[i]);
if(!header) {
console.warn('The request contains an invalid header: "%s"', headers[i]);
this.net.respond_text_and_close(this, 400, "Bad Request", `Invalid header in request: ${headers[i]}`);
this.write(new TextEncoder().encode("HTTP/1.1 400 Bad Request\r\nContent-Length: 0"));
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 = {
@ -135,17 +117,19 @@ 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;
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));
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"));
response_started = true;
if(resp.body && resp.body.getReader) {
@ -168,13 +152,18 @@ 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) {
this.net.respond_text_and_close(this, 502, "Fetch Error", `Fetch ${fetch_url} failed:\n\n${e.stack || e.message}`);
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.close();
});
@ -194,41 +183,19 @@ 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: new Headers({ "Content-Type": "text/plain" }),
headers: headers,
},
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(/^([^:]*):(.*)$/);

View file

@ -1,18 +1,16 @@
import { dbg_assert } from "../log.js";
import { load_file } from "../lib.js";
"use strict";
/** @interface */
export function FileStorageInterface() {}
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, file_size) {};
FileStorageInterface.prototype.read = function(sha256sum, offset, count) {};
/**
* Add a read-only file to the filestorage.
@ -33,7 +31,7 @@ FileStorageInterface.prototype.uncache = function(sha256sum) {};
* @constructor
* @implements {FileStorageInterface}
*/
export function MemoryFileStorage()
function MemoryFileStorage()
{
/**
* From sha256sum to file data.
@ -84,9 +82,8 @@ MemoryFileStorage.prototype.uncache = function(sha256sum)
* @implements {FileStorageInterface}
* @param {FileStorageInterface} file_storage
* @param {string} baseurl
* @param {function(number,Uint8Array):ArrayBuffer} zstd_decompress
*/
export function ServerFileStorageWrapper(file_storage, baseurl, zstd_decompress)
function ServerFileStorageWrapper(file_storage, baseurl)
{
dbg_assert(baseurl, "ServerMemoryFileStorage: baseurl should not be empty");
@ -97,27 +94,19 @@ export function ServerFileStorageWrapper(file_storage, baseurl, zstd_decompress)
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, file_size)
ServerFileStorageWrapper.prototype.load_from_server = function(sha256sum)
{
return new Promise((resolve, reject) =>
{
load_file(this.baseurl + sha256sum, { done: async buffer =>
v86util.load_file(this.baseurl + sha256sum, { done: async buffer =>
{
let data = new Uint8Array(buffer);
if(sha256sum.endsWith(".zst"))
{
data = new Uint8Array(
this.zstd_decompress(file_size, data)
);
}
const data = new Uint8Array(buffer);
await this.cache(sha256sum, data);
resolve(data);
}});
@ -128,15 +117,14 @@ ServerFileStorageWrapper.prototype.load_from_server = function(sha256sum, file_s
* @param {string} sha256sum
* @param {number} offset
* @param {number} count
* @param {number} file_size
* @return {!Promise<Uint8Array>}
*/
ServerFileStorageWrapper.prototype.read = async function(sha256sum, offset, count, file_size)
ServerFileStorageWrapper.prototype.read = async function(sha256sum, offset, count)
{
const data = await this.storage.read(sha256sum, offset, count, file_size);
const data = await this.storage.read(sha256sum, offset, count);
if(!data)
{
const full_file = await this.load_from_server(sha256sum, file_size);
const full_file = await this.load_from_server(sha256sum);
return full_file.subarray(offset, offset + count);
}
return data;

View file

@ -1,5 +1,4 @@
// For Types Only
import { BusConnector } from "../bus.js";
"use strict";
/**
* Network adapter "inbrowser" which connects the emulated NIC
@ -17,7 +16,7 @@ import { BusConnector } from "../bus.js";
* @param {BusConnector} bus
* @param {*=} config
*/
export function InBrowserNetworkAdapter(bus, config)
function InBrowserNetworkAdapter(bus, config)
{
const id = config.id || 0;

View file

@ -1,9 +1,12 @@
// For Types Only
import { BusConnector } from "../bus.js";
"use strict";
const SHIFT_SCAN_CODE = 0x2A;
const SCAN_CODE_RELEASE = 0x80;
/** @const */
var SHIFT_SCAN_CODE = 0x2A;
/** @const */
var SCAN_CODE_RELEASE = 0x80;
/** @const */
const PLATFOM_WINDOWS = typeof window !== "undefined" && window.navigator.platform.toString().toLowerCase().search("win") >= 0;
/**
@ -11,7 +14,7 @@ const PLATFOM_WINDOWS = typeof window !== "undefined" && window.navigator.platfo
*
* @param {BusConnector} bus
*/
export function KeyboardAdapter(bus)
function KeyboardAdapter(bus)
{
var
/**
@ -45,9 +48,12 @@ export function KeyboardAdapter(bus)
*/
this.emu_enabled = true;
// Format:
// Javascript event.keyCode -> make code
const charmap = new Uint16Array([
/**
* Format:
* Javascript event.keyCode -> make code
* @const
*/
var charmap = new Uint16Array([
0, 0, 0, 0, 0, 0, 0, 0,
// 0x08: backspace, tab, enter
0x0E, 0x0F, 0, 0, 0, 0x1C, 0, 0,
@ -122,9 +128,12 @@ export function KeyboardAdapter(bus)
]);
// 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};
/**
* 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};
// From:
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code#Code_values_on_Linux_%28X11%29_%28When_scancode_is_available%29
@ -237,9 +246,7 @@ export function KeyboardAdapter(bus)
"Insert": 0xe052,
"Delete": 0xe053,
"MetaLeft": 0xe05b,
"OSLeft": 0xe05b,
"MetaRight": 0xe05c,
"OSRight": 0xe05c,
"ContextMenu": 0xe05d,
};
@ -253,7 +260,6 @@ export 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);
}
};
@ -268,7 +274,6 @@ export 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();
@ -381,37 +386,6 @@ export 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
@ -428,12 +402,6 @@ export 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)

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,14 @@
import { dbg_log } from "../log.js";
// For Types Only
import { BusConnector } from "../bus.js";
"use strict";
/**
* @constructor
*
* @param {BusConnector} bus
*/
export function MouseAdapter(bus, screen_container)
function MouseAdapter(bus, screen_container)
{
const SPEED_FACTOR = 1;
/** @const */
var SPEED_FACTOR = 0.15;
var left_down = false,
right_down = false,
@ -220,9 +218,6 @@ export function MouseAdapter(bus, screen_container)
delta_y = -delta_y;
// NOTE: affected by https://issues.chromium.org/issues/40737979
// Causes cursor jumps on multi-monitor and/or 120+ HZ monitors
mouse.bus.send("mouse-delta", [delta_x, delta_y]);
if(screen_container)

View file

@ -1,5 +1,4 @@
// For Types Only
import { BusConnector } from "../bus.js";
"use strict";
/**
* An ethernet-through-websocket adapter, to be used with
@ -13,7 +12,7 @@ import { BusConnector } from "../bus.js";
* @param {BusConnector} bus
* @param {number} [id=0] id
*/
export function NetworkAdapter(url, bus, id)
function NetworkAdapter(url, bus, id)
{
this.bus = bus;
this.socket = undefined;

View file

@ -1,281 +1,287 @@
import { pads } from "../lib.js";
"use strict";
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++)
/**
* @export
*/
const print_stats = {
stats_to_string: function(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";
}
return print_stats.print_misc_stats(cpu) +
print_stats.print_instruction_counts(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++)
print_misc_stats: function(cpu)
{
for(let fixed_g = 0; fixed_g < 8; fixed_g++)
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(const is_mem of [false, true])
const name = stat_names[i];
let value;
if(name.includes("/"))
{
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 });
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";
}
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 });
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)
{
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");
},
print_instruction_counts_offset: function(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++)
{
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 });
}
}
}
}
let total = 0;
const prefixes = new Set([
0x26, 0x2E, 0x36, 0x3E,
0x64, 0x65, 0x66, 0x67,
0xF0, 0xF2, 0xF3,
]);
for(const { count, opcode } of counts)
{
if(!prefixes.has(opcode))
let total = 0;
const prefixes = new Set([
0x26, 0x2E, 0x36, 0x3E,
0x64, 0x65, 0x66, 0x67,
0xF0, 0xF2, 0xF3,
]);
for(const { count, opcode } of counts)
{
total += count;
if(!prefixes.has(opcode))
{
total += count;
}
}
}
if(total === 0)
{
return "";
}
const per_opcode = new Uint32Array(0x100);
const per_opcode0f = new Uint32Array(0x100);
for(const { opcode, count } of counts)
{
if((opcode & 0xFF00) === 0x0F00)
if(total === 0)
{
per_opcode0f[opcode & 0xFF] += count;
return "";
}
else
const per_opcode = new Uint32Array(0x100);
const per_opcode0f = new Uint32Array(0x100);
for(const { opcode, count } of counts)
{
per_opcode[opcode & 0xFF] += count;
if((opcode & 0xFF00) === 0x0F00)
{
per_opcode0f[opcode & 0xFF] += count;
}
else
{
per_opcode[opcode & 0xFF] += count;
}
}
}
text += "------------------\n";
text += "Total: " + total + "\n";
text += "------------------\n";
text += "Total: " + total + "\n";
const factor = total > 1e7 ? 1000 : 1;
const factor = total > 1e7 ? 1000 : 1;
const max_count = Math.max.apply(Math,
counts.map(({ count }) => Math.round(count / factor))
);
const pad_length = String(max_count).length;
const max_count = Math.max.apply(Math,
counts.map(({ count }) => Math.round(count / factor))
);
const pad_length = String(max_count).length;
text += `Instruction counts ${label} (in ${factor}):\n`;
text += `Instruction counts ${label} (in ${factor}):\n`;
for(let i = 0; i < 0x100; i++)
{
text += i.toString(16).padStart(2, "0") + ":" + pads(Math.round(per_opcode[i] / factor), pad_length);
for(let i = 0; i < 0x100; i++)
{
text += i.toString(16).padStart(2, "0") + ":" + v86util.pads(Math.round(per_opcode[i] / factor), pad_length);
if(i % 16 === 15)
text += "\n";
else
text += " ";
}
if(i % 16 === 15)
text += "\n";
else
text += " ";
}
text += "\n";
text += `Instruction counts ${label} (0f, in ${factor}):\n`;
text += "\n";
text += `Instruction counts ${label} (0f, in ${factor}):\n`;
for(let i = 0; i < 0x100; i++)
{
text += (i & 0xFF).toString(16).padStart(2, "0") + ":" + pads(Math.round(per_opcode0f[i] / factor), pad_length);
for(let i = 0; i < 0x100; i++)
{
text += (i & 0xFF).toString(16).padStart(2, "0") + ":" + v86util.pads(Math.round(per_opcode0f[i] / factor), pad_length);
if(i % 16 === 15)
text += "\n";
else
text += " ";
}
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);
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";
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;
},
};

View file

@ -1,16 +1,11 @@
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;
"use strict";
/**
* Adapter to use visual screen in browsers (in contrast to node)
* @constructor
* @param {Object} options
* @param {function()} screen_fill_buffer
*/
export function ScreenAdapter(options, screen_fill_buffer)
function ScreenAdapter(options, screen_fill_buffer)
{
const screen_container = options.container;
this.screen_fill_buffer = screen_fill_buffer;
@ -97,8 +92,9 @@ export function ScreenAdapter(options, screen_fill_buffer)
cursor_end,
cursor_enabled,
// 8-bit-text to Unicode character map
charmap = get_charmap(options.encoding),
// 8-bit Unicode character maps
charmap_default = [],
charmap = charmap_default,
// render loop state
timer_id = 0,
@ -318,6 +314,52 @@ export 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";
@ -671,6 +713,11 @@ export 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;
@ -838,10 +885,10 @@ export function ScreenAdapter(options, screen_fill_buffer)
text_mode_data[offset + BG_COLOR_INDEX] === bg_color &&
text_mode_data[offset + FG_COLOR_INDEX] === fg_color)
{
const chr = charmap[text_mode_data[offset + CHARACTER_INDEX]];
var ascii = text_mode_data[offset + CHARACTER_INDEX];
text += chr;
dbg_assert(chr);
text += charmap[ascii];
dbg_assert(charmap[ascii]);
i++;
offset += TEXT_BUF_COMPONENT_SIZE;
@ -921,14 +968,16 @@ export function ScreenAdapter(options, screen_fill_buffer)
this.get_text_row = function(y)
{
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)
let result = "";
for(let x = 0; x < text_mode_width; x++)
{
row += charmap[text_mode_data[i]];
const index = (y * text_mode_width + x) * TEXT_BUF_COMPONENT_SIZE;
const character = text_mode_data[index + CHARACTER_INDEX];
result += charmap[character];
}
return row;
return result;
};
this.init();

View file

@ -1,14 +1,11 @@
import { dbg_assert, dbg_log } from "../log.js";
// For Types Only
import { BusConnector } from "../bus.js";
"use strict";
/**
* @constructor
*
* @param {BusConnector} bus
*/
export function SerialAdapter(element, bus)
function SerialAdapter(element, bus)
{
var serial = this;
@ -219,7 +216,7 @@ function SerialRecordingAdapter(bus)
* @constructor
* @param {BusConnector} bus
*/
export function SerialAdapterXtermJS(element, bus)
function SerialAdapterXtermJS(element, bus)
{
this.element = element;
@ -230,21 +227,19 @@ export function SerialAdapterXtermJS(element, bus)
var term = this.term = new window["Terminal"]({
"logLevel": "off",
"convertEol": "true",
});
term.write("This is the serial console. Whatever you type or paste here will be sent to COM1");
const utf8_encoder = new TextEncoder();
const on_data_disposable = term["onData"](function(data_str) {
for(const utf8_byte of utf8_encoder.encode(data_str))
const on_data_disposable = term["onData"](function(data) {
for(let i = 0; i < data.length; i++)
{
bus.send("serial0-input", utf8_byte);
bus.send("serial0-input", data.charCodeAt(i));
}
});
bus.register("serial0-output-byte", function(utf8_byte)
bus.register("serial0-output-byte", function(byte)
{
term.write(Uint8Array.of(utf8_byte));
term.write(Uint8Array.of(byte));
}, this);
this.destroy = function() {

View file

@ -1,25 +1,16 @@
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";
"use strict";
// For Types Only
import { BusConnector } from "../bus.js";
/** @const */
var DAC_QUEUE_RESERVE = 0.2;
/* global registerProcessor, sampleRate */
const DAC_QUEUE_RESERVE = 0.2;
const AUDIOBUFFER_MINIMUM_SAMPLING_RATE = 8000;
/** @const */
var AUDIOBUFFER_MINIMUM_SAMPLING_RATE = 8000;
/**
* @constructor
* @param {!BusConnector} bus
*/
export function SpeakerAdapter(bus)
function SpeakerAdapter(bus)
{
if(typeof window === "undefined")
{
@ -471,9 +462,14 @@ function SpeakerWorkletDAC(bus, audio_context, mixer)
function worklet()
{
const RENDER_QUANTUM = 128;
const MINIMUM_BUFFER_SIZE = 2 * RENDER_QUANTUM;
const QUEUE_RESERVE = 1024;
/** @const */
var RENDER_QUANTUM = 128;
/** @const */
var MINIMUM_BUFFER_SIZE = 2 * RENDER_QUANTUM;
/** @const */
var QUEUE_RESERVE = 1024;
function sinc(x)
{

View file

@ -1,31 +1,100 @@
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";
"use strict";
/**
* Constructor for emulator instances.
*
* For API usage, see v86.d.ts in the root of this repository.
* 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)
* }
* ```
*
* @param {{
disable_mouse: (boolean|undefined),
@ -36,13 +105,14 @@ import { FS } from "../../lib/filesystem.js";
} | undefined),
}} options
* @constructor
* @export
*/
export function V86(options)
function V86(options)
{
if(typeof options.log_level === "number")
{
// XXX: Shared between all emulator instances
set_log_level(options.log_level);
LOG_LEVEL = options.log_level;
}
//var worker = new Worker("src/browser/worker.js");
@ -52,7 +122,7 @@ export function V86(options)
this.cpu_exception_hook = function(n) {};
const bus = Bus.create();
this.bus = bus[0];
const adapter_bus = this.bus = bus[0];
this.emulator_bus = bus[1];
var cpu;
@ -66,7 +136,8 @@ export 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 get_rand_int(); },
"get_rand_int": function() { return v86util.get_rand_int(); },
"apic_acknowledge_irq": function() { return cpu.devices.apic.acknowledge_irq(); },
"stop_idling": function() { return cpu.stop_idling(); },
"io_port_read8": function(addr) { return cpu.io.port_read8(addr); },
@ -88,11 +159,11 @@ export function V86(options)
},
"log_from_wasm": function(offset, len) {
const str = read_sized_string_from_mem(wasm_memory, offset, len);
const str = v86util.read_sized_string_from_mem(wasm_memory, offset, len);
dbg_log(str, LOG_CPU);
},
"console_log_from_wasm": function(offset, len) {
const str = read_sized_string_from_mem(wasm_memory, offset, len);
const str = v86util.read_sized_string_from_mem(wasm_memory, offset, len);
console.error(str);
},
"dbg_trace_from_wasm": function() {
@ -114,8 +185,6 @@ export 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";
@ -123,7 +192,9 @@ export function V86(options)
if(options.wasm_path)
{
v86_bin = options.wasm_path;
v86_bin_fallback = v86_bin.replace("v86.wasm", "v86-fallback.wasm");
const slash = v86_bin.lastIndexOf("/");
const dir = slash === -1 ? "" : v86_bin.substr(0, slash);
v86_bin_fallback = dir + "/" + v86_bin_fallback;
}
else if(typeof window === "undefined" && typeof __dirname === "string")
{
@ -136,7 +207,7 @@ export function V86(options)
v86_bin_fallback = "build/" + v86_bin_fallback;
}
load_file(v86_bin, {
v86util.load_file(v86_bin, {
done: async bytes =>
{
try
@ -147,7 +218,7 @@ export function V86(options)
}
catch(err)
{
load_file(v86_bin_fallback, {
v86util.load_file(v86_bin_fallback, {
done: async bytes => {
const { instance } = await WebAssembly.instantiate(bytes, env);
this.wasm_source = bytes;
@ -235,6 +306,8 @@ 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)
@ -284,21 +357,22 @@ V86.prototype.continue_init = async function(emulator, options)
}
else
{
this.screen_adapter = new DummyScreenAdapter(screen_options);
this.screen_adapter = new DummyScreenAdapter();
}
settings.screen = this.screen_adapter;
settings.screen_options = screen_options;
if(options.serial_container_xtermjs)
{
this.serial_adapter = new SerialAdapterXtermJS(options.serial_container_xtermjs, this.bus);
}
else if(options.serial_container)
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);
}
if(!options.disable_speaker)
{
this.speaker_adapter = new SpeakerAdapter(this.bus);
@ -397,7 +471,7 @@ V86.prototype.continue_init = async function(emulator, options)
{
files_to_load.push({
name,
loadable: buffer_from_object(file, this.zstd_decompress_worker.bind(this)),
loadable: v86util.buffer_from_object(file, this.zstd_decompress_worker.bind(this)),
});
}
};
@ -419,15 +493,7 @@ V86.prototype.continue_init = async function(emulator, options)
add_file("bzimage", options.bzimage);
add_file("initrd", options.initrd);
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)
if(options.filesystem)
{
var fs_url = options.filesystem.basefs;
var base_url = options.filesystem.baseurl;
@ -436,7 +502,7 @@ V86.prototype.continue_init = async function(emulator, options)
if(base_url)
{
file_storage = new ServerFileStorageWrapper(file_storage, base_url, this.zstd_decompress.bind(this));
file_storage = new ServerFileStorageWrapper(file_storage, base_url);
}
settings.fs9p = this.fs9p = new FS(file_storage);
@ -486,7 +552,7 @@ V86.prototype.continue_init = async function(emulator, options)
}
else
{
load_file(f.url, {
v86util.load_file(f.url, {
done: function(result)
{
if(f.url.endsWith(".zst") && f.name !== "initial_state")
@ -495,7 +561,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 SyncBuffer(result));
put_on_settings.call(this, f.name, f.as_json ? result : new v86util.SyncBuffer(result));
cont(index + 1);
}.bind(this),
progress: function progress(e)
@ -552,8 +618,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 SyncBuffer(initrd.buffer));
put_on_settings.call(this, "bzimage", new SyncBuffer(bzimage.buffer));
put_on_settings.call(this, "initrd", new v86util.SyncBuffer(initrd.buffer));
put_on_settings.call(this, "bzimage", new v86util.SyncBuffer(bzimage.buffer));
}
}
else
@ -634,7 +700,8 @@ 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", "stop_idling",
"cpu_event_halt", "microtick", "get_rand_int",
"apic_acknowledge_irq", "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",
@ -646,7 +713,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(read_sized_string_from_mem(wasm.exports.memory.buffer, off, len));
console.log(String.fromCharCode(...new Uint8Array(wasm.exports.memory.buffer, off, len)));
};
env["dbg_trace_from_wasm"] = () => console.trace();
@ -728,7 +795,9 @@ V86.prototype.get_bzimage_initrd_from_filesystem = function(filesystem)
};
/**
* Start emulation. Do nothing if emulator is running already. Can be asynchronous.
* Start emulation. Do nothing if emulator is running already. Can be
* asynchronous.
* @export
*/
V86.prototype.run = async function()
{
@ -737,6 +806,7 @@ V86.prototype.run = async function()
/**
* Stop emulation. Do nothing if emulator is not running. Can be asynchronous.
* @export
*/
V86.prototype.stop = async function()
{
@ -756,7 +826,8 @@ V86.prototype.stop = async function()
};
/**
* Free resources associated with this instance
* @ignore
* @export
*/
V86.prototype.destroy = async function()
{
@ -773,6 +844,7 @@ V86.prototype.destroy = async function()
/**
* Restart (force a reboot).
* @export
*/
V86.prototype.restart = function()
{
@ -780,12 +852,14 @@ V86.prototype.restart = function()
};
/**
* Add an event listener (the emulator is an event emitter).
* Add an event listener (the emulator is an event emitter). A list of events
* can be found at [events.md](events.md).
*
* 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)
{
@ -797,6 +871,7 @@ V86.prototype.add_listener = function(event, listener)
*
* @param {string} event
* @param {function(*)} listener
* @export
*/
V86.prototype.remove_listener = function(event, listener)
{
@ -816,6 +891,7 @@ V86.prototype.remove_listener = function(event, listener)
* state buffer.
*
* @param {ArrayBuffer} state
* @export
*/
V86.prototype.restore_state = async function(state)
{
@ -827,6 +903,7 @@ 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()
{
@ -837,6 +914,7 @@ V86.prototype.save_state = async function()
/**
* @return {number}
* @ignore
* @export
*/
V86.prototype.get_instruction_counter = function()
{
@ -853,6 +931,7 @@ V86.prototype.get_instruction_counter = function()
/**
* @return {boolean}
* @export
*/
V86.prototype.is_running = function()
{
@ -862,128 +941,37 @@ 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)
{
await new Promise(resolve => {
load_file(file.url, {
done: result =>
{
fda.insert_disk(new SyncBuffer(result));
resolve();
}
});
});
}
else
{
const image = buffer_from_object(file, this.zstd_decompress_worker.bind(this));
image.onload = () =>
{
fda.insert_disk(image);
};
await image.load();
}
};
/**
* 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.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, {
v86util.load_file(file.url, {
done: result =>
{
this.v86.cpu.devices.cdrom.set_cdrom(new SyncBuffer(result));
this.v86.cpu.devices.fdc.set_fda(new v86util.SyncBuffer(result));
},
});
}
else
{
const image = buffer_from_object(file, this.zstd_decompress_worker.bind(this));
const image = v86util.buffer_from_object(file, this.zstd_decompress_worker.bind(this));
image.onload = () =>
{
this.v86.cpu.devices.cdrom.set_cdrom(image);
this.v86.cpu.devices.fdc.set_fda(image);
};
await image.load();
}
};
/**
* Eject the CD-ROM.
* Eject the floppy drive.
* @export
*/
V86.prototype.eject_cdrom = function()
V86.prototype.eject_fda = function()
{
this.v86.cpu.devices.cdrom.eject();
this.v86.cpu.devices.fdc.eject_fda();
};
/**
@ -992,47 +980,47 @@ V86.prototype.eject_cdrom = function()
* Do nothing if there is no keyboard controller.
*
* @param {Array.<number>} codes
* @param {number=} delay
* @export
*/
V86.prototype.keyboard_send_scancodes = async function(codes, delay)
V86.prototype.keyboard_send_scancodes = function(codes)
{
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
* @param {Array.<number>} codes
* @param {number=} delay
* @ignore
* @export
*/
V86.prototype.keyboard_send_keys = async function(codes, delay)
V86.prototype.keyboard_send_keys = function(codes)
{
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, assuming the guest OS uses a US keyboard layout
* @param {string} string
* @param {number=} delay
* Send text
* @ignore
* @export
*/
V86.prototype.keyboard_send_text = async function(string, delay)
V86.prototype.keyboard_send_text = function(string)
{
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 (returns an <img> element, only works in browsers)
* Download a screenshot.
*
* @ignore
* @export
*/
V86.prototype.screen_make_screenshot = function()
{
@ -1048,6 +1036,9 @@ V86.prototype.screen_make_screenshot = function()
*
* @param {number} sx
* @param {number} sy
*
* @ignore
* @export
*/
V86.prototype.screen_set_scale = function(sx, sy)
{
@ -1058,7 +1049,10 @@ V86.prototype.screen_set_scale = function(sx, sy)
};
/**
* Go fullscreen (only browsers)
* Go fullscreen.
*
* @ignore
* @export
*/
V86.prototype.screen_go_fullscreen = function()
{
@ -1100,21 +1094,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 = async function()
V86.prototype.lock_mouse = function()
{
const elem = document.body;
var elem = document.body;
try
var fn = elem["requestPointerLock"] ||
elem["mozRequestPointerLock"] ||
elem["webkitRequestPointerLock"];
if(fn)
{
await elem.requestPointerLock({
unadjustedMovement: true,
});
}
catch(e)
{
// as per MDN, retry without unadjustedMovement option
await elem.requestPointerLock();
fn.call(elem);
}
};
@ -1123,33 +1117,34 @@ V86.prototype.lock_mouse = async function()
*
* @param {boolean} enabled
*/
V86.prototype.mouse_set_enabled = function(enabled)
V86.prototype.mouse_set_status = 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_enabled = function(enabled)
V86.prototype.keyboard_set_status = 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)
{
@ -1163,6 +1158,7 @@ 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)
{
@ -1212,12 +1208,52 @@ 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)
{
@ -1251,6 +1287,7 @@ V86.prototype.create_file = async function(file, data)
* initialized.
*
* @param {string} file
* @export
*/
V86.prototype.read_file = async function(file)
{
@ -1332,110 +1369,52 @@ V86.prototype.automatically = function(steps)
run(steps);
};
/**
* 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)
V86.prototype.wait_until_vga_screen_contains = function(text)
{
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())
return new Promise(resolve =>
{
if(match_multi)
function test_line(line)
{
screen_lines.push(screen_line.trimRight());
return typeof text === "string" ? line.includes(text) : text.test(line);
}
else if(contains_expected(screen_line, expected))
{
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)
for(const line of this.screen_adapter.get_text_screen())
{
let screen_height = screen_lines.length;
while(screen_height > 0 && screen_lines[screen_height - 1] === "")
if(test_line(line))
{
screen_height--;
resolve(true);
return;
}
const screen_offset = screen_height - expected.length;
if(screen_offset >= 0)
}
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)
{
let matches = true;
for(let i = 0; i < expected.length && matches; i++)
const line = this.screen_adapter.get_text_row(row);
if(test_line(line))
{
matches = contains_expected(screen_lines[screen_offset + i], expected[i]);
}
if(matches)
{
succeeded = true;
break;
this.remove_listener("screen-put-char", put_char);
resolve();
return;
}
}
}
await new Promise(resolve => setTimeout(resolve, 100));
changed_rows.clear();
setTimeout(check, 100);
};
check();
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;
this.add_listener("screen-put-char", put_char);
});
};
/**
@ -1468,11 +1447,6 @@ 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
@ -1496,19 +1470,3 @@ 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;
}

View file

@ -1,15 +1,4 @@
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";
"use strict";
/**
* @constructor
@ -18,7 +7,7 @@ import { BusConnector } from "../bus.js";
* @param {BusConnector} bus
* @param {*=} config
*/
export function WispNetworkAdapter(wisp_url, bus, config)
function WispNetworkAdapter(wisp_url, bus, config)
{
this.register_ws(wisp_url);
this.last_stream = 1;
@ -36,8 +25,7 @@ export function WispNetworkAdapter(wisp_url, bus, config)
this.dns_method = config.dns_method || "doh";
this.doh_server = config.doh_server;
this.tcp_conn = {};
this.mtu = config.mtu;
this.eth_encoder_buf = create_eth_encoder_buf(this.mtu);
this.eth_encoder_buf = create_eth_encoder_buf();
this.bus.register("net" + this.id + "-mac", function(mac) {
this.vm_mac = new Uint8Array(mac.split(":").map(function(x) { return parseInt(x, 16); }));
@ -94,12 +82,10 @@ WispNetworkAdapter.prototype.process_incoming_wisp_frame = function(frame) {
}
if(this.connections[stream_id].congested) {
const buffer = this.congested_buffer.slice(0);
this.congested_buffer.length = 0;
this.connections[stream_id].congested = false;
for(const packet of buffer) {
for(const packet of this.congested_buffer) {
this.send_packet(packet.data, packet.type, stream_id);
}
this.connections[stream_id].congested = false;
}
break;
case 4: // CLOSE
@ -188,12 +174,17 @@ WispNetworkAdapter.prototype.destroy = function()
};
/**
* @param {TCPConnection} conn
* @param {Uint8Array} packet
* @param {String} tuple
*/
WispNetworkAdapter.prototype.on_tcp_connection = function(conn, packet)
WispNetworkAdapter.prototype.on_tcp_connection = function(packet, tuple)
{
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) {
@ -220,7 +211,7 @@ WispNetworkAdapter.prototype.on_tcp_connection = function(conn, packet)
type: "CONNECT",
stream_id: conn.stream_id,
hostname: packet.ipv4.dest.join("."),
port: conn.sport,
port: packet.tcp.dport,
data_callback: (data) => {
conn.write(data);
},
@ -229,7 +220,7 @@ WispNetworkAdapter.prototype.on_tcp_connection = function(conn, packet)
}
});
conn.accept();
conn.accept(packet);
return true;
};

View file

@ -1,7 +1,9 @@
import { dbg_assert } from "../log.js";
"use strict";
var WorkerBus = {};
/** @constructor */
export var Connector = function(pair)
WorkerBus.Connector = function(pair)
{
this.listeners = {};
this.pair = pair;
@ -20,7 +22,7 @@ export var Connector = function(pair)
};
Connector.prototype.register = function(name, fn, this_value)
WorkerBus.Connector.prototype.register = function(name, fn, this_value)
{
var listeners = this.listeners[name];
@ -42,7 +44,7 @@ Connector.prototype.register = function(name, fn, this_value)
* @param {*=} value
* @param {*=} transfer_list
*/
Connector.prototype.send = function(name, value, transfer_list)
WorkerBus.Connector.prototype.send = function(name, value, transfer_list)
{
dbg_assert(arguments.length >= 1);
@ -55,7 +57,7 @@ Connector.prototype.send = function(name, value, transfer_list)
};
export var init = function(worker)
WorkerBus.init = function(worker)
{
return new Connector(worker);
return new WorkerBus.Connector(worker);
};

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,9 @@
import { dbg_assert } from "./log.js";
"use strict";
export var Bus = {};
var Bus = {};
/** @constructor */
export function BusConnector()
function BusConnector()
{
this.listeners = {};
this.pair = undefined;

View file

@ -1,5 +0,0 @@
/**
* @define {boolean}
* Overridden for production by closure compiler
*/
var DEBUG = true;

53
src/config.js Normal file
View file

@ -0,0 +1,53 @@
"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;

View file

@ -1,38 +1,41 @@
export const
LOG_ALL = -1,
LOG_NONE = 0,
"use strict";
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;
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;
/**
* @const
* @type {Array<Array<string|number>>}
*/
export const LOG_NAMES = [
var LOG_NAMES = [
[1, ""],
[LOG_CPU, "CPU"],
[LOG_DISK, "DISK"],
@ -59,81 +62,105 @@ export const LOG_NAMES = [
[LOG_FETCH, "FETC"],
];
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,
var
// default values of reserved flags bits
FLAGS_DEFAULT = 1 << 1,
// 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,
REG_EAX = 0,
REG_ECX = 1,
REG_EDX = 2,
REG_EBX = 3,
REG_ESP = 4,
REG_EBP = 5,
REG_ESI = 6,
REG_EDI = 7,
/**
* default values of reserved flags bits
* @const
*/
FLAGS_DEFAULT = 1 << 1,
REG_ES = 0,
REG_CS = 1,
REG_SS = 2,
REG_DS = 3,
REG_FS = 4,
REG_GS = 5,
REG_LDTR = 7; // local descriptor table register
/** @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,
export const
// The minimum number of bytes that can be memory-mapped by one device.
/** @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
*/
MMAP_BLOCK_BITS = 17,
/** @const */
MMAP_BLOCK_SIZE = 1 << MMAP_BLOCK_BITS,
/** @const */
MMAP_MAX = 0x100000000;
export const CR0_PG = 1 << 31;
export const CR4_PAE = 1 << 5;
/** @const */
var CR0_PG = 1 << 31;
/** @const */
var CR4_PAE = 1 << 5;
// https://github.com/qemu/seabios/blob/14221cd86eadba82255fdc55ed174d401c7a0a04/src/fw/paravirt.c#L205-L219
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_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_CUSTOM_START = 0x8000;
/** @const */ var FW_CFG_CUSTOM_START = 0x8000;
// This value is specific to v86, choosen to hopefully not collide with other indexes
export const FW_CFG_FILE_START = 0xC000;
export const FW_CFG_SIGNATURE_QEMU = 0x554D4551;
/** @const */ var FW_CFG_FILE_START = 0xC000;
/** @const */ var FW_CFG_SIGNATURE_QEMU = 0x554D4551;
// See same constant in jit.rs
export const WASM_TABLE_SIZE = 900;
/** @const */
var WASM_TABLE_SIZE = 900;
export const WASM_TABLE_OFFSET = 1024;
/** @const */
var WASM_TABLE_OFFSET = 1024;
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;
/** @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;

1129
src/cpu.js

File diff suppressed because it is too large Load diff

662
src/debug.js Normal file
View file

@ -0,0 +1,662 @@
"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();
}
}
};
};

View file

@ -1,15 +1,10 @@
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";
"use strict";
/**
* @constructor
* @param {CPU} cpu
*/
export function DMA(cpu)
function DMA(cpu)
{
/** @const @type {CPU} */
this.cpu = cpu;

View file

@ -1,4 +1,4 @@
import { dbg_log, LOG_LEVEL } from "./log.js";
"use strict";
// 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 */
export function read_elf(buffer)
function read_elf(buffer)
{
const view = new DataView(buffer);

View file

@ -1,3 +1,5 @@
"use strict";
var global = {};
var process = { hrtime: function() {} };
@ -7,7 +9,8 @@ var process = { hrtime: function() {} };
*/
var registerProcessor = function(name, processor) {};
const sampleRate = 0;
/** @const */
var sampleRate = 0;
var WabtModule = {
readWasm: function(buf, opt) {},

File diff suppressed because it is too large Load diff

2389
src/ide.js

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,4 @@
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;
"use strict";
/**
* The ISA IO bus
@ -15,7 +7,7 @@ const LOG_ALL_IO = false;
* @constructor
* @param {CPU} cpu
*/
export function IO(cpu)
function IO(cpu)
{
/** @const */
this.ports = [];
@ -95,9 +87,9 @@ IO.prototype.empty_port_write = function(x)
/**
* @param {number} port_addr
* @param {Object} device
* @param {function(number):number=} r8
* @param {function(number):number=} r16
* @param {function(number):number=} r32
* @param {function():number=} r8
* @param {function():number=} r16
* @param {function():number=} r32
*/
IO.prototype.register_read = function(port_addr, device, r8, r16, r32)
{
@ -364,7 +356,7 @@ IO.prototype.port_read8 = function(port_addr)
}
var value = entry.read8.call(entry.device, port_addr);
dbg_assert(typeof value === "number");
if(value < 0 || value >= 0x100) dbg_assert(false, "8 bit port returned large value: " + h(port_addr));
dbg_assert(value < 0x100 && value >= 0, "8 bit port returned large value: " + h(port_addr));
return value;
};
@ -381,7 +373,7 @@ IO.prototype.port_read16 = function(port_addr)
}
var value = entry.read16.call(entry.device, port_addr);
dbg_assert(typeof value === "number");
if(value < 0 || value >= 0x10000) dbg_assert(false, "16 bit port returned large value: " + h(port_addr));
dbg_assert(value < 0x10000 && value >= 0, "16 bit port returned large value: " + h(port_addr));
return value;
};

364
src/ioapic.js Normal file
View file

@ -0,0 +1,364 @@
"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];
};

View file

@ -1,255 +0,0 @@
// 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"
);
}

View file

@ -1,6 +1,4 @@
import { h } from "./lib.js";
import { dbg_assert, dbg_log } from "./log.js";
"use strict";
// https://www.kernel.org/doc/Documentation/x86/boot.txt
@ -39,7 +37,7 @@ const LINUX_BOOT_HDR_LOADFLAGS_KEEP_SEGMENTS = 1 << 6;
const LINUX_BOOT_HDR_LOADFLAGS_CAN_USE_HEAPS = 1 << 7;
export function load_kernel(mem8, bzimage, initrd, cmdline)
function load_kernel(mem8, bzimage, initrd, cmdline)
{
dbg_log("Trying to load kernel of size " + bzimage.byteLength);

View file

@ -1,20 +1,52 @@
import { dbg_assert } from "./log.js";
"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 || {};
// pad string with spaces on the right
export function pads(str, len)
v86util.pads = function(str, len)
{
str = (str || str === 0) ? str + "" : "";
return str.padEnd(len, " ");
}
};
// pad string with zeros on the left
export function pad0(str, len)
v86util.pad0 = function(str, len)
{
str = (str || str === 0) ? str + "" : "";
return str.padStart(len, "0");
}
};
export var view = function(constructor, memory, offset, length)
// 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)
{
dbg_assert(offset >= 0);
return new Proxy({},
@ -46,7 +78,7 @@ export var view = function(constructor, memory, offset, length)
* @param {number=} len
* @return {string}
*/
export function h(n, len)
function h(n, len)
{
if(!n)
{
@ -57,14 +89,14 @@ export function h(n, len)
var str = n.toString(16);
}
return "0x" + pad0(str.toUpperCase(), len || 1);
return "0x" + v86util.pad0(str.toUpperCase(), len || 1);
}
export function hex_dump(buffer)
function hex_dump(buffer)
{
function hex(n, len)
{
return pad0(n.toString(16).toUpperCase(), len);
return v86util.pad0(n.toString(16).toUpperCase(), len);
}
const result = [];
@ -112,13 +144,11 @@ export 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);
get_rand_int = function()
v86util.get_rand_int = function()
{
crypto.getRandomValues(rand_data);
return rand_data[0];
@ -129,41 +159,34 @@ else if(typeof require !== "undefined")
/** @type {{ randomBytes: Function }} */
const crypto = require("crypto");
get_rand_int = function()
v86util.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");
}
export var int_log2;
if(typeof Math.clz32 === "function" && Math.clz32(0) === 32 && Math.clz32(0x12345) === 15 && Math.clz32(-1) === 0)
(function()
{
/**
* calculate the integer logarithm base 2
* @param {number} x
* @return {number}
*/
int_log2 = function(x)
if(typeof Math.clz32 === "function" && Math.clz32(0) === 32 && Math.clz32(0x12345) === 15 && Math.clz32(-1) === 0)
{
dbg_assert(x > 0);
/**
* calculate the integer logarithm base 2
* @param {number} x
* @return {number}
*/
v86util.int_log2 = function(x)
{
dbg_assert(x > 0);
return 31 - Math.clz32(x);
};
} else {
return 31 - Math.clz32(x);
};
return;
}
var int_log2_table = new Int8Array(256);
@ -180,7 +203,7 @@ if(typeof Math.clz32 === "function" && Math.clz32(0) === 32 && Math.clz32(0x1234
* @param {number} x
* @return {number}
*/
int_log2 = function(x)
v86util.int_log2 = function(x)
{
x >>>= 0;
dbg_assert(x > 0);
@ -213,28 +236,28 @@ if(typeof Math.clz32 === "function" && Math.clz32(0) === 32 && Math.clz32(0x1234
}
}
};
}
})();
export const round_up_to_next_power_of_2 = function(x)
v86util.round_up_to_next_power_of_2 = function(x)
{
dbg_assert(x >= 0);
return x <= 1 ? 1 : 1 << 1 + int_log2(x - 1);
return x <= 1 ? 1 : 1 << 1 + v86util.int_log2(x - 1);
};
if(typeof DEBUG !== "undefined" && DEBUG)
if(DEBUG)
{
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.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(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);
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);
}
/**
@ -243,7 +266,7 @@ if(typeof DEBUG !== "undefined" && DEBUG)
* Queue wrapper around Uint8Array
* Used by devices such as the PS2 controller
*/
export function ByteQueue(size)
function ByteQueue(size)
{
var data = new Uint8Array(size),
start,
@ -314,7 +337,7 @@ export function ByteQueue(size)
* Queue wrapper around Float32Array
* Used by devices such as the sound blaster sound card
*/
export function FloatQueue(size)
function FloatQueue(size)
{
this.size = size;
this.data = new Float32Array(size);
@ -442,7 +465,7 @@ CircularQueue.prototype.set = function(new_data)
this.index = 0;
};
export function dump_file(ab, name)
function dump_file(ab, name)
{
if(!Array.isArray(ab))
{
@ -453,7 +476,7 @@ export function dump_file(ab, name)
download(blob, name);
}
export function download(file_or_blob, name)
function download(file_or_blob, name)
{
var a = document.createElement("a");
a["download"] = name;
@ -479,7 +502,7 @@ export function download(file_or_blob, name)
* A simple 1d bitmap
* @constructor
*/
export var Bitmap = function(length_or_buffer)
v86util.Bitmap = function(length_or_buffer)
{
if(typeof length_or_buffer === "number")
{
@ -491,11 +514,11 @@ export var Bitmap = function(length_or_buffer)
}
else
{
dbg_assert(false, "Bitmap: Invalid argument");
dbg_assert(false, "v86util.Bitmap: Invalid argument");
}
};
Bitmap.prototype.set = function(index, value)
v86util.Bitmap.prototype.set = function(index, value)
{
const bit_index = index & 7;
const byte_index = index >> 3;
@ -505,7 +528,7 @@ Bitmap.prototype.set = function(index, value)
value ? this.view[byte_index] | bit_mask : this.view[byte_index] & ~bit_mask;
};
Bitmap.prototype.get = function(index)
v86util.Bitmap.prototype.get = function(index)
{
const bit_index = index & 7;
const byte_index = index >> 3;
@ -513,249 +536,187 @@ Bitmap.prototype.get = function(index)
return this.view[byte_index] >> bit_index & 1;
};
Bitmap.prototype.get_buffer = function()
v86util.Bitmap.prototype.get_buffer = function()
{
return this.view.buffer;
};
export var load_file;
export var get_file_size;
if(typeof XMLHttpRequest === "undefined" ||
typeof process !== "undefined" && process.versions && process.versions.node)
if(typeof XMLHttpRequest === "undefined")
{
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;
};
v86util.load_file = load_file_nodejs;
}
else
{
/**
* @param {string} filename
* @param {Object} options
* @param {number=} n_tries
*/
load_file = async function(filename, options, n_tries)
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)
{
var http = new XMLHttpRequest();
http.responseType = "json";
}
else
{
http.responseType = "arraybuffer";
}
http.open(options.method || "get", filename, true);
if(options.headers)
{
var header_names = Object.keys(options.headers);
if(options.as_json)
for(var i = 0; i < header_names.length; i++)
{
http.responseType = "json";
}
else
{
http.responseType = "arraybuffer";
var name = header_names[i];
http.setRequestHeader(name, options.headers[name]);
}
}
if(options.headers)
{
var header_names = Object.keys(options.headers);
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");
for(var i = 0; i < header_names.length; i++)
// Abort if server responds with complete file in response to range
// request, to prevent downloading large files from broken http servers
http.onreadystatechange = function()
{
if(http.status === 200)
{
var name = header_names[i];
http.setRequestHeader(name, options.headers[name]);
}
}
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()
{
if(http.status === 200)
{
console.error("Server sent full file in response to ranged request, aborting", { filename });
http.abort();
}
};
}
http.onload = function(e)
{
if(http.readyState === 4)
{
if(http.status !== 200 && http.status !== 206)
{
console.error("Loading the image " + filename + " failed (status %d)", http.status);
if(http.status >= 500 && http.status < 600)
{
retry();
}
}
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);
}
console.error("Server sent full file in response to ranged request, aborting", { filename });
http.abort();
}
};
}
http.onerror = function(e)
http.onload = function(e)
{
if(http.readyState === 4)
{
console.error("Loading the image " + filename + " failed", e);
retry();
};
if(options.progress)
{
http.onprogress = function(e)
if(http.status !== 200 && http.status !== 206)
{
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);
console.error("Loading the image " + filename + " failed (status %d)", http.status);
if(http.status >= 500 && http.status < 600)
{
retry();
}
}
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);
}
}
};
get_file_size = async function(url)
http.onerror = function(e)
{
return new Promise((resolve, reject) => {
load_file(url, {
done: (buffer, http) =>
{
var header = http.getResponseHeader("Content-Range") || "";
var match = header.match(/\/(\d+)\s*$/);
console.error("Loading the image " + filename + " failed", e);
retry();
};
if(match)
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) =>
{
resolve(+match[1]);
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)
{
console.log("Could not read file:", filename, err);
}
else
{
var result = data;
if(options.as_json)
{
result = JSON.parse(result);
}
else
{
const error = new Error("`Range: bytes=...` header not supported (Got `" + header + "`)");
reject(error);
result = new Uint8Array(result).buffer;
}
},
headers: {
Range: "bytes=0-0",
"X-Accept-Encoding": "identity"
options.done(result);
}
});
});
};
}
}
// Reads len characters at offset from Memory object mem as a JS string
export function read_sized_string_from_mem(mem, offset, len)
v86util.read_sized_string_from_mem = 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;
}

View file

@ -1,26 +1,6 @@
if(typeof DEBUG === "undefined")
{
globalThis.DEBUG = true;
}
"use strict";
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 = [];
var log_data = [];
function do_the_log(message)
{
@ -36,16 +16,17 @@ function do_the_log(message)
/**
* @type {function((string|number), number=)}
* @const
*/
export const dbg_log = (function()
var dbg_log = (function()
{
if(!DEBUG)
{
return function() {};
}
/** @type {Object.<number, string>} */
const dbg_names = LOG_NAMES.reduce(function(a, x)
/** @const @type {Object.<number, string>} */
var dbg_names = LOG_NAMES.reduce(function(a, x)
{
a[x[0]] = x[1];
return a;
@ -66,7 +47,7 @@ export const dbg_log = (function()
if(level & LOG_LEVEL)
{
var level_name = dbg_names[level] || "",
message = "[" + pads(level_name, 4) + "] " + stuff;
message = "[" + v86util.pads(level_name, 4) + "] " + stuff;
if(message === log_last_message)
{
@ -79,10 +60,10 @@ export const dbg_log = (function()
}
var now = new Date();
var time_str = pad0(now.getHours(), 2) + ":" +
pad0(now.getMinutes(), 2) + ":" +
pad0(now.getSeconds(), 2) + "+" +
pad0(now.getMilliseconds(), 3) + " ";
var time_str = v86util.pad0(now.getHours(), 2) + ":" +
v86util.pad0(now.getMinutes(), 2) + ":" +
v86util.pad0(now.getSeconds(), 2) + "+" +
v86util.pad0(now.getMilliseconds(), 3) + " ";
if(log_message_repetitions)
{
@ -109,7 +90,7 @@ export const dbg_log = (function()
/**
* @param {number=} level
*/
export function dbg_trace(level)
function dbg_trace(level)
{
if(!DEBUG) return;
@ -121,7 +102,7 @@ export function dbg_trace(level)
* @param {string=} msg
* @param {number=} level
*/
export function dbg_assert(cond, msg, level)
function dbg_assert(cond, msg, level)
{
if(!DEBUG) return;
@ -132,7 +113,7 @@ export function dbg_assert(cond, msg, level)
}
export function dbg_assert_failed(msg)
function dbg_assert_failed(msg)
{
debugger;
console.trace();

View file

@ -1,12 +1,10 @@
import { CPU } from "./cpu.js";
import { save_state, restore_state } from "./state.js";
export { V86 } from "./browser/starter.js";
"use strict";
/**
* @constructor
* @param {Object=} wasm
*/
export function v86(bus, wasm)
function v86(bus, wasm)
{
/** @type {boolean} */
this.running = false;
@ -100,7 +98,6 @@ if(typeof process !== "undefined")
{
v86.prototype.yield = function(t, tick)
{
/* global global */
if(t < 1)
{
global.setImmediate(tick => this.yield_callback(tick), tick);
@ -114,17 +111,6 @@ 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
@ -166,7 +152,8 @@ else if(typeof Worker !== "undefined")
// // TODO: Make this deactivatable, for other applications
// // using postMessage
//
// const MAGIC_POST_MESSAGE = 0xAA55;
// /** @const */
// let MAGIC_POST_MESSAGE = 0xAA55;
//
// v86.prototype.yield = function(t)
// {
@ -209,16 +196,16 @@ else
v86.prototype.save_state = function()
{
// TODO: Should be implemented here, not on cpu
return save_state(this.cpu);
return this.cpu.save_state();
};
v86.prototype.restore_state = function(state)
{
// TODO: Should be implemented here, not on cpu
return restore_state(this.cpu, state);
return this.cpu.restore_state(state);
};
/* global require */
if(typeof performance === "object" && performance.now)
{
v86.microtick = performance.now.bind(performance);

98
src/memory.js Normal file
View file

@ -0,0 +1,98 @@
"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);
};

View file

@ -1,66 +1,59 @@
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";
"use strict";
// http://www.ethernut.de/pdf/8019asds.pdf
const NE2K_LOG_VERBOSE = false;
const NE2K_LOG_PACKETS = false;
const E8390_CMD = 0x00; /* The command register (for all pages) */
/** @const */ var E8390_CMD = 0x00; /* The command register (for all pages) */
/* Page 0 register offsets. */
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 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 NE_DATAPORT = 0x10; /* NatSemi-defined port window offset. */
const NE_RESET = 0x1f; /* Issue a read to reset, a write to clear. */
/** @const */ var NE_DATAPORT = 0x10; /* NatSemi-defined port window offset. */
/** @const */ var NE_RESET = 0x1f; /* Issue a read to reset, a write to clear. */
/* Bits in EN0_ISR - Interrupt status register */
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 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 ENRSR_RXOK = 0x01; /* Received a good packet */
/** @const */ var ENRSR_RXOK = 0x01; /* Received a good packet */
const START_PAGE = 0x40;
const START_RX_PAGE = 0x40 + 12;
const STOP_PAGE = 0x80;
/** @const */ var START_PAGE = 0x40;
/** @const */ var START_RX_PAGE = 0x40 + 12;
/** @const */ var STOP_PAGE = 0x80;
// Search and replace MAC addresses in ethernet, arp and dhcp packets.
@ -233,7 +226,7 @@ function translate_mac_address(packet, search_mac, replacement_mac)
}
}
export function format_mac(mac)
function format_mac(mac)
{
return [
mac[0].toString(16).padStart(2, "0"),
@ -295,7 +288,7 @@ function dump_packet(packet, prefix)
* @param {Boolean} mac_address_translation
* @param {number} [id=0] id
*/
export function Ne2k(cpu, bus, preserve_mac_from_state_image, mac_address_translation, id)
function Ne2k(cpu, bus, preserve_mac_from_state_image, mac_address_translation, id)
{
/** @const @type {CPU} */
this.cpu = cpu;
@ -318,7 +311,8 @@ export function Ne2k(cpu, bus, preserve_mac_from_state_image, mac_address_transl
this.name = "ne2k";
const use_pci = true;
/** @const */
var use_pci = true;
if(use_pci)
{
@ -396,12 +390,7 @@ export function Ne2k(cpu, bus, preserve_mac_from_state_image, mac_address_transl
{
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)
{

View file

@ -1,20 +1,16 @@
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";
"use strict";
// http://wiki.osdev.org/PCI
export const PCI_CONFIG_ADDRESS = 0xCF8;
export const PCI_CONFIG_DATA = 0xCFC;
var
/** @const */ PCI_CONFIG_ADDRESS = 0xCF8,
/** @const */ PCI_CONFIG_DATA = 0xCFC;
/**
* @constructor
* @param {CPU} cpu
*/
export function PCI(cpu)
function PCI(cpu)
{
this.pci_addr = new Uint8Array(4);
this.pci_value = new Uint8Array(4);
@ -394,7 +390,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 from " + h(space[addr >> 2]) + " to " +
dbg_log("BAR" + bar_nr + " exists=" + (bar ? "y" : "n") + " changed to " +
h(written >>> 0) + " dev=" + h(bdf >> 3, 2) + " (" + device.name + ") ", LOG_PCI);
if(bar)
@ -495,10 +491,7 @@ PCI.prototype.register_device = function(device)
dbg_log("PCI register bdf=" + h(device_id) + " (" + device.name + ")", LOG_PCI);
if(this.devices[device_id])
{
dbg_log("warning: overwriting device " + this.devices[device_id].name + " with " + device.name, LOG_PCI);
}
dbg_assert(!this.devices[device_id]);
dbg_assert(device.pci_space.length >= 64);
dbg_assert(device_id < this.devices.length);
@ -521,7 +514,6 @@ 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 = [];
@ -561,6 +553,17 @@ 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);
@ -569,6 +572,18 @@ 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);
}
}
};

View file

@ -1,21 +1,17 @@
import { v86 } from "./main.js";
import { LOG_PIT } from "./const.js";
import { h } from "./lib.js";
import { dbg_log } from "./log.js";
"use strict";
// For Types Only
import { CPU } from "./cpu.js";
// In kHz
export const OSCILLATOR_FREQ = 1193.1816666; // 1.193182 MHz
/**
* @const
* In kHz
*/
var OSCILLATOR_FREQ = 1193.1816666; // 1.193182 MHz
/**
* @constructor
*
* Programmable Interval Timer
*/
export function PIT(cpu, bus)
function PIT(cpu, bus)
{
/** @const @type {CPU} */
this.cpu = cpu;
@ -308,13 +304,13 @@ PIT.prototype.port43_write = function(reg_byte)
if(read_mode === 1)
{
// lsb
this.counter_next_low[i] = 1;
// msb
this.counter_next_low[i] = 0;
}
else if(read_mode === 2)
{
// msb
this.counter_next_low[i] = 0;
// lsb
this.counter_next_low[i] = 1;
}
else
{

View file

@ -1,12 +1,6 @@
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";
"use strict";
/** @const */
const PS2_LOG_VERBOSE = false;
/**
@ -14,7 +8,7 @@ const PS2_LOG_VERBOSE = false;
* @param {CPU} cpu
* @param {BusConnector} bus
*/
export function PS2(cpu, bus)
function PS2(cpu, bus)
{
/** @const @type {CPU} */
this.cpu = cpu;
@ -22,40 +16,6 @@ export function PS2(cpu, bus)
/** @const @type {BusConnector} */
this.bus = bus;
this.reset();
this.bus.register("keyboard-code", function(code)
{
this.kbd_send_code(code);
}, this);
this.bus.register("mouse-click", function(data)
{
this.mouse_send_click(data[0], data[1], data[2]);
}, this);
this.bus.register("mouse-delta", function(data)
{
this.mouse_send_delta(data[0], data[1]);
}, this);
this.bus.register("mouse-wheel", function(data)
{
this.wheel_movement -= data[0];
this.wheel_movement -= data[1] * 2; // X Wheel Movement
this.wheel_movement = Math.min(7, Math.max(-8, this.wheel_movement));
this.send_mouse_packet(0, 0);
}, this);
cpu.io.register_read(0x60, this, this.port60_read);
cpu.io.register_read(0x64, this, this.port64_read);
cpu.io.register_write(0x60, this, this.port60_write);
cpu.io.register_write(0x64, this, this.port64_write);
}
PS2.prototype.reset = function()
{
/** @type {boolean} */
this.enable_mouse_stream = false;
@ -141,13 +101,42 @@ PS2.prototype.reset = function()
/** @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()
{
@ -297,8 +286,7 @@ PS2.prototype.mouse_send_delta = function(delta_x, delta_y)
// note: delta_x or delta_y can be floating point numbers
//const factor = this.resolution * this.sample_rate / 80;
const factor = 1;
var factor = this.resolution * this.sample_rate / 80;
this.mouse_delta_x += delta_x * factor;
this.mouse_delta_y += delta_y * factor;
@ -310,7 +298,8 @@ 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

View file

@ -1,66 +1,57 @@
import { v86 } from "./main.js";
import { LOG_RTC } from "./const.js";
import { h } from "./lib.js";
import { dbg_assert, dbg_log } from "./log.js";
"use strict";
// For Types Only
import { CPU } from "./cpu.js";
import { DMA } from "./dma.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;
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;
/** @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;
// see CPU.prototype.fill_cmos
export const BOOT_ORDER_CD_FIRST = 0x123;
export const BOOT_ORDER_HD_FIRST = 0x312;
export const BOOT_ORDER_FD_FIRST = 0x321;
const BOOT_ORDER_CD_FIRST = 0x123;
const BOOT_ORDER_HD_FIRST = 0x312;
const BOOT_ORDER_FD_FIRST = 0x321;
/**
* RTC (real time clock) and CMOS
* @constructor
* @param {CPU} cpu
*/
export function RTC(cpu)
function RTC(cpu)
{
/** @const @type {CPU} */
this.cpu = cpu;
@ -87,13 +78,8 @@ export 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;
@ -120,9 +106,6 @@ 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;
};
@ -141,9 +124,6 @@ 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)
@ -167,13 +147,6 @@ 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;
@ -185,10 +158,6 @@ 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;
};
@ -316,11 +285,7 @@ RTC.prototype.cmos_port_read = function()
return c;
case CMOS_STATUS_D:
return 1 << 7; // CMOS battery charged
case CMOS_DIAG_STATUS:
dbg_log("cmos diagnostic status read", LOG_RTC);
return this.cmos_diag_status;
return 0;
case CMOS_CENTURY:
case CMOS_CENTURY2:
@ -345,11 +310,6 @@ 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();
@ -376,19 +336,11 @@ RTC.prototype.cmos_port_write = function(data_byte)
this.next_interrupt_alarm = +alarm_date;
}
if(this.cmos_b & 0x10)
{
dbg_log("update interrupt", LOG_RTC);
this.update_interrupt_time = Date.now();
}
if(this.cmos_b & 0x10) dbg_log("Unimplemented: updated interrupt", LOG_RTC);
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:
@ -399,7 +351,6 @@ 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;
};

View file

@ -1,658 +0,0 @@
// 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
}

View file

@ -1,5 +1,23 @@
#![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::*;
@ -9,8 +27,7 @@ use crate::cpu::misc_instr::{
push16, push32,
};
use crate::cpu::modrm::{resolve_modrm16, resolve_modrm32};
use crate::cpu::{apic, ioapic, pic};
use crate::dbg::dbg_trace;
use crate::cpu::pic;
use crate::gen;
use crate::jit;
use crate::jit::is_near_end_of_page;
@ -22,36 +39,11 @@ 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;
@ -216,10 +208,7 @@ 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;
@ -229,8 +218,6 @@ 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;
@ -243,10 +230,7 @@ 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 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 APIC_ADDRESS: i32 = 0xFEE00000u32 as i32;
pub const MXCSR_MASK: i32 = 0xffff;
pub const MXCSR_FZ: i32 = 1 << 15;
@ -286,8 +270,6 @@ 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;
@ -729,13 +711,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;
}
@ -911,19 +893,13 @@ pub unsafe fn call_interrupt_vector(
let ss_segment_descriptor =
match return_on_pagefault!(lookup_segment_selector(ss_segment_selector)) {
Ok((desc, _)) => desc,
Err(
SelectorNullOrInvalid::IsNull | SelectorNullOrInvalid::OutsideOfTableLimit,
) => {
Err(_) => {
panic!("Unimplemented: #TS handler");
},
};
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_dc(), "TODO: Handle direction bit");
dbg_assert!(new_esp as u32 <= 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() {
@ -1182,18 +1158,12 @@ 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}", cs_selector);
trigger_gp(cs_selector & !3);
dbg_log!("#gp invalid cs: {:x}", selector);
trigger_gp(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);
@ -1232,12 +1202,8 @@ pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool
},
};
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_dc(), "TODO: Handle direction bit");
dbg_assert!(new_esp as u32 <= ss_info.effective_limit());
dbg_assert!(!ss_info.is_system() && ss_info.is_writable());
if ss_selector.rpl() != cs_info.dpl()
@ -1259,18 +1225,16 @@ 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_cpl(
cs_info.dpl(),
return_on_pagefault!(writable_or_pagefault(
ss_info.base() + new_esp - stack_space,
stack_space
));
)); // , cs_info.dpl
}
else {
return_on_pagefault!(writable_or_pagefault_cpl(
cs_info.dpl(),
return_on_pagefault!(writable_or_pagefault(
ss_info.base() + (new_esp - stack_space & 0xFFFF),
stack_space
));
)); // , cs_info.dpl
}
let old_esp = read_reg32(ESP);
@ -1284,7 +1248,6 @@ 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);
@ -1306,6 +1269,7 @@ 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();
}
@ -1316,6 +1280,7 @@ 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();
}
@ -1334,6 +1299,7 @@ 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 {
@ -1349,8 +1315,6 @@ 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
@ -1380,11 +1344,10 @@ 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, "TODO: #gp invalid system type");
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] + ")");
}
}
else {
@ -1843,14 +1806,10 @@ 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 = other_cpl == 3;
let user = *cpl == 3;
translate_address(addr, true, user, false, true)?;
let end = addr + size - 1 & !0xFFF;
@ -2237,7 +2196,7 @@ pub unsafe fn trigger_fault_end_jit() {
#[allow(static_mut_refs)]
let (code, error_code) = jit_fault.take().unwrap();
if DEBUG {
if js::cpu_exception_hook(code) {
if cpu_exception_hook(code) {
return;
}
}
@ -2961,7 +2920,7 @@ pub unsafe fn cycle_internal() {
{
in_jit = true;
}
wasm::call_indirect1(
call_indirect1(
wasm_table_index as i32 + WASM_TABLE_OFFSET as i32,
initial_state,
);
@ -3146,11 +3105,11 @@ pub unsafe fn segment_prefix_op(seg: i32) {
pub unsafe fn main_loop() -> f64 {
profiler::stat_increment(stat::MAIN_LOOP);
let start = js::microtick();
let start = microtick();
if *in_hlt {
if *flags & FLAG_INTERRUPT != 0 {
let t = js::run_hardware_timers(*acpi_enabled, start);
let t = run_hardware_timers(*acpi_enabled, start);
handle_irqs();
if *in_hlt {
profiler::stat_increment(stat::MAIN_LOOP_IDLE);
@ -3166,8 +3125,8 @@ pub unsafe fn main_loop() -> f64 {
loop {
do_many_cycles_native();
let now = js::microtick();
let t = js::run_hardware_timers(*acpi_enabled, now);
let now = microtick();
let t = run_hardware_timers(*acpi_enabled, now);
handle_irqs();
if *in_hlt {
return t;
@ -3196,7 +3155,7 @@ pub unsafe fn trigger_de() {
dbg_log!("#de");
*instruction_pointer = *previous_ip;
if DEBUG {
if js::cpu_exception_hook(CPU_EXCEPTION_DE) {
if cpu_exception_hook(CPU_EXCEPTION_DE) {
return;
}
}
@ -3209,7 +3168,7 @@ pub unsafe fn trigger_ud() {
dbg_trace();
*instruction_pointer = *previous_ip;
if DEBUG {
if js::cpu_exception_hook(CPU_EXCEPTION_UD) {
if cpu_exception_hook(CPU_EXCEPTION_UD) {
return;
}
}
@ -3222,7 +3181,7 @@ pub unsafe fn trigger_nm() {
dbg_trace();
*instruction_pointer = *previous_ip;
if DEBUG {
if js::cpu_exception_hook(CPU_EXCEPTION_NM) {
if cpu_exception_hook(CPU_EXCEPTION_NM) {
return;
}
}
@ -3234,7 +3193,7 @@ pub unsafe fn trigger_gp(code: i32) {
dbg_log!("#gp");
*instruction_pointer = *previous_ip;
if DEBUG {
if js::cpu_exception_hook(CPU_EXCEPTION_GP) {
if cpu_exception_hook(CPU_EXCEPTION_GP) {
return;
}
}
@ -3326,7 +3285,7 @@ pub unsafe fn safe_read32s(addr: i32) -> OrPageFault<i32> {
}
pub unsafe fn safe_read_f32(addr: i32) -> OrPageFault<f32> {
Ok(f32::from_bits(i32::cast_unsigned(safe_read32s(addr)?)))
Ok(std::mem::transmute(safe_read32s(addr)?))
}
pub unsafe fn safe_read64s(addr: i32) -> OrPageFault<u64> {
@ -3580,11 +3539,10 @@ 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} value={:x}",
"SMC: bits={} eip={:x} writeaddr={:x}",
bitsize,
(*instruction_pointer & !0xFFF | eip_offset_in_page) as u32,
addr as u32,
value_low,
addr as u32
);
}
let crosses_page = (addr & 0xFFF) + bitsize / 8 > 0x1000;
@ -4104,7 +4062,7 @@ pub unsafe fn set_tsc(low: u32, high: u32) {
#[no_mangle]
pub unsafe fn read_tsc() -> u64 {
let value = (js::microtick() * TSC_RATE) as u64 - tsc_offset;
let value = (microtick() * TSC_RATE) as u64 - tsc_offset;
if !TSC_ENABLE_IMPRECISE_BROWSER_WORKAROUND {
return value;
@ -4289,7 +4247,7 @@ pub unsafe fn trigger_np(code: i32) {
dbg_log!("#np");
*instruction_pointer = *previous_ip;
if DEBUG {
if js::cpu_exception_hook(CPU_EXCEPTION_NP) {
if cpu_exception_hook(CPU_EXCEPTION_NP) {
return;
}
}
@ -4301,7 +4259,7 @@ pub unsafe fn trigger_ss(code: i32) {
dbg_log!("#ss");
*instruction_pointer = *previous_ip;
if DEBUG {
if js::cpu_exception_hook(CPU_EXCEPTION_SS) {
if cpu_exception_hook(CPU_EXCEPTION_SS) {
return;
}
}
@ -4312,14 +4270,17 @@ pub unsafe fn trigger_ss(code: i32) {
pub unsafe fn store_current_tsc() { *current_tsc = read_tsc(); }
#[no_mangle]
pub unsafe fn handle_irqs() {
pub unsafe fn handle_irqs() { handle_irqs_internal(&mut pic::get_pic()) }
pub unsafe fn handle_irqs_internal(pic: &mut pic::Pic) {
if *flags & FLAG_INTERRUPT != 0 {
if let Some(irq) = pic::pic_acknowledge_irq() {
if let Some(irq) = pic::pic_acknowledge_irq(pic) {
pic_call_irq(irq)
}
else if *acpi_enabled {
if let Some(irq) = apic::acknowledge_irq() {
pic_call_irq(irq)
let irq = apic_acknowledge_irq();
if irq >= 0 {
pic_call_irq(irq as u8)
}
}
}
@ -4328,68 +4289,12 @@ pub unsafe fn handle_irqs() {
unsafe fn pic_call_irq(interrupt_nr: u8) {
*previous_ip = *instruction_pointer; // XXX: What if called after instruction (port IO)
if *in_hlt {
js::stop_idling();
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) {

View file

@ -77,11 +77,10 @@ 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;
f64::from_bits((*fpu_st.offset(i as isize)).to_f64())
std::mem::transmute((*fpu_st.offset(i as isize)).to_f64())
}
#[no_mangle]
@ -497,7 +496,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(f64::to_bits(64.0)) {
if !intel_compatibility || d < F80::of_f64(std::mem::transmute(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);
@ -514,7 +513,7 @@ pub unsafe fn fpu_fprem(ieee: bool) {
*fpu_status_word &= !FPU_C2;
}
else {
let n = F80::of_f64(f64::to_bits(32.0));
let n = F80::of_f64(std::mem::transmute(32.0));
let fprem_quotient =
(if ieee { (st0 / st1).round() } else { (st0 / st1).trunc() } / (d - n).two_pow());
fpu_write_st(

View file

@ -1,7 +1,6 @@
#![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::*;
@ -576,6 +575,7 @@ 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,6 +995,7 @@ 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) {
@ -1004,6 +1005,7 @@ 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() {
@ -2055,11 +2057,13 @@ 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) {
@ -2163,12 +2167,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 {
js::run_hardware_timers(*acpi_enabled, js::microtick());
run_hardware_timers(*acpi_enabled, microtick());
handle_irqs();
}
else {
// execution can never resume (until NMIs are supported)
js::cpu_event_halt();
cpu_event_halt();
}
}
#[no_mangle]
@ -2387,6 +2391,7 @@ 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
@ -2409,6 +2414,7 @@ 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))));
@ -2449,6 +2455,7 @@ 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) {
@ -2477,6 +2484,7 @@ 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))));

View file

@ -1,5 +1,9 @@
#![allow(non_snake_case)]
extern "C" {
fn get_rand_int() -> i32;
}
unsafe fn undefined_instruction() {
dbg_assert!(false, "Undefined instructions");
trigger_ud()
@ -271,11 +275,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 & 0xFFFF));
return_on_pagefault!(safe_write16(addr, *cr));
}
#[no_mangle]
pub unsafe fn instr32_0F01_4_mem(addr: i32) {
return_on_pagefault!(safe_write16(addr, *cr & 0xFFFF));
return_on_pagefault!(safe_write16(addr, *cr));
}
#[no_mangle]
@ -1203,14 +1207,13 @@ pub unsafe fn instr_0F30() {
);
let address = low & !(IA32_APIC_BASE_BSP | IA32_APIC_BASE_EXTD | IA32_APIC_BASE_EN);
dbg_assert!(
address == APIC_MEM_ADDRESS as i32,
address == APIC_ADDRESS,
"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/
@ -1223,8 +1226,6 @@ 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
@ -1282,7 +1283,7 @@ pub unsafe fn instr_0F32() {
IA32_PLATFORM_ID => {},
IA32_APIC_BASE => {
if *acpi_enabled {
low = APIC_MEM_ADDRESS as i32;
low = APIC_ADDRESS;
if *apic_enabled {
low |= IA32_APIC_BASE_EN
}
@ -1295,11 +1296,13 @@ 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_PERFEVTSEL0 | IA32_PERFEVTSEL1 => {}, // linux/9legacy
IA32_PMC0 | IA32_PMC1 => {}, // linux
IA32_MCG_CAP => {
// netbsd
},
IA32_PAT => {},
MSR_PKG_C2_RESIDENCY => {},
IA32_SPEC_CTRL => {}, // linux 5.19
@ -2186,14 +2189,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] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 4] = i32::to_le_bytes(source);
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 4] = std::mem::transmute(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, u64::from_le_bytes(result));
write_mmx_reg64(r, std::mem::transmute(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0F60_reg(r1: i32, r2: i32) { instr_0F60(read_mmx32s(r1), r2); }
@ -2204,7 +2207,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] = u64::to_le_bytes(read_xmm64s(r));
let destination: [u8; 8] = std::mem::transmute(read_xmm64s(r));
let mut result = reg128 { i8: [0; 16] };
for i in 0..8 {
result.u8[2 * i + 0] = destination[i];
@ -2289,7 +2292,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, u64::from_le_bytes(result));
write_mmx_reg64(r, std::mem::transmute(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0F63_reg(r1: i32, r2: i32) { instr_0F63(read_mmx64s(r1), r2); }
@ -2321,7 +2324,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, u64::from_le_bytes(result));
write_mmx_reg64(r, std::mem::transmute(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0F64_reg(r1: i32, r2: i32) { instr_0F64(read_mmx64s(r1), r2); }
@ -2417,7 +2420,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, u64::from_le_bytes(result));
write_mmx_reg64(r, std::mem::transmute(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0F67_reg(r1: i32, r2: i32) { instr_0F67(read_mmx64s(r1), r2); }
@ -2443,14 +2446,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] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(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, u64::from_le_bytes(result));
write_mmx_reg64(r, std::mem::transmute(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0F68_reg(r1: i32, r2: i32) { instr_0F68(read_mmx64s(r1), r2); }
@ -2865,13 +2868,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] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(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, u64::from_le_bytes(result));
write_mmx_reg64(r, std::mem::transmute(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0F74_reg(r1: i32, r2: i32) { instr_0F74(read_mmx64s(r1), r2); }
@ -3238,7 +3241,8 @@ pub unsafe fn instr_0FA2() {
},
1 => {
eax = 3 | 7 << 4 | 6 << 8; // pentium3
// pentium
eax = 3 | 6 << 4 | 15 << 8;
ebx = 1 << 16 | 8 << 8; // cpu count, clflush size
ecx = 1 << 0 | 1 << 23 | 1 << 30; // sse3, popcnt, rdrand
let vme = 0 << 1;
@ -3816,7 +3820,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 = f32::from_bits(i32::cast_unsigned(source));
let source: f32 = std::mem::transmute(source);
let result = if sse_comparison(imm8, destination as f64, source as f64) { -1 } else { 0 };
write_xmm32(r, result);
}
@ -3940,7 +3944,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 = js::get_rand_int();
let rand = get_rand_int();
write_reg16(r, rand);
*flags &= !FLAGS_ALL;
*flags |= 1;
@ -3949,7 +3953,7 @@ pub unsafe fn instr16_0FC7_6_reg(r: i32) {
#[no_mangle]
pub unsafe fn instr32_0FC7_6_reg(r: i32) {
// rdrand
let rand = js::get_rand_int();
let rand = get_rand_int();
write_reg32(r, rand);
*flags &= !FLAGS_ALL;
*flags |= 1;
@ -4127,7 +4131,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] = u64::to_le_bytes(read_mmx64s(r1));
let x: [u8; 8] = std::mem::transmute(read_mmx64s(r1));
let mut result = 0;
for i in 0..8 {
result |= x[i] as i32 >> 7 << i
@ -4151,13 +4155,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] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(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, u64::from_le_bytes(result));
write_mmx_reg64(r, std::mem::transmute(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0FD8_reg(r1: i32, r2: i32) { instr_0FD8(read_mmx64s(r1), r2); }
@ -4211,13 +4215,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] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(source);
let mut result = [0; 8];
for i in 0..8 {
result[i] = u8::min(source[i], destination[i])
}
write_mmx_reg64(r, u64::from_le_bytes(result));
write_mmx_reg64(r, std::mem::transmute(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0FDA_reg(r1: i32, r2: i32) { instr_0FDA(read_mmx64s(r1), r2); }
@ -4263,13 +4267,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] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(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, u64::from_le_bytes(result));
write_mmx_reg64(r, std::mem::transmute(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0FDC_reg(r1: i32, r2: i32) { instr_0FDC(read_mmx64s(r1), r2); }
@ -4325,13 +4329,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] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(source);
let mut result = [0; 8];
for i in 0..8 {
result[i] = u8::max(source[i], destination[i])
}
write_mmx_reg64(r, u64::from_le_bytes(result));
write_mmx_reg64(r, std::mem::transmute(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0FDE_reg(r1: i32, r2: i32) { instr_0FDE(read_mmx64s(r1), r2); }
@ -4377,13 +4381,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] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(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, u64::from_le_bytes(result));
write_mmx_reg64(r, std::mem::transmute(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0FE0_reg(r1: i32, r2: i32) { instr_0FE0(read_mmx64s(r1), r2); }
@ -4956,8 +4960,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] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(source);
let mut sum = 0;
for i in 0..8 {
sum += (destination[i] as i32 - source[i] as i32).abs() as u64;
@ -4991,8 +4995,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] = u64::to_le_bytes(read_mmx64s(r2));
let mask: [u8; 8] = u64::to_le_bytes(read_mmx64s(r1));
let source: [u8; 8] = std::mem::transmute(read_mmx64s(r2));
let mask: [u8; 8] = std::mem::transmute(read_mmx64s(r1));
match writable_or_pagefault(addr, 8) {
Ok(()) => *page_fault = false,
Err(()) => {
@ -5044,13 +5048,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] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(source);
let mut result = [0; 8];
for i in 0..8 {
result[i] = destination[i] - source[i];
}
write_mmx_reg64(r, u64::from_le_bytes(result));
write_mmx_reg64(r, std::mem::transmute(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0FF8_reg(r1: i32, r2: i32) { instr_0FF8(read_mmx64s(r1), r2); }
@ -5162,13 +5166,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] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(source);
let mut result = [0; 8];
for i in 0..8 {
result[i] = destination[i] + source[i];
}
write_mmx_reg64(r, u64::from_le_bytes(result));
write_mmx_reg64(r, std::mem::transmute(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0FFC_reg(r1: i32, r2: i32) { instr_0FFC(read_mmx64s(r1), r2); }

View file

@ -1,316 +0,0 @@
// 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
)
},
}
}

View file

@ -12,12 +12,8 @@ mod ext {
}
}
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::cpu::reg128;
use crate::cpu::global_pointers::memory_size;
use crate::cpu::ioapic;
use crate::cpu::vga;
use crate::jit;
use crate::page::Page;
@ -126,12 +122,6 @@ 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) }
}
@ -216,7 +206,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) {
@ -248,8 +238,6 @@ 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),
@ -286,14 +274,6 @@ 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)
}

View file

@ -1,4 +1,3 @@
pub mod apic;
pub mod arith;
pub mod call_indirect;
pub mod cpu;
@ -6,7 +5,6 @@ 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;

View file

@ -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)
const _: () = assert!(std::mem::offset_of!(Pic0, special_mask_mode) == 12);
#[repr(C)]
#[repr(C, packed)]
struct Pic0 {
irq_mask: u8,
@ -39,7 +39,7 @@ struct Pic0 {
special_mask_mode: bool,
}
struct Pic {
pub struct Pic {
master: Pic0,
slave: Pic0,
}
@ -91,16 +91,17 @@ static PIC: Mutex<Pic> = Mutex::new(Pic {
},
});
fn get_pic() -> MutexGuard<'static, Pic> { PIC.try_lock().unwrap() }
pub fn get_pic() -> MutexGuard<'static, Pic> { PIC.try_lock().unwrap() }
// 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 }
// 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)
impl Pic0 {
fn get_irq(&mut self) -> Option<u8> {
unsafe fn get_irq(&mut self) -> Option<u8> {
let enabled_irr = self.irr & self.irq_mask;
if enabled_irr == 0 {
@ -143,36 +144,44 @@ impl Pic0 {
Some(irq_number)
}
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 }
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 }
}
impl Pic {
fn set_irq(self: &mut Pic, i: u8) {
unsafe 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 {
if i < 8 {
self.check_irqs_master()
}
else {
self.check_irqs_slave()
}
}
}
fn clear_irq(self: &mut Pic, i: u8) {
unsafe 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 {
if i < 8 {
self.check_irqs_master()
}
else {
self.check_irqs_slave()
}
}
}
fn port0_write(&mut self, index: u8, v: u8) {
unsafe 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
@ -236,13 +245,16 @@ impl Pic {
dev.isr &= dev.isr - 1;
}
if index == 1 {
if index == 0 {
self.check_irqs_master()
}
else {
self.check_irqs_slave()
}
}
}
fn port1_write(&mut self, index: u8, v: u8) {
unsafe 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 {
@ -261,7 +273,10 @@ impl Pic {
dbg_log!("interrupt mask: {:x}", dev.irq_mask);
}
if index == 1 {
if index == 0 {
self.check_irqs_master()
}
else {
self.check_irqs_slave()
}
}
@ -279,7 +294,13 @@ impl Pic {
}
}
fn check_irqs_slave(&mut self) {
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) {
let is_set = self.slave.get_irq().is_some();
if is_set {
self.set_irq(2)
@ -291,8 +312,7 @@ impl Pic {
}
// called by the cpu
pub fn pic_acknowledge_irq() -> Option<u8> {
let mut pic = get_pic();
pub unsafe fn pic_acknowledge_irq(pic: &mut Pic) -> Option<u8> {
let irq = match pic.master.get_irq() {
Some(i) => i,
None => return None,
@ -323,14 +343,14 @@ pub fn pic_acknowledge_irq() -> Option<u8> {
dbg_assert!(pic.master.get_irq().is_none());
if irq == 2 {
acknowledge_irq_slave(&mut pic)
acknowledge_irq_slave(pic)
}
else {
Some(pic.master.irq_map | irq)
}
}
fn acknowledge_irq_slave(pic: &mut Pic) -> Option<u8> {
unsafe fn acknowledge_irq_slave(pic: &mut Pic) -> Option<u8> {
let irq = match pic.slave.get_irq() {
Some(i) => i,
None => return None,
@ -364,7 +384,9 @@ fn acknowledge_irq_slave(pic: &mut Pic) -> Option<u8> {
Some(pic.slave.irq_map | irq)
}
pub fn set_irq(i: u8) {
// called by javascript
#[no_mangle]
pub unsafe fn pic_set_irq(i: u8) {
dbg_assert!(i < 16);
if PIC_LOG_VERBOSE {
@ -374,7 +396,9 @@ pub fn set_irq(i: u8) {
get_pic().set_irq(i)
}
pub fn clear_irq(i: u8) {
// called by javascript
#[no_mangle]
pub unsafe fn pic_clear_irq(i: u8) {
dbg_assert!(i < 16);
if PIC_LOG_VERBOSE {
@ -384,19 +408,36 @@ pub fn clear_irq(i: u8) {
get_pic().clear_irq(i)
}
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 port20_read() -> u32 { get_pic().master.port0_read() }
#[no_mangle]
pub unsafe fn port21_read() -> u32 { get_pic().master.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 portA0_read() -> u32 { get_pic().slave.port0_read() }
#[no_mangle]
pub unsafe fn portA1_read() -> u32 { get_pic().slave.port1_read() }
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 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 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 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 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 }
#[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 }

View file

@ -9,6 +9,7 @@
// 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,
@ -299,6 +300,7 @@ 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,

View file

@ -1,43 +1,3 @@
#[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) => {
@ -70,12 +30,14 @@ macro_rules! dbg_assert {
macro_rules! console_log {
($fmt:expr) => {
{
crate::dbg::console_log_to_js_console($fmt);
use crate::util::{ console_log_to_js_console };
console_log_to_js_console($fmt);
}
};
($fmt:expr, $($arg:tt)*) => {
{
crate::dbg::console_log_to_js_console(format!($fmt, $($arg)*));
use crate::util::{ console_log_to_js_console };
console_log_to_js_console(format!($fmt, $($arg)*));
}
};
}
@ -85,14 +47,52 @@ macro_rules! console_log {
macro_rules! dbg_log {
($fmt:expr) => {
{
use crate::dbg::{ DEBUG, log_to_js_console };
use crate::util::{ DEBUG, log_to_js_console };
if DEBUG { log_to_js_console($fmt); }
}
};
($fmt:expr, $($arg:tt)*) => {
{
use crate::dbg::{ DEBUG, log_to_js_console };
use crate::util::{ 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();
}
}
}};
}

View file

@ -20,6 +20,7 @@ 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)]
@ -485,7 +486,7 @@ fn jit_find_basic_blocks(
let mut pages: HashSet<Page> = HashSet::new();
let mut page_blacklist = HashSet::new();
// 16-bit doesn't work correctly, most likely due to instruction pointer wrap-around
// 16-bit doesn't not work correctly, most likely due to instruction pointer wrap-around
let max_pages = if cpu.state_flags.is_32() { unsafe { MAX_PAGES } } else { 1 };
for virt_addr in entry_points {
@ -539,22 +540,13 @@ 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);
@ -566,22 +558,26 @@ 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),
"should be handled above"
"TODO: Handle STI instruction near end of page"
);
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(&current_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),
@ -634,6 +630,9 @@ 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 {
@ -671,6 +670,8 @@ fn jit_find_basic_blocks(
jump_offset: offset,
jump_offset_is_32: is_32,
};
current_block.last_instruction_addr = addr_before_instruction;
current_block.end_addr = current_address;
break;
},
@ -690,25 +691,24 @@ 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,30 +1230,28 @@ fn jit_generate_module(
let mut index_for_addr = HashMap::new();
for (i, &addr) in entry_blocks.iter().enumerate() {
dbg_assert!(i < 0x10000);
index_for_addr.insert(addr, i as u16);
index_for_addr.insert(addr, i as i32);
}
for b in basic_blocks.values() {
if !index_for_addr.contains_key(&b.addr) {
let i = index_for_addr.len();
dbg_assert!(i < 0x10000);
index_for_addr.insert(b.addr, i as u16);
index_for_addr.insert(b.addr, i as i32);
}
}
let mut label_for_addr: HashMap<u32, (Label, Option<u16>)> = HashMap::new();
let mut label_for_addr: HashMap<u32, (Label, Option<i32>)> = HashMap::new();
enum Work {
WasmStructure(WasmStructure),
BlockEnd {
label: Label,
targets: Vec<u32>,
olds: HashMap<u32, (Label, Option<u16>)>,
olds: HashMap<u32, (Label, Option<i32>)>,
},
LoopEnd {
label: Label,
entries: Vec<u32>,
olds: HashMap<u32, (Label, Option<u16>)>,
olds: HashMap<u32, (Label, Option<i32>)>,
},
}
let mut work: VecDeque<Work> = structure
@ -1425,10 +1423,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.into());
ctx.builder.const_i32(target_index);
ctx.builder.call_fn1("debug_set_dispatcher_target");
}
ctx.builder.const_i32(target_index.into());
ctx.builder.const_i32(target_index);
ctx.builder.set_local(target_block);
codegen::gen_profiler_stat_increment(
ctx.builder,
@ -1446,10 +1444,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.into());
ctx.builder.const_i32(target_index);
ctx.builder.call_fn1("debug_set_dispatcher_target");
}
ctx.builder.const_i32(target_index.into());
ctx.builder.const_i32(target_index);
ctx.builder.set_local(target_block);
codegen::gen_profiler_stat_increment(
ctx.builder,
@ -1573,10 +1571,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.into());
ctx.builder.const_i32(target_index);
ctx.builder.call_fn1("debug_set_dispatcher_target");
}
ctx.builder.const_i32(target_index.into());
ctx.builder.const_i32(target_index);
ctx.builder.set_local(target_block);
codegen::gen_profiler_stat_increment(
ctx.builder,
@ -1597,10 +1595,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.into());
ctx.builder.const_i32(target_index);
ctx.builder.call_fn1("debug_set_dispatcher_target");
}
ctx.builder.const_i32(target_index.into());
ctx.builder.const_i32(target_index);
ctx.builder.set_local(target_block);
}
@ -1753,11 +1751,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.into());
ctx.builder.const_i32(target_index_taken);
ctx.builder.set_local(target_block);
ctx.builder.else_();
ctx.builder.const_i32(target_index_not_taken.into());
ctx.builder.const_i32(target_index_not_taken);
ctx.builder.set_local(target_block);
ctx.builder.block_end();
@ -1770,9 +1768,9 @@ fn jit_generate_module(
codegen::gen_condition_fn(ctx, condition);
ctx.builder.if_i32();
ctx.builder.const_i32(target_index_taken.into());
ctx.builder.const_i32(target_index_taken);
ctx.builder.else_();
ctx.builder.const_i32(target_index_not_taken.into());
ctx.builder.const_i32(target_index_not_taken);
ctx.builder.block_end();
ctx.builder.set_local(target_block);
}
@ -1826,7 +1824,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.into());
ctx.builder.const_i32(index);
ctx.builder.eq_i32();
ctx.builder.br_if(label);
}
@ -1988,7 +1986,8 @@ 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
(block.addr, index)
let initial_state = index.safe_to_u16();
(block.addr, initial_state)
}));
for b in basic_blocks.values() {

View file

@ -1,3 +1,5 @@
#![allow(const_item_mutation)]
#[macro_use]
mod dbg;
@ -25,5 +27,6 @@ mod prefix;
mod regs;
mod softfloat;
mod state_flags;
mod util;
mod wasmgen;
mod zstd;

View file

@ -112,11 +112,11 @@ impl F80 {
unsafe { f64_to_extF80M(src, &mut x) };
x
}
fn of_f64x(src: f64) -> F80 { F80::of_f64(f64::to_bits(src)) }
fn of_f64x(src: f64) -> F80 { F80::of_f64(unsafe { std::mem::transmute(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 { f64::from_bits(self.to_f64()) }
fn to_f64x(&self) -> f64 { unsafe { std::mem::transmute(extF80M_to_f64(self)) } }
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) } }

98
src/rust/util.rs Normal file
View file

@ -0,0 +1,98 @@
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();
}
}
}

View file

@ -4,28 +4,9 @@ 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 {

View file

@ -1,18 +1,4 @@
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";
"use strict";
// Useful documentation, articles, and source codes for reference:
// ===============================================================
@ -39,54 +25,55 @@ import { ByteQueue, FloatQueue } from "./lib.js";
// -> https://www.virtualbox.org/svn/vbox/trunk/src/VBox/Devices/Audio/DevSB16.cpp
// -> https://github.com/mdaniel/virtualbox-org-svn-vbox-trunk/blob/master/src/VBox/Devices/Audio/DevSB16.cpp
const
var
// Used for drivers to identify device (DSP command 0xE3).
DSP_COPYRIGHT = "COPYRIGHT (C) CREATIVE TECHNOLOGY LTD, 1992.",
/** @const */ 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.
DSP_NO_COMMAND = 0,
/** @const */ DSP_NO_COMMAND = 0,
// Size (bytes) of the DSP write/read buffers
DSP_BUFSIZE = 64,
/** @const */ DSP_BUFSIZE = 64,
// Size (bytes) of the buffers containing floating point linear PCM audio.
DSP_DACSIZE = 65536,
/** @const */ DSP_DACSIZE = 65536,
// Size (bytes) of the buffer in which DMA transfers are temporarily
// stored before being processed.
SB_DMA_BUFSIZE = 65536,
/** @const */ SB_DMA_BUFSIZE = 65536,
// Number of samples to attempt to retrieve per transfer.
SB_DMA_BLOCK_SAMPLES = 1024,
/** @const */ SB_DMA_BLOCK_SAMPLES = 1024,
// Usable DMA channels.
SB_DMA0 = 0,
SB_DMA1 = 1,
SB_DMA3 = 3,
SB_DMA5 = 5,
SB_DMA6 = 6,
SB_DMA7 = 7,
/** @const */ SB_DMA0 = 0,
/** @const */ SB_DMA1 = 1,
/** @const */ SB_DMA3 = 3,
/** @const */ SB_DMA5 = 5,
/** @const */ SB_DMA6 = 6,
/** @const */ SB_DMA7 = 7,
// Default DMA channels.
SB_DMA_CHANNEL_8BIT = SB_DMA1,
SB_DMA_CHANNEL_16BIT = SB_DMA5,
/** @const */ SB_DMA_CHANNEL_8BIT = SB_DMA1,
/** @const */ SB_DMA_CHANNEL_16BIT = SB_DMA5,
// Usable IRQ channels.
SB_IRQ2 = 2,
SB_IRQ5 = 5,
SB_IRQ7 = 7,
SB_IRQ10 = 10,
/** @const */ SB_IRQ2 = 2,
/** @const */ SB_IRQ5 = 5,
/** @const */ SB_IRQ7 = 7,
/** @const */ SB_IRQ10 = 10,
// Default IRQ channel.
SB_IRQ = SB_IRQ5,
/** @const */ SB_IRQ = SB_IRQ5,
// Indices to the irq_triggered register.
SB_IRQ_8BIT = 0x1,
SB_IRQ_16BIT = 0x2,
SB_IRQ_MIDI = 0x1,
SB_IRQ_MPU = 0x4;
/** @const */ SB_IRQ_8BIT = 0x1,
/** @const */ SB_IRQ_16BIT = 0x2,
/** @const */ SB_IRQ_MIDI = 0x1,
/** @const */ SB_IRQ_MPU = 0x4;
// Probably less efficient, but it's more maintainable, instead
@ -105,7 +92,7 @@ var FM_HANDLERS = [];
* @param {CPU} cpu
* @param {BusConnector} bus
*/
export function SB16(cpu, bus)
function SB16(cpu, bus)
{
/** @const @type {CPU} */
this.cpu = cpu;
@ -163,7 +150,7 @@ export 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 SyncBuffer(this.dma_buffer);
this.dma_syncbuffer = new v86util.SyncBuffer(this.dma_buffer);
this.dma_waiting_transfer = false;
this.dma_paused = false;
this.sampling_rate = 22050;
@ -412,7 +399,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 SyncBuffer(this.dma_buffer);
this.dma_syncbuffer = new v86util.SyncBuffer(this.dma_buffer);
if(this.dma_paused)
{
@ -1539,7 +1526,7 @@ function between(start, end)
return a;
}
const SB_FM_OPERATORS_BY_OFFSET = new Uint8Array(32);
/** @const */ var 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;

View file

@ -1,14 +1,25 @@
import { h } from "./lib.js";
import { dbg_assert, dbg_log } from "./log.js";
import { CPU } from "./cpu.js";
"use strict";
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 */
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 ZSTD_MAGIC = 0xFD2FB528;
@ -20,7 +31,6 @@ function StateLoadError(msg)
StateLoadError.prototype = new Error;
const CONSTRUCTOR_TABLE = {
"Map": Map,
"Uint8Array": Uint8Array,
"Int8Array": Int8Array,
"Uint16Array": Uint16Array,
@ -44,17 +54,6 @@ 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);
@ -120,19 +119,14 @@ 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);
}
/* @param {CPU} cpu */
export function save_state(cpu)
CPU.prototype.save_state = function()
{
var saved_buffers = [];
var state = save_object(cpu, saved_buffers);
var state = save_object(this, saved_buffers);
var buffer_infos = [];
var total_buffer_size = 0;
@ -194,10 +188,9 @@ export function save_state(cpu)
dbg_log("State: Total buffers size " + (buffer_block.byteLength >> 10) + "k");
return result;
}
};
/* @param {CPU} cpu */
export function restore_state(cpu, state)
CPU.prototype.restore_state = function(state)
{
state = new Uint8Array(state);
@ -242,19 +235,19 @@ export function restore_state(cpu, state)
if(new Uint32Array(state.buffer, 0, 1)[0] === ZSTD_MAGIC)
{
const ctx = cpu.zstd_create_ctx(state.length);
const ctx = this.zstd_create_ctx(state.length);
new Uint8Array(cpu.wasm_memory.buffer, cpu.zstd_get_src_ptr(ctx) >>> 0, state.length).set(state);
new Uint8Array(this.wasm_memory.buffer, this.zstd_get_src_ptr(ctx), state.length).set(state);
let ptr = cpu.zstd_read(ctx, 16);
const header_block = new Uint8Array(cpu.wasm_memory.buffer, ptr >>> 0, 16);
let ptr = this.zstd_read(ctx, 16);
const header_block = new Uint8Array(this.wasm_memory.buffer, ptr, 16);
const info_block_len = read_state_header(header_block, false);
cpu.zstd_read_free(ptr, 16);
this.zstd_read_free(ptr, 16);
ptr = cpu.zstd_read(ctx, info_block_len);
const info_block_buffer = new Uint8Array(cpu.wasm_memory.buffer, ptr >>> 0, info_block_len);
ptr = this.zstd_read(ctx, info_block_len);
const info_block_buffer = new Uint8Array(this.wasm_memory.buffer, ptr, info_block_len);
const info_block_obj = read_info_block(info_block_buffer);
cpu.zstd_read_free(ptr, info_block_len);
this.zstd_read_free(ptr, info_block_len);
let state_object = info_block_obj["state"];
const buffer_infos = info_block_obj["buffer_infos"];
@ -269,8 +262,8 @@ export function restore_state(cpu, state)
if(buffer_info.length > CHUNK_SIZE)
{
const ptr = cpu.zstd_read(ctx, front_padding) >>> 0;
cpu.zstd_read_free(ptr, front_padding);
const ptr = this.zstd_read(ctx, front_padding);
this.zstd_read_free(ptr, front_padding);
const buffer = new Uint8Array(buffer_info.length);
buffers.push(buffer.buffer);
@ -282,28 +275,28 @@ export function restore_state(cpu, state)
dbg_assert(remaining >= 0);
const to_read = Math.min(remaining, CHUNK_SIZE);
const ptr = cpu.zstd_read(ctx, to_read);
buffer.set(new Uint8Array(cpu.wasm_memory.buffer, ptr >>> 0, to_read), have);
cpu.zstd_read_free(ptr, to_read);
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);
have += to_read;
}
}
else
{
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);
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);
}
position += front_padding + buffer_info.length;
}
state_object = restore_buffers(state_object, buffers);
cpu.set_state(state_object);
this.set_state(state_object);
cpu.zstd_free_ctx(ctx);
this.zstd_free_ctx(ctx);
}
else
{
@ -327,6 +320,6 @@ export function restore_state(cpu, state)
});
state_object = restore_buffers(state_object, buffers);
cpu.set_state(state_object);
this.set_state(state_object);
}
}
};

View file

@ -1,10 +1,4 @@
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";
"use strict";
/*
* Serial ports
@ -13,36 +7,35 @@ import { BusConnector } from "./bus.js";
* https://www.freebsd.org/doc/en/articles/serial-uart/
*/
const DLAB = 0x80;
/** @const */
var 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 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_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 */
// Modem control register
const UART_MCR_LOOPBACK = 0x10;
/** @const */var UART_IIR_MSI = 0x00; /* Modem status interrupt (Low priority) */
/** @const */var UART_IIR_NO_INT = 0x01;
/** @const */var UART_IIR_THRI = 0x02; /* Transmitter holding register empty */
/** @const */var UART_IIR_RDI = 0x04; /* Receiver data interrupt */
/** @const */var UART_IIR_RLSI = 0x06; /* Receiver line status interrupt (High p.) */
/** @const */var UART_IIR_CTI = 0x0c; /* Character timeout */
const 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
/** @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
// Modem status register
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
/** @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
// Delta bits
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
/** @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
/**
@ -51,7 +44,7 @@ const UART_MSR_DCTS = 0x0; // Delta CTS
* @param {number} port
* @param {BusConnector} bus
*/
export function UART(cpu, port, bus)
function UART(cpu, port, bus)
{
/** @const @type {BusConnector} */
this.bus = bus;
@ -402,11 +395,7 @@ UART.prototype.write_data = function(out_byte)
this.ThrowInterrupt(UART_IIR_THRI);
if(this.modem_control & UART_MCR_LOOPBACK) {
this.data_received(out_byte);
} else {
this.bus.send("serial" + this.com + "-output-byte", out_byte);
}
this.bus.send("serial" + this.com + "-output-byte", out_byte);
if(DEBUG)
{

View file

@ -1,13 +1,4 @@
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";
"use strict";
// Always 64k
const VGA_BANK_SIZE = 64 * 1024;
@ -43,6 +34,7 @@ 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([
@ -59,7 +51,7 @@ const VGA_HOST_MEMORY_SPACE_SIZE = Uint32Array.from([
* @param {ScreenAdapter|DummyScreenAdapter} screen
* @param {number} vga_memory_size
*/
export function VGAScreen(cpu, bus, screen, vga_memory_size)
function VGAScreen(cpu, bus, screen, vga_memory_size)
{
this.cpu = cpu;
@ -224,7 +216,7 @@ export function VGAScreen(cpu, bus, screen, vga_memory_size)
else
{
// required for pci code
this.vga_memory_size = round_up_to_next_power_of_2(this.vga_memory_size);
this.vga_memory_size = v86util.round_up_to_next_power_of_2(this.vga_memory_size);
}
dbg_log("effective vga memory size: " + this.vga_memory_size, LOG_VGA);
@ -364,7 +356,7 @@ export function VGAScreen(cpu, bus, screen, vga_memory_size)
const vga_offset = cpu.svga_allocate_memory(this.vga_memory_size) >>> 0;
this.svga_memory = view(Uint8Array, cpu.wasm_memory, vga_offset, this.vga_memory_size);
this.svga_memory = v86util.view(Uint8Array, cpu.wasm_memory, vga_offset, this.vga_memory_size);
this.diff_addr_min = this.vga_memory_size;
this.diff_addr_max = 0;
@ -574,7 +566,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 >>> 0), LOG_VGA);
dbg_log("vga read outside memory space: addr:" + h(addr), LOG_VGA);
return 0;
}
@ -648,7 +640,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 >>> 0) + ", value:" + h(value), LOG_VGA);
dbg_log("vga write outside memory space: addr:" + h(addr) + ", value:" + h(value), LOG_VGA);
return;
}
@ -1218,18 +1210,12 @@ 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);
@ -1245,7 +1231,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, bpp);
this.set_size_graphical(screen_width, screen_height, virtual_width, virtual_height, 8);
this.update_vertical_retrace();
this.update_layers();
@ -2212,7 +2198,7 @@ VGAScreen.prototype.port1CF_write = function(value)
}
else
{
dbg_log("SVGA: disabled", LOG_VGA);
dbg_log("SVGA: disabled");
}
if(this.svga_enabled && !was_enabled)
@ -2226,16 +2212,6 @@ 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;

View file

@ -1,10 +1,4 @@
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";
"use strict";
// http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html
@ -38,9 +32,9 @@ const VIRTIO_ISR_DEVICE_CFG = 2;
// Feature bits (bit positions).
export const VIRTIO_F_RING_INDIRECT_DESC = 28;
export const VIRTIO_F_RING_EVENT_IDX = 29;
export const VIRTIO_F_VERSION_1 = 32;
const VIRTIO_F_RING_INDIRECT_DESC = 28;
const VIRTIO_F_RING_EVENT_IDX = 29;
const VIRTIO_F_VERSION_1 = 32;
// Queue struct sizes.
@ -159,7 +153,7 @@ var VirtIO_Options;
* @param {CPU} cpu
* @param {VirtIO_Options} options
*/
export function VirtIO(cpu, options)
function VirtIO(cpu, options)
{
const io = cpu.io;
@ -230,7 +224,7 @@ export function VirtIO(cpu, options)
];
// Prevent sparse arrays by preallocating.
this.pci_space = this.pci_space.concat(Array(256 - this.pci_space.length).fill(0));
this.pci_space = this.pci_space.concat(v86util.zeros(256 - this.pci_space.length));
// Remaining PCI space is appended by capabilities further below.
this.pci_id = options.pci_id;
@ -486,7 +480,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 << (int_log2(data - 1) + 1);
data = 1 << (v86util.int_log2(data - 1) + 1);
}
if(data > this.queue_selected.size_supported)
{
@ -733,7 +727,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 << (int_log2(bar_size - 1) + 1);
bar_size = bar_size < 16 ? 16 : 1 << (v86util.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");
@ -825,13 +819,6 @@ 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:
@ -842,7 +829,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, shim_read32_on_16);
this.cpu.io.register_read(port, this, shim_read8_on_16, read);
this.cpu.io.register_read(port + 1, this, shim_read8_on_16);
this.cpu.io.register_write(port, this, undefined, write);
break;
@ -1084,7 +1071,6 @@ 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;
};
@ -1102,7 +1088,6 @@ 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()
@ -1141,10 +1126,6 @@ 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;
};

View file

@ -1,14 +1,7 @@
"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;
@ -32,7 +25,7 @@ const STAT_NAMES = [
* @param {CPU} cpu
* @param {BusConnector} bus
*/
export function VirtioBalloon(cpu, bus)
function VirtioBalloon(cpu, bus)
{
/** @const @type {BusConnector} */
this.bus = bus;

View file

@ -1,10 +1,4 @@
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";
"use strict";
// https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-2900003
@ -26,7 +20,7 @@ const VIRTIO_CONSOLE_F_EMERG_WRITE = 2;
*
* @param {CPU} cpu
*/
export function VirtioConsole(cpu, bus)
function VirtioConsole(cpu, bus)
{
/** @const @type {BusConnector} */
this.bus = bus;
@ -107,9 +101,11 @@ export function VirtioConsole(cpu, bus)
{
dbg_assert(false, "VirtioConsole Notified for wrong queue: " + queue_id +
" (expected queue_id of 2)");
return;
}
const queue = this.virtio.queues[queue_id];
// Full buffer looks like an empty buffer so prevent it from filling
while(queue.count_requests() > queue.size - 2) queue.pop_request();
},
(queue_id) =>
{

View file

@ -1,15 +1,7 @@
"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;
@ -26,9 +18,8 @@ const VIRTIO_NET_CTRL_MAC_ADDR_SET = 1;
* @param {CPU} cpu
* @param {BusConnector} bus
* @param {Boolean} preserve_mac_from_state_image
* @param {number} mtu
*/
export function VirtioNet(cpu, bus, preserve_mac_from_state_image, mtu = MTU_DEFAULT)
function VirtioNet(cpu, bus, preserve_mac_from_state_image)
{
/** @const @type {BusConnector} */
this.bus = bus;
@ -180,7 +171,7 @@ export function VirtioNet(cpu, bus, preserve_mac_from_state_image, mtu = MTU_DEF
{
bytes: 2,
name: "mtu",
read: () => mtu,
read: () => 1500,
write: data => {},
}
])

View file

@ -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 (source files with v86-debug.wasm)
debug build (libv86-debug.js, 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)

View file

@ -1,87 +0,0 @@
#!/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();
}
});

View file

@ -1,13 +1,13 @@
#!/usr/bin/env node
import url from "node:url";
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
"use strict";
// 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 { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js");
const fs = require("fs");
var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86;
process.on("unhandledRejection", exn => { throw exn; });

View file

@ -1,24 +1,23 @@
#!/usr/bin/env node
import { setTimeout as pause } from "timers/promises";
import url from "node:url";
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
"use strict";
const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD;
const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js");
const pause = require("timers/promises").setTimeout;
const fs = require("fs");
var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86;
process.on("unhandledRejection", exn => { throw exn; });
const emulator = new V86({
bios: { url: __dirname + "/../../bios/seabios.bin" },
vga_bios: { url: __dirname + "/../../bios/vgabios.bin" },
hda: { url: __dirname + "/../../images/msdos622.img" },
hda: { url: __dirname + "/../../images/msdos.img" },
network_relay_url: "<UNUSED>",
autostart: true,
memory_size: 32 * 1024 * 1024,
filesystem: {},
log_level: 0,
log_level: 3,
disable_jit: +process.env.DISABLE_JIT,
});
@ -36,16 +35,15 @@ setTimeout(async () =>
await emulator.wait_until_vga_screen_contains("C:\\> ");
console.log("Got C:\\>");
await pause(1000);
emulator.keyboard_send_text("dir D:\n");
emulator.keyboard_send_text("dir A:\n");
await emulator.wait_until_vga_screen_contains("Abort, Retry, Fail?");
console.log("Got Abort, Retry, Fail?");
await pause(1000);
emulator.keyboard_send_text("a");
emulator.set_cdrom({ url: __dirname + "/../../images/linux4.iso" });
await pause(1000);
emulator.keyboard_send_text("dir D:\n");
await emulator.wait_until_vga_screen_contains("BOOT <DIR>");
console.log("Got BOOT");
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.destroy();
clearTimeout(timeout);
//clearInterval(interval);

Some files were not shown because too many files have changed in this diff Show more