Compare commits

..

301 commits
0.5 ... master

Author SHA1 Message Date
Fabian
11cf7dd926 fix: some properties missing from state images 2025-12-26 16:35:29 -06:00
Rob Blanckaert
f053c4af7b Allow Maps in state object 2025-12-26 10:50:53 -06:00
Fabian
880fa18e35 fix: Handle STI instruction near end of page 2025-12-25 15:12:52 -06:00
Maël Nison
946819902b Reverts some changes 2025-12-25 15:12:52 -06:00
Maël Nison
9b2e65166f Change enums to constant enums in v86.d.ts 2025-12-25 15:12:52 -06:00
Maël Nison
40f504e933 Add type definitions and update files in package.json 2025-12-25 15:12:52 -06:00
Fabian
dcc1700cef add bootOs (#1434) 2025-12-25 15:12:52 -06:00
Rob Blanckaert
0898f64b28 Fake networking socket handling cleanup. 2025-12-19 09:37:00 -06:00
SuperMaxusa
b6c940d0d4 make libv86-debug.mjs readable, add missing ls 2025-11-30 09:48:38 -06:00
SuperMaxusa
524d67326e mention RootlessRelay in docs 2025-11-30 09:46:43 -06:00
Rob Blanckaert
f032585090 Correct sign bit of pointers used with zstd contexts 2025-11-27 17:00:41 -06:00
Дмитрий Ценеков
b91ad42fe2 Add --zstd flag and use 19 lvl compression 2025-11-26 08:08:39 -06:00
Дмитрий Ценеков
bd493da2dc Add zstd compression support at tools 2025-11-26 08:08:39 -06:00
Дмитрий Ценеков
ae35964179 Remove space after "if" 2025-11-26 08:08:39 -06:00
Дмитрий Ценеков
71584c833c Add space after "if" 2025-11-26 08:08:39 -06:00
Дмитрий Ценеков
404c1e4ff0 Fixed a bug with parameters 2025-11-26 08:08:39 -06:00
Дмитрий Ценеков
56b8c85e3f Adding zstd_decompress to the filestore 2025-11-26 08:08:39 -06:00
ading2210
c380cabc4c fix eslint errors 2025-11-26 07:56:22 -06:00
ading2210
97cd3069ac cleanup code style 2025-11-26 07:56:22 -06:00
ading2210
081b1eaafd fix wisp adapter crash when a stream starts buffering 2025-11-26 07:56:22 -06:00
ading2210
1a1afab08d implement tcp mss option and set it based on the mtu 2025-11-26 07:56:22 -06:00
ading2210
0beafeee07 add mtu and nic type options to the interface 2025-11-26 07:56:22 -06:00
ading2210
d093088616 add mtu options to network code 2025-11-26 07:56:22 -06:00
Rob Blanckaert
ec90cede42 Nuke some old stuff from virtio console thats fixed in the overall virtio impl. now 2025-11-26 06:59:15 -06:00
Rob Blanckaert
c004ebb3ab Use global this for scheduler access
This makes libv86 work inside shared workers again.
2025-11-26 06:58:51 -06:00
FunnyCorgi
db22e06746 Fix HDB buffer creation using raw instead of calculated result size 2025-10-31 00:33:30 -06:00
FunnyCorgi
5a48c7e867 Fix HDA and HDB always being set to 1 MB
This is towards #1427.
2025-10-31 00:33:30 -06:00
toast
e65e4f441e add loopback to UART 2025-10-30 11:43:45 -06:00
SuperMaxusa
54ec8e8daa docs: add vmdisp9x instruction in windows-9x.md 2025-10-30 11:41:10 -06:00
SuperMaxusa
c70dafd411 docs: fix sb16 driver url 2025-10-30 11:41:10 -06:00
SuperMaxusa
81eb6a1d41 docs: use tables in windows-9x.md 2025-10-30 11:41:10 -06:00
SuperMaxusa
a4c9943c3d docs: add the "Troubleshooting" section for windows-9x.md 2025-10-30 11:41:10 -06:00
SuperMaxusa
777b8c397c
fix iso file extension detection (#1426) 2025-10-30 18:36:00 +01:00
Christian Schnell
667313401d
Fix UTF-8 encoding in SerialAdapterXtermJS (#1439)
* avoid possibly creating unused SerialAdapter instance

* use UTF-8 encoding when exchanging strings between XTerm and serial port

Use TextEncoder.encode() instead of String.charCodeAt() in Terminal.onData(data_str) to encode user input strings (keyboard or paste).
2025-10-30 18:35:05 +01:00
Fabian
9735a0eed8 fix fetching wasm progress message 2025-09-20 15:06:02 -03:00
Fabian
ac5e657e5a fix minor error in debug.html 2025-09-20 14:40:00 -03:00
Fabian
71f4a416ab fix warning 2025-09-20 11:37:40 -03:00
Fabian
ccb5e1ce5d disable use of scheduling api for now
see https://github.com/copy/v86/issues/789#issuecomment-3314001637
2025-09-20 11:22:34 -03:00
Fabian
772f0a1faa fix the mobile css 2025-09-19 15:51:12 -03:00
Fabian
0669f7a477 make tests a more reliable 2025-09-13 19:25:51 -03:00
Fabian
4e15afcd77 update docs with proper qemu invocation 2025-09-13 19:25:51 -03:00
Fabian
6e3afa67ab remove trailing spaces 2025-09-13 19:25:51 -03:00
Fabian
f6083ac6b0 upgrade haiku to beta5
fixes rare crash

thanks @SuperMaxusa
2025-09-13 19:25:51 -03:00
Fabian
be7a8aa874 apic: reduce verbosity of logging 2025-09-13 19:25:51 -03:00
Fabian
7c4cbd0b04 fix: correct field indices for restoring old state images with apic 2025-09-13 19:25:51 -03:00
Fabian
2f65f9d6d1 use new task scheduling api if available
about 2% faster than the worker-based yield function
2025-09-13 19:25:51 -03:00
Fabian
b0d20cee00 add BSD/OS, Mojo OS, XENUS, Vanadium OS, Asuro and PrettyOS
thanks @SuperMaxusa
2025-09-13 19:25:51 -03:00
SuperMaxusa
464cddcbed
fix filters, add "Reset filters" button (#1405) 2025-09-14 00:25:29 +02:00
Christian Schnell
4251ac1a9e bugfix: fixed string concatenation method in DummyScreenAdapter.get_text_row()
previous method created undefined strings, switched to working method
2025-09-08 15:53:28 -06:00
Christian Schnell
18ff2d85a1 concatenate strings without using arrays 2025-09-08 15:53:28 -06:00
Christian Schnell
07270db5f4 inlined and removed function to_unicode() from lib.js 2025-09-08 15:53:28 -06:00
Christian Schnell
dfef28b7e1 changed type of charmap from Array<number> to string 2025-09-08 15:53:28 -06:00
Christian Schnell
0ee4138226 changed mapping of CP437[0] to whitespace 2025-09-08 15:53:28 -06:00
Christian Schnell
7bd35defe9 allow falsey argument to get_charmap() 2025-09-08 15:53:28 -06:00
Christian Schnell
b7ad6ee562 codepage support
Support for encoding 8-bit text into Unicode strings exists in ScreenAdapter but is missing in DummyScreenAdapter.

- Moved and rewrote the CP437-related code in src/browser/screen.js to src/lib.js.
- Added support for codepages other than CP437.
- Made the active codepage configurable in the V86 constructor in options.screen.encoding.

src/lib.js
- Added public function get_charmap(encoding)
  Returns the charmap of type Array<number> for given encoding string.
  Supported encodings: "cp437", "cp858" and "ascii", more can be added easily from
  https://github.com/chschnell/v86-i18n/blob/main/codepage-tables/codepage_tables.js
- Added public function to_unicode(text_8bit, charmap)
  Returns the Unicode string representation of given 8-bit text and charmap.
  Supported types for text_8bit are Array<number>, Uint8Array and number.

src/browser/dummy_screen.js
- Added options argument to DummyScreenAdapter constructor (same as for ScreenAdapter).
- Uses options.encoding argument for user-defined encoding, defaults to "cp437".
- DummyScreenAdapter.get_text_row() now uses to_unicode() for string encoding.

src/browser/screen.js
- Uses options.encoding argument for user-defined encoding, defaults to "cp437".
- DummyScreenAdapter.get_text_row() now uses to_unicode() for string encoding.
- Removed CP437 table, no longer needed.

src/browser/starter.js
- Added options argument to DummyScreenAdapter constructor call.
- Removed unneeded assignment to settings.screen_options, this gets overwritten
  a couple of dozen lines below.
2025-09-08 15:53:28 -06:00
SuperMaxusa
95ca55d943 convert count to an unsigned value 2025-09-05 12:08:37 -06:00
SuperMaxusa
3830424c4e ide: ATAPI command READ(12) 2025-09-05 12:08:37 -06:00
Fabian
88d4ba8fe5 fix file locking test 2025-09-05 14:37:17 -03:00
SuperMaxusa
865725bbac rtc: set CMOS battery charge status, add diagnostic status register 2025-09-04 17:36:02 -06:00
Fabian
21a6318a13 improve apic timer 2025-08-25 15:03:49 -06:00
Fabian
c1785b724f change text in html 2025-08-25 15:03:49 -06:00
Fabian
c78885fd84 update paths 2025-08-25 15:03:49 -06:00
Fabian
1301eb1196 keyboard: add MetaLeft/MetaRight key 2025-08-25 15:03:49 -06:00
Fabian
3b0325d899 reactos state image test 2025-08-25 15:03:49 -06:00
Fabian
403f7168b2 add state tests 2025-08-25 15:03:49 -06:00
Fabian
2001db0db3 add delay parameter to V86.prototype.keyboard_send_{scancodes,keys,text} 2025-08-25 15:03:49 -06:00
Fabian
0864656091 add note to BeOS 2025-08-25 15:03:49 -06:00
Fabian
a480e88594 don't fail hard on some odd io port accesses from archhurd 2025-08-25 15:03:49 -06:00
Fabian
73945e4574 optimise apic register_get_highest_bit 2025-08-25 15:03:49 -06:00
Fabian
da3547a96f don't hard fail on some MSR writes 2025-08-25 15:03:49 -06:00
Fabian
7a2df17dae pic ain't unsafe 2025-08-25 15:03:49 -06:00
Fabian
dfbe60926b option to run all tests with acpi forced on 2025-08-25 15:03:49 -06:00
Fabian
8f66766f49 gitignore bench-results 2025-08-25 15:03:49 -06:00
Fabian
fe50e6ff6f remove allow(const_item_mutation) 2025-08-25 15:03:49 -06:00
Fabian
c6c2017ba8 port apic & ioapic to rust
- handle mmap access and port io directly in rust
- call handle_irqs after port/memory writes, rather than from the
  interrupt hw directly; this makes it more obvious that handle_irqs is
  dangerous as it can change control flow

state images produced by this version are not backwards-compatible (older stage images will still be working)
2025-08-25 15:03:49 -06:00
Fabian
244a877989 minor: move env variables to top of file 2025-08-25 15:03:49 -06:00
Fabian
7c6da9d8f2 disable mouse by default in buildroot profile 2025-08-25 15:03:49 -06:00
Fabian
9a2e81b35b fix: onclick handler errors if there is no audio context 2025-08-25 15:03:49 -06:00
Fabian
525fa58719 validate empty disk size 2025-08-25 15:03:49 -06:00
Fabian
2f040c0502 minor: move mark_dirty into memcpy_into_svga_lfb 2025-08-25 15:03:49 -06:00
Fabian
ab4440d28f ide: don't assert on vendor-specific command (BSD/OS 4) 2025-08-25 15:03:49 -06:00
Fabian
c2dc88dd15 improve assertion for esp < limit check 2025-08-25 15:03:49 -06:00
Fabian
af483a9036 add some stub MSRs 2025-08-25 15:03:49 -06:00
Fabian
d074a7f8a4 make #ud non-panic 2025-08-25 15:03:49 -06:00
Fabian
4aa1c16995 vga: after turning off vbe, switch back to the current mode, rather than staying in graphical mode
fixes gnu hurd, which leaves vbe mode without writing to attribute mode control
2025-08-25 15:03:49 -06:00
Fabian
46fbe8fa40 cpuid: downgrade model to pentium3
this fixes a page fault in BSD/OS 3.0 (#1369)
2025-08-25 15:03:49 -06:00
Fabian
80d77f23a8 add tests for BSD/OS 2025-08-25 15:03:49 -06:00
Fabian
76595e4612 Add Arch Hurd 2025-08-25 15:03:49 -06:00
Fabian
2d3965b53f add manifest for installing on phones (#1372) 2025-08-25 15:03:49 -06:00
Fabian
990fecb59f (breaking change) remove emulator.mount_fs: planning future simplifications for the filesystem 2025-08-25 15:03:49 -06:00
Fabian
afb30c608e add tools/split-image.py 2025-08-25 15:03:49 -06:00
Fabian
007b0d9e71 fs: remove AddEvent/HandleEvent, make await OpenInode instead 2025-08-25 15:03:49 -06:00
Fabian
5aeac32af3 remove invalid assertion 2025-08-25 15:03:48 -06:00
Fabian
13d4dd0ca9 merge util.rs into dbg.rs 2025-08-25 15:03:48 -06:00
Fabian
0cc8f0221e use u16 as type for wasm table indices, move safe_to_{u8,u16} around 2025-08-25 15:03:48 -06:00
Fabian
80a3ab7b3d remove custom dbg_assert macro
the default panic printing already works fine for this
2025-08-25 15:03:48 -06:00
Fabian
b35621e9aa minor: use read_sized_string_from_mem 2025-08-25 15:03:48 -06:00
Fabian
6d2ef6ca41 fix far jumps
- gp error code should use gate cs
- missing gp on system cs
- arg copying should use target cpl

fixes hurd
fixes BSD/OS (#1369)
2025-08-25 15:03:48 -06:00
Fabian
732390abb9 help debugging 2025-08-25 15:03:48 -06:00
Fabian
a1f160cf32 log negative addresses correctly 2025-08-25 15:03:48 -06:00
Fabian
fd80637976 make debug_dump_code more ergonomic 2025-08-25 15:03:48 -06:00
Fabian
c7a3b8b050 cursed css things to turn table rows into links 2025-08-25 15:03:48 -06:00
Fabian
e27e89877c update filesystem docs 2025-08-25 15:02:43 -06:00
Fabian
318003f117 clean up 2025-08-25 15:02:43 -06:00
Jeff Lindsay
88aff1091f updating the filesystem docs with all the ways it can be used 2025-08-25 15:02:43 -06:00
Jeff Lindsay
1acdbbe785 update implementation based on feedback and added a pre-made websocket proxy 2025-08-25 15:02:43 -06:00
Jeff Lindsay
f020b016f5 add support for custom 9p request handler 2025-08-25 15:02:43 -06:00
SuperMaxusa
137ab1f9ad refactor: extract code into functions 2025-08-23 10:57:57 -06:00
SuperMaxusa
a25e7181d5 forEach -> for, fit to viewport when the virtual keyboard is displayed 2025-08-23 10:57:57 -06:00
SuperMaxusa
519480d4cd add theatre mode 2025-08-23 10:57:57 -06:00
Christian Schnell
92ba111d92 refactored V86.wait_until_vga_screen_contains()
- reduced clutter and made control flow more comprehensible
- moved initial delay of 100ms to expect(), needed for keyboard buffer "cooldown" in between commands
2025-08-23 10:14:32 -06:00
Christian Schnell
47fb4e1dd2 final cleanup and alignments with the V86 codebase
- changed return value of V86.wait_until_vga_screen_contains() to boolean with true:success and false:timeout
- removed "throw" and "finally" statements in V86.wait_until_vga_screen_contains()
- removed "finally" statements in exec_test() to simplify cleanup code
- removed "export" statements (forgotten leftovers from a silly experiment)
- simplified parsing of options.timeout_msec in V86.wait_until_vga_screen_contains()
- changed line-matching function to string.startsWith()
- switched from Element.innerHTML to Element.textContent in html assignments
- relaxed rules for handling of command in expected[] in expect()
- removed unused argument "fdc" from FloppyDrive constructor
- renamed variable "tm_end" to "end"
2025-08-23 10:14:32 -06:00
Christian Schnell
e16ce249fe replaced use of RegExp.escape() with own function
RegExp.escape() is available since the May 2025 Baseline which is not yet supported at github CI, replaced with a helper function.
2025-08-23 10:14:32 -06:00
Christian Schnell
00203987f4 moved text matching code from expect() to V86.wait_until_vga_screen_contains()
All changes to V86.wait_until_vga_screen_contains() are backward compatible.
2025-08-23 10:14:32 -06:00
Christian Schnell
b91afaa821 moved FreeDOS boot floppy tests to tests/full/run.js
Moved tests using optional image files

    /images/freedos-fds/freedos.boot.disk.160K.img
    /images/freedos-fds/freedos.boot.disk.180K.img
    /images/freedos-fds/freedos.boot.disk.320K.img
    /images/freedos-fds/freedos.boot.disk.360K.img
    /images/freedos-fds/freedos.boot.disk.640K.img
    /images/freedos-fds/freedos.boot.disk.1200K.img

from tests/api/floppy.js to tests/full/run.js.

Dropped redundant 720K and 1440K FreeDOS boot floppies, these formats are already covered in tests/full/run.js.
2025-08-23 10:14:32 -06:00
Christian Schnell
26ae4dc019 removed Space Invaders boot test, already covered in tests/full/run.js 2025-08-23 10:14:32 -06:00
Christian Schnell
200219335b deactivated Space Invaders test until image file is available on github
Maybe I'm assuming the wrong file name "images/asm-space-invaders.img"?
2025-08-23 10:14:32 -06:00
Christian Schnell
e8235c5f3e added custom floppy size test using Space Invaders (~2K) 2025-08-23 10:14:32 -06:00
Christian Schnell
d7e9c64975 minor optimization 2025-08-23 10:14:32 -06:00
Christian Schnell
51998355ed reduced usage of string.endsWith() in expect() to minimum
If the command string passed to expect() is non-empty it is also used as the first expected response line, but it must be matched against the screen line using .endsWith() because any text preceding the command (usually the shell prompt) is unknown. All other response lines must now match exactly their respective screen lines.
2025-08-23 10:14:32 -06:00
Christian Schnell
ed3f083d50 simplified floppy size label formatting for web UI 2025-08-23 10:14:32 -06:00
Christian Schnell
0a3a406f69 fixed minor issues
- added log message for missing FreeDOS boot image files
- simplified array construction using spread (...) syntax
- removed deprecated comment about linux test failure
2025-08-23 10:14:32 -06:00
Christian Schnell
f2d8ee7334 attempt to fix the linux-test bug at github CI 2025-08-23 10:14:32 -06:00
Christian Schnell
30cd26ca58 fixed missing awaits within V86.set_fda() and V86.set_fdb()
Async methods V86.set_fda/set_fdb() were not awaiting URL-based file downloads.

This fixed a bug in the state snapshot tests, removed now uneeded call to pause(1000) after loading the image URL.
2025-08-23 10:14:32 -06:00
Christian Schnell
4a22c44bce fixed async calls to V86.set_fda() and V86.set_fdb() 2025-08-23 10:14:32 -06:00
Christian Schnell
8b13c70470 disabled Linux format command test 2025-08-23 10:14:32 -06:00
Christian Schnell
0541e3bc9b added pause command to Linux format test 2025-08-23 10:14:32 -06:00
Christian Schnell
57fb799328 added two floppy format tests, general improvements and cleanup
- added floppy format test to MS-DOS and Linux tests
- added RegExp support to expect() and support to capture its command output
- improved emulator setup and teardown
- removed most of the calls to pause()
- activated FreeDOS boot floppy tests, but conditional (they depend on image file existence on the test host)
- improved documentation of expect() function
- removed unused test with TinyCore 4.7.7, using TinyCore 11 instead
2025-08-23 10:14:32 -06:00
Christian Schnell
b909cfe802 added tests for 8 FreeDOS boot disks of different sizes 2025-08-23 10:14:32 -06:00
Christian Schnell
8f536c090c added support for 3"1/2 640 kB floppy images (C/H/S=80/2/8) 2025-08-23 10:14:32 -06:00
Christian Schnell
fc1b204d50 moved Linux test from Core-4.7.7.iso to TinyCore-11.0.iso 2025-08-23 10:14:32 -06:00
Christian Schnell
6f891de57d added two more floppy tests
- added fda state snapshot test by using delayed keyboard input for MS-DOS 6.22 (10ms inter-character delay)
- added fda floppy test using Core 4.7.7 Linux
2025-08-23 10:14:32 -06:00
Christian Schnell
5d94c6bf7b added two floppy drive tests
- renamed floppy-insert-eject.js to floppy.js
- merged and reactivated test from floppy-insert-eject.js
- added fdb test using hda guest MSDOS-6.22
2025-08-23 10:14:32 -06:00
Christian Schnell
be3d6c169b further relaxed disk geometry detection from image size
Reverted to the original method of expanding any image to its closest fitting geometry if its exact size cannot be matched. This now includes images with a size smaller or equal to 512 bytes which are expanded to the smallest known geometry of 160K.

This is the most robust method, loading an image will now only fail if the given image is larger than any known geometry.
2025-08-23 10:14:32 -06:00
Christian Schnell
46629ecfd2 three minor fixes
- added floppy command code to debug output of unimplemented command
- replaced call to .forEach() with regular for() loop
- removed unused members FloppyDrive.fdc and FloppyDrive.cpu
2025-08-23 10:14:32 -06:00
Christian Schnell
415a65a5c6 renamed img_buffer to buffer, removed fdc_config.img_buffer 2025-08-23 10:14:32 -06:00
Christian Schnell
b1c1b8bc76 raised minimum floppy image size from 512 to 163840 bytes (160K)
- auto-expand images to minimum floppy disk size of 160 kB (5"1/4, CHS=40/1/8)
- images larger than the minimum size must match one of the predefined disk geometries
2025-08-23 10:14:32 -06:00
Christian Schnell
23c25c0131 fixed license text 2025-08-23 10:14:32 -06:00
Christian Schnell
8b01980b9b replaced empty floppy size range field with dropdown list
Changed web UI HTML element type of empty floppy size fields from INPUT to SELECT.

The INPUT element allowed any value between 160 and 3840 (KB), even though only a strict subset is actually supported.

Using the SELECT element we can present a meaningful set of valid floppy disk sizes in a human readable format. This prevents faulty user input and is self-documenting.
2025-08-23 10:14:32 -06:00
Christian Schnell
196001e956 integrated new floppy features into v86 web ui
Changes in index.html and debug.html:
- added fdb image selector
- added empty disk options for fda and fdb (constrained to 160..3840 KB, default: 1440 KB)
- added insert/eject button for fdb

Revised floppy controller API in starter.js (and main.js):
- V86.set_fda(file)
- V86.set_fdb(file)     // new
- V86.eject_fda()
- V86.eject_fdb()       // new
- V86.get_disk_fda()    // new, return disk buffer or null if drive is empty
- V86.get_disk_fdb()    // new
2025-08-23 10:14:32 -06:00
Christian Schnell
90ccbcd9ee resolved qemu license issue 2025-08-23 10:14:32 -06:00
Christian Schnell
3cf1e89556 Changed default fdb drive type from 2.88M to 1.44M (3"1/2). 2025-08-23 10:14:32 -06:00
Christian Schnell
5332e7ba6b changed disk format detection
- changed to strict disk format detection with the exception of images smaller than 512 bytes
- images smaller than 512 are extended with zeros at the end to 512 bytes
- also reworked length calculation in FloppyController.start_read_write()
2025-08-23 10:14:32 -06:00
Christian Schnell
c22a8f1d4c rewrote class FloppyController
Changes:
- rewrote floppy controller to make it more compatible with the Intel 82078
- now supports optional second drive fdb (enabled by default)
- now supports write-protected media and configurable drive types
- added configuration object to FloppyController constructor
- added a few registers and rewrote their read/write handlers
- added explicit command phases and reimplemented all floppy commands
- added controller runtime state as needed by registers and commands
- split off class FloppyDrive from FloppyController to support mulitple drives
- rewrote drive and disk format detection
- get/set_state() now includes the drive's image buffer (hopefully got that right)
- improved code readability by using named instead of literal constants, added comments where needed
- improved log output readability

The Intel 82078 (44-Pin) manual and fdc.c from qemu were used for reference material.

Known issues:
- hwinfo under linux reports read errors when no floppy disk is inserted into a drive
2025-08-23 10:14:32 -06:00
SuperMaxusa
2482a9957a fix keyboard input in mobile browsers
fix onclick handler, phone_keyboard follows text mode cursor
always show phone keyboard
2025-08-19 16:29:56 -06:00
Oskari Alaranta
51bf5a63dc fix PIT single byte access order 2025-07-22 19:34:16 +07:00
Fabian
6c5fe0f40c replace some transmutes with from_bits/to_bits/from_le_bytes/to_le_bytes 2025-07-02 16:30:28 +07:00
Fabian
45d190548a use unadjustedMovement for requestPointerLock 2025-07-02 15:01:19 +07:00
Fabian
ef25dbf95f add typescript definitions 2025-07-02 15:01:19 +07:00
Fabian
066d5efd23 (breaking change) rename set_{mouse,keyboard}_status to set_{mouse,keyboard}_enabled 2025-07-02 15:01:19 +07:00
Fabian
27e6994abb refactor old debug code, make sure there's no require calls to capstone/wabt (#631) 2025-07-02 15:01:19 +07:00
Fabian
ccdc7b6b14 haiku beta4 only boots reliably with acpi 2025-07-02 15:01:19 +07:00
Fabian
be8530cf05 mention ISO 9660 generator 2025-07-02 15:01:19 +07:00
Fabian
9af729f56c simplify mouse speed code: don't modify deltas 2025-07-02 15:01:19 +07:00
Fabian
5831d23fe3 add missing LOG_VGA 2025-07-02 15:01:19 +07:00
Fabian
fe3757fe5b smsw: mask (triggers assertion otherwise) 2025-07-02 15:01:19 +07:00
Fabian
1ab04fe419 SMC: log written value 2025-07-02 15:01:19 +07:00
Fabian
80917ab8b9 rewrite some assertions to speed up port io in debug mode 2025-07-02 15:01:19 +07:00
Fabian
8daade0267 minor: remove export 2025-07-02 15:01:19 +07:00
Fabian
0eec29652b hide change cdrom button if no cdrom 2025-06-12 19:40:09 +07:00
Fabian
0d627a22f3 update reactos (#1321)
thanks @SuperMaxusa
2025-06-12 19:40:09 +07:00
Fabian
8c4d38219b remove incorrect assertion 2025-06-12 01:14:36 +07:00
Fabian
9a72cb43a6 duplicate 67h prefix is allowed and does nothing 2025-06-11 22:15:24 +07:00
Fabian
fff0df6665 include msdos image for tests, and use curl over wget 2025-06-11 21:36:51 +07:00
Fabian
8feebbb5cb generate iso files on demand 2025-06-11 19:44:11 +07:00
Fabian
b8875ff12b update state images for cdrom insert/eject support at runtime 2025-06-11 19:44:11 +07:00
Fabian
a4b86d85e2 create empty disks and second hard drive 2025-06-10 17:45:00 +07:00
Fabian
f28837785b add tests for cdrom+hda+hdb 2025-06-10 17:43:20 +07:00
Fabian
05acfb12eb add cdrom insert/eject test 2025-06-10 17:41:45 +07:00
Fabian
a4e4967683 less bold 2025-06-10 16:45:37 +07:00
Fabian
6d54013976 minor: add missing LOG_DISK 2025-06-10 15:39:58 +07:00
Fabian
aa6bfcfb32 fix boot order url parameter 2025-06-10 14:36:03 +07:00
Fabian
258f98560b don't error on floppy command 0x14 (fiwix) 2025-06-09 14:47:55 +07:00
Fabian
c8facf37f5 add note on chromium bug 2025-06-09 14:47:55 +07:00
Fabian
44fb0d57ea reduce verbosity of cpu logging 2025-06-09 14:47:55 +07:00
Fabian
45c4a7d0e1 make exit button preserve settings 2025-06-09 14:47:55 +07:00
Fabian
ed34b06339 add Chokanji 4 2025-06-09 14:47:55 +07:00
Fabian
1a0f31e34f add SqueakNOS 2025-06-09 14:47:55 +07:00
Fabian
f81470a762 minor 2025-06-09 14:47:55 +07:00
Fabian
197d43cb76 fix profile name 2025-06-09 14:47:55 +07:00
Christian Schnell
10bb5bc0fa raise IRQ only if IRQ bit in DOR is enabled
The floppy controller's Digital Output Register (DOR) is a read/writeable
8 bit register. Amongst other things, this register allows the guest to
reset the floppy controller ("nRESET" bit 0x8) and to enable/disable the
use of the controller's IRQ line ("IRQ" bit 0x4). Note that the "nRESET"
bit is negated ("not RESET").

The floppy controller is expected to raise an IRQ when exiting the RESET
state, meaning when the guest changes the nRESET bit from 0 to 1.

That is what is currently done in the DOR write handler in floppy.js.

But the floppy controller is also expected to raise IRQs only if the IRQ
bit in DOR is set, and this was not considered in the DOR write handler.

This commit changes the DOR write handler to raise the IRQ only if the
IRQ bit is also set in the new DOR value received from the guest.
2025-06-09 14:35:16 +07:00
Fabian
d603a779db minor cleanup 2025-06-07 21:33:46 +07:00
Fabian
2ea8ff0baf make ide changes compatible with old state images 2025-06-07 21:33:46 +07:00
Christian Schnell
cb3274c7ee fixed misdeclared register locations of BAR1 and BAR3
There was an overlap in the PCI space register declarations of the Floppy and the IDE controller. Since the conflicting registers were used exclusively by the two controllers this was fine, but 9front prints out this boot warning message:

    ioalloc: 3f0 - 3f5 floppy: clashes with: 3f4 - 3f5 PCI.0.31.0

Note that "PCI.0.31.0" is our IDE controller (with pci_id 0x1f).

Problem: IDE actually uses only register address 0x3f6, but declared BAR1 for address range 0x3f4-0x3f7 which overlaps with the Floppy controller (the same with BAR3 and its address range of 0x374-0x377, but there was no overlap with other devices here).

Fix:
- changed BAR1 base address from 0x3f4 to 0x3f6 and its size from 4 to 1
- changed BAR3 base address from 0x374 to 0x376 and its size from 4 to 1

This commit fixes that problem and the 9front boot message from above.
2025-06-07 21:33:46 +07:00
Christian Schnell
1f855f85ee write RTC CMOS data only for master drives 2025-06-07 21:33:46 +07:00
Christian Schnell
ba2186ee49 improved identify packet 2025-06-07 21:33:46 +07:00
Christian Schnell
20521979e7 added ATAPI command "START STOP UNIT" (W95)
Adds support to eject a CD from within Windows 95 through the context menu of the CD icon.

Before this commit eject was only supported externally through the v86 API.
2025-06-07 21:33:46 +07:00
Christian Schnell
50945b3d16 set "CD Not Ready" condition in ATAPI commands as specified in MMC-3
A few ATAPI commands did not return an error (CD Not Ready condition) with ejected medium as specified in MMC-3 (https://www.t10.org/ftp/t10/document.97/97-108r0.pdf).

Adding these to our subset of ATAPI commands improved the FreeDOS 1.4 situation around eject/insert a bit (the problem really is eject, insert always works given the medium is ejected). Waiting around 20-30 sec after eject seems to work reliably.
2025-06-07 21:33:46 +07:00
Christian Schnell
ef0b306732 added ATAPI command "PAUSE" (W95)
The minor change from yesterday changed Win95's behaviour such that it now issues an ATAPI command ("PAUSE") that wasn't implemented yet which led to Win95 crashing in v86 Debug mode. Added that command and now it works again.
2025-06-07 21:33:46 +07:00
Christian Schnell
28cfa1501b added ATAPI signature in ATA commands "READ SECTORS" and "IDENTIFY DEVICE" 2025-06-07 21:33:46 +07:00
Christian Schnell
5a7d8948d8 added references to ACS-3 from ATA/ATAPI-8 2025-06-07 21:33:46 +07:00
Christian Schnell
06d9b9bf92 removed CPU.devices.hda 2025-06-07 21:33:46 +07:00
Christian Schnell
f601daf748 added ATAPI command flags 2025-06-07 21:33:46 +07:00
Christian Schnell
50c153e6c2 improved register handling based on ACS-3 (ATA/ATAPI-8) 2025-06-07 21:33:46 +07:00
Christian Schnell
8e95ee4f64 minor fix 2025-06-07 21:33:46 +07:00
Christian Schnell
0dff08dfa6 changed CPU.devices.cdrom and .hda to point to their IDEInterface objects
- changed cdrom and hda to point to the drive's IDEInterface instead of its parent IDEChannel
- added boolean method IDEInterface.has_disk() to query whether a Compact Disk is inserted or not
- the IDEController object and the CD-ROM device are now created unconditionally
2025-06-07 21:33:46 +07:00
Christian Schnell
b1e45723df removed overly verbose I/O BAR debug output 2025-06-07 21:33:46 +07:00
Christian Schnell
b71ebced7d reduced debug log output some more 2025-06-07 21:33:46 +07:00
Christian Schnell
eb374437e3 made the debug output more comprehensible
Debug log messages in ide.js are currently at maximum granularity and emitted at a very high volume.

Under rare conditions this output can be very useful, else it clutters the view and makes comprehension of and reasoning about the runtime behaviour too challenging.

Changes:

- Erros and warnings are always logged, as well as important IDE state-related messages.
- Debug log messages are now split into 5 categories, 4 of them (the highly specific and also high-frequency ones) are optional and by default disabled.
- The 4 optional categories are REG_IO (log messages related to register I/O), IRQ, RW (read/write data) and RW_DMA (read/write data with DMA).
- ATA/ATAPI commands are always logged unless they fall into one of the 4 optional categories.
- Commands are logged with their name and including the full register state before and after command execution (input and output arguments).

Normal debug log output (with all 4 optional categories disabled, the default) is now mostly reduced to the actual ATA/ATAPI command flow with input/output command arguments, enough so that it's much easier to understand what happens.

The 4 optional debug categories can only be enabled programmatically in ide.js, they are meant to be used in advanced debugging sessions.
2025-06-07 21:33:46 +07:00
Christian Schnell
0cd7ea7a3d added IDEInterface.ata_abort_command() to reduce code duplication 2025-06-07 21:33:46 +07:00
Christian Schnell
9e45314ed2 abort any ATA read/write attempt on ATAPI device 2025-06-07 21:33:46 +07:00
Christian Schnell
e98f30b4e5 reduced DMA-related debug log output 2025-06-07 21:33:46 +07:00
Christian Schnell
694537f51e added "Insert/Eject CD" button to debug.html 2025-06-07 21:33:46 +07:00
Christian Schnell
e1610ff1a9 abort any ATA READ command called on ejected device (W95) 2025-06-07 21:33:46 +07:00
Christian Schnell
59a697ca93 added ATA command "NOP" 2025-06-07 21:33:46 +07:00
Christian Schnell
b63e488844 cleanup: cd-rom device now exists unconditionally 2025-06-07 21:33:46 +07:00
Christian Schnell
0257cb83c9 added "Insert/Eject CD" button to web UI 2025-06-07 21:33:46 +07:00
Christian Schnell
f3e29baa9c bugfix: cd-rom device now exists unconditionally 2025-06-07 21:33:46 +07:00
Christian Schnell
add68c3202 restored IDEInterface.device_reset() back to its original code 2025-06-07 21:33:46 +07:00
Christian Schnell
56cab05d26 documented IDENTIFY binary response, and more code cleanup 2025-06-07 21:33:46 +07:00
Christian Schnell
3f121c831f fixed SCSI Sense responses (CHECK CONDITION)
The SCSI Status field ("Status Key" and "Additional Status Code" aka. ASC) was incorrectly passed from the SCSI layer to the ATA register layer, and ATAPI command "TEST UNIT READY" was not implemented correctly.

This led to Linux not properly detecting when no CD was inserted into the CD-ROM device, which led to error output in "hwinfo" like (it attempted to read a disk that's no inserted):

> root@debian-iscsi:~# hwinfo --cdrom
> > block.5.1: /dev/sr0 cache[   78.371349] I/O error, dev sr0, sector 64 op 0x0:(READ) flags 0x80700 phys_seg 1 prio class 2
> [   78.412349] I/O error, dev sr0, sector 64 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 2
> [   78.412349] Buffer I/O error on dev sr0, logical block 16, async page read
> [   78.448365] I/O error, dev sr0, sector 68 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 2
> [   78.448369] Buffer I/O error on dev sr0, logical block 17, async page read 08: SCSI 100.0: 10602 CD-ROM

This commit fixes that problem by now properly returning SCSI Sense and Additional Sense responses to the ATAPI host.
2025-06-07 21:33:46 +07:00
Christian Schnell
2add432dbf added some comments to IDE CMOS setup 2025-06-07 21:33:46 +07:00
Christian Schnell
40dab74fa5 added references to MMC-2 (SCSI-2 Multimedia Commands) 2025-06-07 21:33:46 +07:00
Christian Schnell
20acdacfcc downgraded ATAPI CD-ROM commands from SCSI-3 to SCSI-2
In the context of ATAPI there's only a single linked document in ide.js: https://www.t10.org/ftp/x3t9.2/document.87/87-106r0.txt.

This link points to "PROPOSAL FOR CD-ROM IN SCSI-2" from 1987 (!) and its content matches the current code in v86, which the SCSI-3 documents did not at all (only the command codes matched, but otherwise SCSI-3 is very different from SCSI-2).
2025-06-07 21:33:46 +07:00
Christian Schnell
c51a0c1f53 reintroduced ATA status register bit DSC
In the original code, status register bit DSC (Drive Seek Complete, 0x10) is always set when completing without error, but ATA/ATAPI-6 specifies this bit to be command dependent. This PR now sets the status register exactly like it was before, just using named constants instead of magic numbers.

Also replaced most of the remaining magic number assignments to the ATA status register.
2025-06-07 21:33:46 +07:00
Christian Schnell
a000334266 added references to SAM-3 (SCSI Architecture Model) 2025-06-07 21:33:46 +07:00
Christian Schnell
03d0b402fb added references to SPC-3 (SCSI Primary Commands) 2025-06-07 21:33:46 +07:00
Christian Schnell
64df9aef35 added named constants for SCSI commands 2025-06-07 21:33:46 +07:00
Christian Schnell
f253f8b67e replaced more magic numbers 2025-06-07 21:33:46 +07:00
Christian Schnell
588dcaa876 replaced more magic numbers with named constants 2025-06-07 21:33:46 +07:00
Christian Schnell
81483e41fa first round of ATA command overhaul 2025-06-07 21:33:46 +07:00
Christian Schnell
4131aa709f renamed register members to better align with the standard
Renamed members that carry register values to better align with the actual wording in the ATA/ATAPI-6 standard.

Other changes in this commit include:

- added a bunch of named constants for ATA registers and their bits
- improved PCI configuration space setup
- renamed class IDEPCIAdapter to IDEController
2025-06-07 21:33:46 +07:00
Christian Schnell
b9e6445baa improved dbg_log() formatting
Merged and improved on the work from JoeOsborn in respect to formatting in dbg_log() output.
Used register names from the standard for better readability of the output, and added a few missing logs.
2025-06-07 21:33:46 +07:00
Christian Schnell
2564e30950 implemented ATA command GET_MEDIA_STATUS 2025-06-07 21:33:46 +07:00
Christian Schnell
e538b849ce renamed class IDEDevice to IDEChannel
In v86, the name "device" is already used in the context of PCI, and additionally, in the ATA/ATAPI standard the term "device" is used for disk drives.

The name used for this entity (IDEDevice) in the ATA/ATAPI standard is "channel", and since "channel" is not otherwise used in ide.js it seems right to rename IDEDevice to IDEChannel.
2025-06-07 21:33:46 +07:00
Christian Schnell
9c929e1547 fixed set/get_state()-related error 2025-06-07 21:33:46 +07:00
Christian Schnell
17d92c56d6 fixed eslint errors 2025-06-07 21:33:46 +07:00
Christian Schnell
1e645e0f06 improved single-device implementation
- immplemented Command and Status register requirements from the standard, ignored all others
- rearranged IDE register definitions, added a descriptive comment to each (standard lingo)
- fixed the EXECUTE DEVICE DIAGNOSTIC (90h) ATA command
2025-06-07 21:33:46 +07:00
Christian Schnell
f5e35644da improved documentation 2025-06-07 21:33:46 +07:00
Christian Schnell
f37e0eb5ad added boolean member IDEInterface.drive_connected 2025-06-07 21:33:46 +07:00
Christian Schnell
a04af85852 added hacky shortcut to indicate missing slave drive 2025-06-07 21:33:46 +07:00
Christian Schnell
c64d1d2dbe code cleanup 2025-06-07 21:33:46 +07:00
Christian Schnell
bcaf02af5f added IDEPCIAdapter.channels[] and IDEDevice.interfaces[] for index-based access to IDEDevice and IDEInterface objects 2025-06-07 21:33:46 +07:00
Christian Schnell
1b90d2e74e create a single PCI device instead of two
Implement a single PCI IDE device in Compatibility Mode for up to 4 drives.

Changes to ide.js:
- added new exported class IDEPCIAdapter, the root class for up to 4 drives
- introduced new config scheme for IDEPCIAdapter (see comment about adapter_config)
- moved PCI-device related members from IDEDevice to IDEPCIAdapter
- rewrote constructor of IDEDevice (no longer exported)
- fixed (?) write-access to ata_port registers 1-6 (see comment below)
- added missing declarations for BAR2 and BAR3 in pci_bars[]
- made definition of the PCI configuration space a bit more more verbose
- renamed "master_port" to "bus_master_port" to avoid confusion
- added a few comments here and there

Regarding write-access to ata_port registers 1-6: It is not clear why these 6
functions simultaneously modify master and slave attributes instead of just
modifying this.current_interface (as is the case everywhere else in the code).
This patch changes that to use this.current_interface, experimental until
there is some explanation.

Also changed cpu.js to now use IDEPCIAdapter instead of IDEDevice.

TODO:
1. clear up the matter around write-access to ata_port registers 1-6
2. in ide.js, a single-device channel needs to be better supported, currently
   the code reports a Hard-Disk device of size 0 for an unconnected slave drive,
   and there must be a better way to signal this case.
3. in cpu.js, this.devices.hda and this.devices.cdrom point to a IDEDevice, they
   should point to an IDEInterface for this.devices.hdb to make sense. This needs
   to be better understood and fixed for slave drives to work.
4. getting some very early output under SeaBIOS, but the screen clears so quickly
   that I cannot read it and it's also not printed to the serial console
2025-06-07 21:33:46 +07:00
Christian Schnell
3c944a02e0 merge changes from JoeOsborn's last changeset into current master
Merged all functionally relevant changes, omitted:

1. Left out almost all of the "dbg_log()" changes, they're like 90% of the original PR and make the essential changes in the diffs a bit hard to read, these can be added back in at a later point in time.
2. Also left out the "wants_cdrom" option and implemented its desired effect with "cdrom: { ejected: true }" as was suggested.
3. The test "tests/api/cdrom-insert-eject.js" is also left out, can be added later back in.

This patch should behave like the last changeset from JoeOsborn in 2023.
2025-06-07 21:33:46 +07:00
SuperMaxusa
760fff6af8 rtc: implement update interrupt 2025-06-04 12:04:52 +07:00
ConfusedPenguin
45d045cc44 Fix serial not handling end of line properly 2025-06-03 23:05:20 +07:00
SuperMaxusa
a07e43bc31 remove local_http flag, small fixes 2025-05-26 23:15:59 +08:00
SuperMaxusa
add1d741c5 run emulator after server, small cleanup 2025-05-26 23:15:59 +08:00
SuperMaxusa
a1c0f12961 move remaining code into if statement 2025-05-26 23:15:59 +08:00
SuperMaxusa
f344f0f40c change v86local.http to external, inline all server code into test and bench scripts, always use random available port 2025-05-26 23:15:59 +08:00
SuperMaxusa
6ce3b69be0 run a server when the emulator is ready (fixes ci) 2025-05-26 23:15:59 +08:00
SuperMaxusa
ad77448248 fix parentPort 2025-05-26 23:15:59 +08:00
SuperMaxusa
3367bedec8 use random port for server, fix console parsing for benchmark 2025-05-26 23:15:59 +08:00
SuperMaxusa
06f33e4a76 remove "use strict" after sync 2025-05-26 23:15:59 +08:00
SuperMaxusa
fc5d381c7f another fix 2025-05-26 23:15:59 +08:00
SuperMaxusa
5dc5cd249b fix closure compiler 2025-05-26 23:15:59 +08:00
SuperMaxusa
8c0fdb9f9d FetchNetworkAdapter.respond_text_and_close() method, form_response_head returns bytes instead of a string 2025-05-26 23:15:59 +08:00
SuperMaxusa
4fd2478d67 fix headers and worker error handling 2025-05-26 23:15:59 +08:00
SuperMaxusa
5f088cc066 release build for bench, check non-zero exit codes 2025-05-26 23:15:59 +08:00
SuperMaxusa
c8391e5347 fetch benchmark 2025-05-26 23:15:59 +08:00
SuperMaxusa
5e16fa3d7b docs about local_http 2025-05-26 23:15:59 +08:00
SuperMaxusa
b0b5fbee12 fix eslint 2025-05-26 23:15:59 +08:00
SuperMaxusa
6ddb163284 test with local server 2025-05-26 23:15:59 +08:00
SuperMaxusa
577b464aa4 allow access from vm to localhost 2025-05-26 23:15:59 +08:00
SuperMaxusa
9a918b3749 fix "connection: closed" header 2025-05-26 23:15:59 +08:00
SuperMaxusa
22ef9ba14b FetchNetworkAdapter.form_response_head() method 2025-05-26 23:15:59 +08:00
Christian Schnell
a3840e32b3 distinguish 8-, 4- and 1-bit color modes for non-SVGA graphics modes 2025-04-28 00:20:04 -07:00
Fabian
5d928d4cc7 remove useless @const 2025-04-16 09:26:13 -07:00
Fabian
73ac915421 defensive programming 2025-04-16 09:26:13 -07:00
Fabian
121a5170f2 minor refactoring (remove readfile, use direct import for libv86.js) 2025-04-16 09:26:13 -07:00
Fabian
611bc7d394 use newer arch state for benchs 2025-04-16 09:26:13 -07:00
Fabian
58471e809b add 2 GB memory test 2025-04-16 09:26:13 -07:00
Fabian
ee9be5a266 move stats_to_string into V86, remove the export 2025-04-16 09:26:13 -07:00
Fabian
7936bcd17d put window/self exports back 2025-04-16 09:26:13 -07:00
Fabian
8559d4d46d simplify print_stats exports 2025-04-16 09:26:13 -07:00
Fabian
3bb724fa69 minor: inline some library functions 2025-04-16 09:26:13 -07:00
Fabian
51bd604cc5 remove "use strict" (is default in es6 modules) 2025-04-16 09:26:13 -07:00
Fabian
2d69353416 remove @export annotations (exports are explicit now) 2025-04-16 09:26:13 -07:00
Fabian
2a4fe44a7d liberate tests from libv86-debug.js 2025-04-16 09:26:13 -07:00
Fabian
b44f6ea83b arch profile uses virtio-net now 2025-04-16 09:26:13 -07:00
Fabian
862ea200b9 move DEBUG magic into log.js, remove nodejs-loader.js (use src/main.js instead) 2025-04-16 09:26:13 -07:00
Fabian
16d961e862 in electron, prefer the fs module over XMLHttpRequest (see #1262) 2025-04-16 09:26:13 -07:00
Fabian
f02715d2ee move get_file_size into lib.js and make it async 2025-04-16 09:26:13 -07:00
Fabian
39c129168e inline config.js into its relevant files 2025-04-16 09:26:13 -07:00
Fabian
a2f6cbb572 s/setLogLevel/set_log_level 2025-04-16 09:26:13 -07:00
Fabian
b3051ebcf9 minor: fix indent 2025-04-16 09:26:13 -07:00
Fabian
1d232cf262 state.js: don't add to CPU's prototype 2025-04-16 09:26:13 -07:00
Fabian
e9d5849afe inlinde memory.js into cpu.js 2025-04-16 09:26:13 -07:00
Fabian
48a71b04a1 inlinde debug.js into cpu.js 2025-04-16 09:26:13 -07:00
Fabian
47f0bf533c replace jor1k's message.Debug with dbg_log 2025-04-16 09:26:13 -07:00
Fabian
c11805a70a minor cleanup of imports 2025-04-16 09:26:13 -07:00
Fabian
9abf526271 add ipxe & netboot.xyz (#554, #1251) 2025-04-16 09:26:13 -07:00
Fabian
1445fdf244 add 9legacy 2025-04-16 09:26:13 -07:00
Fabian
1ed0f28218 test fiwix 2025-04-16 09:26:13 -07:00
Fabian
181e1a6442 net_device_type: take from profile if not specified in query string 2025-04-16 09:26:13 -07:00
Fabian
c8f33a91e9 append version to query string (anti-cache) 2025-04-16 09:26:13 -07:00
SuperMaxusa
f97330d99e
VGA BIOS input (#1307) 2025-04-15 07:42:43 +02:00
Christian Schnell
ed0ed07a18
Reset PS/2 runtime state when soft-resetting CPU (#1301)
- Moved assignment of all member variables from constructor to new method PS2.reset().
- Added call to PS2.reset() in PS2 constructor.
2025-04-06 18:49:35 +02:00
Rob Blanckaert
a9219613af Switch to es6 modules. 2025-04-06 08:53:49 -07:00
Rob Blanckaert
30be975d6a Fix this.avail_last_idx in state images that are made for older v86 versions 2025-04-06 07:50:08 -07:00
Fabian
ff56eed36b ci 2 2025-03-25 16:07:15 +07:00
Fabian
1f9a5fbc68 ci 2025-03-25 13:19:59 +07:00
Fabian
d24565479d setup-node is needed for npm publish 2025-03-25 12:47:39 +07:00
Fabian
62ca883ca2 NODE_AUTH_TOKEN -> NPM_TOKEN 2025-03-25 12:20:46 +07:00
Fabian
78835581ac ignore 'latest' tag 2025-03-25 03:02:54 +07:00
Fabian
970de79d2b fetch history so we can use git describe --tags later 2025-03-25 02:55:44 +07:00
Fabian
bf8a9a9724 why no sponge in github actions 2025-03-25 02:02:19 +07:00
Fabian
4990c43930 update package.json version earlier 2025-03-25 01:53:54 +07:00
Fabian
59ffdcb2dd npm version number from git describe 2025-03-25 01:13:58 +07:00
Rob Blanckaert
573dc8335e More Github actions fixes 2025-03-25 01:13:58 +07:00
137 changed files with 14383 additions and 10208 deletions

View file

@ -22,6 +22,9 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
filter: tree:0
- name: Setup Node.js version
uses: actions/setup-node@v4
@ -65,7 +68,7 @@ jobs:
run: (cd tests/kvm-unit-tests && ./configure && make x86/realmode.flat)
- name: Run kvm-unit-test
run: tests/kvm-unit-tests/run.js tests/kvm-unit-tests/x86/realmode.flat
run: tests/kvm-unit-tests/run.mjs tests/kvm-unit-tests/x86/realmode.flat
- name: Fetch namsmtests cache
uses: actions/cache@v4
@ -92,7 +95,7 @@ jobs:
- name: Download uncached images
if: steps.cache-images.outputs.cache-hit != 'true'
run: wget -nv -P images/ https://i.copy.sh/{linux.iso,linux3.iso,linux4.iso,buildroot-bzimage68.bin,TinyCore-11.0.iso,oberon.img,msdos.img,openbsd-floppy.img,kolibri.img,windows101.img,os8.img,freedos722.img,mobius-fd-release5.img}
run: mkdir -p images && curl --compressed --output-dir images --remote-name-all https://i.copy.sh/{linux.iso,linux3.iso,linux4.iso,buildroot-bzimage68.bin,TinyCore-11.0.iso,oberon.img,msdos.img,openbsd-floppy.img,kolibri.img,windows101.img,os8.img,freedos722.img,mobius-fd-release5.img,msdos622.img}
- name: Run api-tests
run: make api-tests
@ -115,6 +118,9 @@ jobs:
- name: Run expect tests
run: make expect-tests
- name: Update package.json version
run: make update-package-json-version
- name: Upload the artifact
uses: actions/upload-artifact@v4
with:
@ -134,6 +140,10 @@ jobs:
needs: test
if: github.ref == 'refs/heads/master'
permissions:
id-token: write
contents: write
steps:
- name: Delete old release and tag
uses: dev-drprasad/delete-tag-and-release@v1.0.1
@ -146,20 +156,14 @@ jobs:
uses: actions/download-artifact@v4
with:
name: v86
path: build
- name: Display structure of downloaded files
run: ls -R
- name: Set package.json version
uses: actions/github-script@v7
- uses: actions/setup-node@v4
with:
result-encoding: string
script: |
const fs = require("fs");
const pkg = {...require("./build/package.json"), version: '0.' + String(process.env.GITHUB_RUN_NUMBER) + '.0'}
console.log(pkg);
fs.writeFileSync("package.json", JSON.stringify(pkg, null, " "), "utf8");
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- name: Release to GitHub
uses: ncipollo/release-action@v1
@ -171,5 +175,6 @@ jobs:
artifacts: "build/libv86*.js,build/libv86*.js.map,build/*.mjs,build/v86*.wasm"
prerelease: true
- run: mv build/Readme.md build/LICENSE .
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}

1
.gitignore vendored
View file

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

22
LICENSE.MIT Normal file
View file

@ -0,0 +1,22 @@
QEMU Floppy disk emulator (Intel 82078)
Copyright (c) 2003, 2007 Jocelyn Mayer
Copyright (c) 2008 Hervé Poussineau
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

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=const.js config.js io.js main.js lib.js buffer.js ide.js pci.js floppy.js \
memory.js dma.js pit.js vga.js ps2.js rtc.js uart.js \
acpi.js apic.js ioapic.js \
CORE_FILES=cjs.js const.js io.js main.js lib.js buffer.js ide.js pci.js floppy.js \
dma.js pit.js vga.js ps2.js rtc.js uart.js \
acpi.js iso9660.js \
state.js ne2k.js sb16.js virtio.js virtio_console.js virtio_net.js virtio_balloon.js \
bus.js log.js cpu.js debug.js \
bus.js log.js cpu.js \
elf.js kernel.js
LIB_FILES=9p.js filesystem.js jor1k.js marshall.js
LIB_FILES=9p.js filesystem.js marshall.js
BROWSER_FILES=screen.js keyboard.js mouse.js speaker.js serial.js \
network.js starter.js worker_bus.js dummy_screen.js \
inbrowser_network.js fake_network.js wisp_network.js fetch_network.js \
@ -151,7 +151,7 @@ build/libv86.mjs: $(CLOSURE) src/*.js lib/*.js src/browser/*.js
$(CLOSURE_FLAGS)\
--compilation_level SIMPLE\
--jscomp_off=missingProperties\
--output_wrapper ';let module = {exports:{}}; %output%; export default module.exports.V86;'\
--output_wrapper ';let module = {exports:{}}; %output%; export default module.exports.V86; export let {V86, CPU} = module.exports;'\
--js $(CORE_FILES)\
--js $(BROWSER_FILES)\
--js $(LIB_FILES)\
@ -172,6 +172,7 @@ build/libv86-debug.js: $(CLOSURE) src/*.js lib/*.js src/browser/*.js
--js $(CORE_FILES)\
--js $(BROWSER_FILES)\
--js $(LIB_FILES)
ls -lh build/libv86-debug.js
build/libv86-debug.mjs: $(CLOSURE) src/*.js lib/*.js src/browser/*.js
mkdir -p build
@ -179,6 +180,7 @@ build/libv86-debug.mjs: $(CLOSURE) src/*.js lib/*.js src/browser/*.js
--js_output_file build/libv86-debug.mjs\
--define=DEBUG=true\
$(CLOSURE_FLAGS)\
$(CLOSURE_READABLE)\
--compilation_level SIMPLE\
--jscomp_off=missingProperties\
--output_wrapper ';let module = {exports:{}}; %output%; export default module.exports.V86; export let {V86, CPU} = module.exports;'\
@ -257,6 +259,7 @@ clean:
-rm build/libv86.js
-rm build/libv86.mjs
-rm build/libv86-debug.js
-rm build/libv86-debug.mjs
-rm build/v86_all.js
-rm build/v86.wasm
-rm build/v86-debug.wasm
@ -273,8 +276,11 @@ update_version:
set -e ;\
COMMIT=`git log --format="%h" -n 1` ;\
DATE=`git log --date="format:%b %e, %Y %H:%m" --format="%cd" -n 1` ;\
SEARCH='<code>Version: <a href="https://github.com/copy/v86/commits/[a-f0-9]\+">[a-f0-9]\+</a> ([^(]\+)</code>' ;\
REPLACE='<code>Version: <a href="https://github.com/copy/v86/commits/'$$COMMIT'">'$$COMMIT'</a> ('$$DATE')</code>' ;\
SEARCH='<code>Version: <a id="version" href="https://github.com/copy/v86/commits/[a-f0-9]\+">[a-f0-9]\+</a> ([^(]\+)</code>' ;\
REPLACE='<code>Version: <a id="version" href="https://github.com/copy/v86/commits/'$$COMMIT'">'$$COMMIT'</a> ('$$DATE')</code>' ;\
sed -i "s@$$SEARCH@$$REPLACE@g" index.html ;\
SEARCH='<script src="build/v86_all.js?[a-f0-9]\+"></script>' ;\
REPLACE='<script src="build/v86_all.js?'$$COMMIT'"></script>' ;\
sed -i "s@$$SEARCH@$$REPLACE@g" index.html ;\
grep $$COMMIT index.html
@ -293,51 +299,51 @@ build/integration-test-fs/fs.json: images/buildroot-bzimage68.bin
./tools/copy-to-sha256.py build/integration-test-fs/fs.tar build/integration-test-fs/flat
rm build/integration-test-fs/fs.tar build/integration-test-fs/bzImage build/integration-test-fs/initrd
tests: build/libv86-debug.js build/v86-debug.wasm build/integration-test-fs/fs.json
tests: build/v86-debug.wasm build/integration-test-fs/fs.json
LOG_LEVEL=3 ./tests/full/run.js
tests-release: build/libv86.js build/v86.wasm build/integration-test-fs/fs.json
TEST_RELEASE_BUILD=1 ./tests/full/run.js
nasmtests: build/libv86-debug.js build/v86-debug.wasm
nasmtests: build/v86-debug.wasm
$(NASM_TEST_DIR)/create_tests.js
$(NASM_TEST_DIR)/gen_fixtures.js
$(NASM_TEST_DIR)/run.js
nasmtests-force-jit: build/libv86-debug.js build/v86-debug.wasm
nasmtests-force-jit: build/v86-debug.wasm
$(NASM_TEST_DIR)/create_tests.js
$(NASM_TEST_DIR)/gen_fixtures.js
$(NASM_TEST_DIR)/run.js --force-jit
jitpagingtests: build/libv86-debug.js build/v86-debug.wasm
jitpagingtests: build/v86-debug.wasm
$(MAKE) -C tests/jit-paging test-jit
./tests/jit-paging/run.js
qemutests: build/libv86-debug.js build/v86-debug.wasm
qemutests: build/v86-debug.wasm
$(MAKE) -C tests/qemu test-i386
LOG_LEVEL=3 ./tests/qemu/run.js build/qemu-test-result
./tests/qemu/run-qemu.js > build/qemu-test-reference
diff build/qemu-test-result build/qemu-test-reference
qemutests-release: build/libv86.js build/v86.wasm
qemutests-release: build/libv86.mjs build/v86.wasm
$(MAKE) -C tests/qemu test-i386
TEST_RELEASE_BUILD=1 time ./tests/qemu/run.js build/qemu-test-result
./tests/qemu/run-qemu.js > build/qemu-test-reference
diff build/qemu-test-result build/qemu-test-reference
kvm-unit-test: build/libv86-debug.js build/v86-debug.wasm
kvm-unit-test: build/v86-debug.wasm
(cd tests/kvm-unit-tests && ./configure && make x86/realmode.flat)
tests/kvm-unit-tests/run.js tests/kvm-unit-tests/x86/realmode.flat
tests/kvm-unit-tests/run.mjs tests/kvm-unit-tests/x86/realmode.flat
kvm-unit-test-release: build/libv86.js build/v86.wasm
kvm-unit-test-release: build/libv86.mjs build/v86.wasm
(cd tests/kvm-unit-tests && ./configure && make x86/realmode.flat)
TEST_RELEASE_BUILD=1 tests/kvm-unit-tests/run.js tests/kvm-unit-tests/x86/realmode.flat
TEST_RELEASE_BUILD=1 tests/kvm-unit-tests/run.mjs tests/kvm-unit-tests/x86/realmode.flat
expect-tests: build/libv86-debug.js build/v86-debug.wasm build/libwabt.js
expect-tests: build/v86-debug.wasm build/libwabt.cjs
make -C tests/expect/tests
./tests/expect/run.js
devices-test: build/libv86-debug.js build/v86-debug.wasm
devices-test: build/v86-debug.wasm
./tests/devices/virtio_9p.js
./tests/devices/virtio_console.js
./tests/devices/fetch_network.js
@ -352,11 +358,12 @@ rust-test: $(RUST_FILES)
rust-test-intensive:
QUICKCHECK_TESTS=100000000 make rust-test
api-tests: build/libv86-debug.js build/v86-debug.wasm
api-tests: build/v86-debug.wasm
./tests/api/clean-shutdown.js
./tests/api/state.js
./tests/api/reset.js
#./tests/api/floppy-insert-eject.js # disabled for now, sometimes hangs
./tests/api/floppy.js
./tests/api/cdrom-insert-eject.js
./tests/api/serial.js
./tests/api/reboot.js
./tests/api/pic.js
@ -375,13 +382,19 @@ build/capstone-x86.min.js:
mkdir -p build
wget -nv -P build https://github.com/AlexAltea/capstone.js/releases/download/v3.0.5-rc1/capstone-x86.min.js
build/libwabt.js:
build/libwabt.cjs:
mkdir -p build
wget -nv -P build https://github.com/WebAssembly/wabt/archive/1.0.6.zip
unzip -j -d build/ build/1.0.6.zip wabt-1.0.6/demo/libwabt.js
mv build/libwabt.js build/libwabt.cjs
rm build/1.0.6.zip
build/xterm.js:
curl https://cdn.jsdelivr.net/npm/xterm@5.2.1/lib/xterm.min.js > build/xterm.js
curl https://cdn.jsdelivr.net/npm/xterm@5.2.1/lib/xterm.js.map > build/xterm.js.map
curl https://cdn.jsdelivr.net/npm/xterm@5.2.1/css/xterm.css > build/xterm.css
update-package-json-version:
git describe --tags --exclude latest | sed 's/-/./' | tr - + | tee build/version
jq --arg version "$$(cat build/version)" '.version = $$version' package.json > package.json.tmp
mv package.json.tmp package.json

View file

@ -25,6 +25,7 @@ list of emulated hardware:
- A generic VGA card with SVGA support and Bochs VBE Extensions.
- A PCI bus. This one is partly incomplete and not used by every device.
- An IDE disk controller.
- A built-in ISO 9660 CD-ROM generator.
- An NE2000 (RTL8390) PCI network card.
- Various virtio devices: Filesystem, network and balloon.
- A SoundBlaster 16 sound card.
@ -148,7 +149,7 @@ for a full setup on Debian or
The disk images for testing are not included in this repository. You can
download them directly from the website using:
`wget -P images/ https://i.copy.sh/{linux3.iso,linux.iso,linux4.iso,buildroot-bzimage68.bin,openbsd-floppy.img,kolibri.img,windows101.img,os8.img,freedos722.img}`
`curl --compressed --output-dir images/ --remote-name-all https://i.copy.sh/{linux.iso,linux3.iso,linux4.iso,buildroot-bzimage68.bin,TinyCore-11.0.iso,oberon.img,msdos.img,openbsd-floppy.img,kolibri.img,windows101.img,os8.img,freedos722.img,mobius-fd-release5.img,msdos622.img}`
Run integration tests: `make tests`
@ -196,6 +197,7 @@ repository under their own licenses:
- [`lib/zstd/zstddeclib.c`](lib/zstd/zstddeclib.c)
- [`tests/kvm-unit-tests/`](tests/kvm-unit-tests)
- [`tests/qemutests/`](tests/qemutests)
- [`src/floppy.js/`](src/floppy.js) contains parts ported from qemu under the MIT license, see LICENSE.MIT.
## Credits

View file

@ -2,60 +2,11 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>v86 (debug)</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no,interactive-widget=resizes-content">
<script src="src/const.js"></script>
<script src="src/config.js"></script>
<script src="src/log.js"></script>
<script src="src/lib.js"></script>
<script src="src/buffer.js"></script>
<script src="src/cpu.js"></script>
<script src="src/debug.js"></script>
<script src="src/io.js"></script>
<script src="src/main.js"></script>
<script src="src/ide.js"></script>
<script src="src/pci.js"></script>
<script src="src/floppy.js"></script>
<script src="src/memory.js"></script>
<script src="src/dma.js"></script>
<script src="src/pit.js"></script>
<script src="src/vga.js"></script>
<script src="src/ps2.js"></script>
<script src="src/rtc.js"></script>
<script src="src/uart.js"></script>
<script src="src/acpi.js"></script>
<script src="src/apic.js"></script>
<script src="src/ioapic.js"></script>
<script src="src/sb16.js"></script>
<script src="src/ne2k.js"></script>
<script src="src/state.js"></script>
<script src="src/virtio.js"></script>
<script src="src/virtio_balloon.js"></script>
<script src="src/virtio_console.js"></script>
<script src="src/virtio_net.js"></script>
<script src="src/bus.js"></script>
<script src="src/elf.js"></script>
<script src="src/kernel.js"></script>
<script src="src/browser/main.js"></script>
<script src="src/browser/screen.js"></script>
<script src="src/browser/keyboard.js"></script>
<script src="src/browser/mouse.js"></script>
<script src="src/browser/speaker.js"></script>
<script src="src/browser/serial.js"></script>
<script src="src/browser/network.js"></script>
<script src="src/browser/fake_network.js"></script>
<script src="src/browser/fetch_network.js"></script>
<script src="src/browser/wisp_network.js"></script>
<script src="src/browser/starter.js"></script>
<script src="src/browser/worker_bus.js"></script>
<script src="src/browser/print_stats.js"></script>
<script src="src/browser/filestorage.js"></script>
<script src="lib/jor1k.js"></script>
<script src="lib/9p.js"></script>
<script src="lib/filesystem.js"></script>
<script src="lib/marshall.js"></script>
<script src="build/capstone-x86.min.js"></script>
<script src="build/libwabt.js"></script>
<script src="build/libwabt.cjs"></script>
<script type="module" src="src/browser/main.js"></script>
<link rel="stylesheet" href="v86.css">
@ -136,17 +87,22 @@
<tr>
<td><label for="floppy_image">Floppy disk image</label></td>
<td> <input type="file" id="floppy_image"><br></td>
<td><input type="file" id="fda_image"> or <a id="fda_toggle_empty_disk">create empty floppy disk</a><br></td>
</tr>
<tr>
<td><label for="floppy_image">Second floppy disk image</label></td>
<td><input type="file" id="fdb_image"> or <a id="fdb_toggle_empty_disk">create empty floppy disk</a><br></td>
</tr>
<tr>
<td><label for="hda_image">Hard disk image</label></td>
<td><input type="file" id="hda_image"><br></td>
<td><input type="file" id="hda_image"> or <a id="hda_toggle_empty_disk">create empty hard disk</a><br></td>
</tr>
<tr>
<td>Second hard disk image</td>
<td><input type="file" id="hdb_image"><br></td>
<td><input type="file" id="hdb_image"> or <a id="hdb_toggle_empty_disk">create empty hard disk</a><br></td>
</tr>
<tr>
@ -168,6 +124,11 @@
<td><input type="file" id="bios"><br></td>
</tr>
<tr>
<td><label for="vga_bios">VGA BIOS</label></td>
<td><input type="file" id="vga_bios"><br></td>
</tr>
<tr>
<td colspan="2"><small>Disk images are not uploaded to the server</small><hr></td>
</tr>
@ -186,6 +147,10 @@
</td>
</tr>
<tr>
<td colspan="2"><hr></td>
</tr>
<tr>
<td><label for="relay_url">Networking proxy (leave blank to disable)</label></td>
<td>
@ -193,6 +158,24 @@
</td>
</tr>
<tr>
<td><label for="net_device_type">Network Device Type</label></td>
<td>
<select id="net_device_type">
<option value="ne2k">ne2k</option>
<option value="virtio">virtio</option>
</select>
</td>
</tr>
<tr>
<td><label for="mtu">Network MTU (only used for virtio)</label></td>
<td>
<input id="mtu" type="number" value="1500" min="68" max="65535" step="1"> B<br>
</td>
</tr>
<tr>
<td colspan="2"><hr></td>
</tr>
@ -256,6 +239,8 @@
<input type="button" value="Get second hard disk image" id="get_hdb_image">
<input type="button" value="Get CD-ROM image" id="get_cdrom_image">
<input type="button" value="Insert floppy image" id="change_fda_image">
<input type="button" value="Insert second floppy image" id="change_fdb_image">
<input type="button" value="Insert CD image" id="change_cdrom_image">
<input type="button" value="Save State" id="save_state">
<input type="button" value="Load State" id="load_state"> <input type="file" style="display: none" id="load_state_input">
<input type="button" value="Memory Dump" id="memory_dump">
@ -265,13 +250,13 @@
<input type="button" value="Go fullscreen" id="fullscreen">
<input type="button" value="Take screenshot" id="take_screenshot">
<input type="button" value="Mute" id="mute">
<input type="button" value="Enable theatre mode" id="toggle_theatre">
<input type="button" style="display: none" value="Enable zoom to fit" id="toggle_zoom_to_fit">
<label>
Scale:
<input type="number" min="0.25" step="0.25" value="1.0" id="scale" style="width: 50px">
</label>
<br>
</div>
<pre style="margin: 0" id="log_levels"></pre>
@ -345,3 +330,6 @@
</textarea>
<div id="terminal"></div>
<input type="button" value="Hide UI" id="toggle_ui" style="display: none">
<div id="theatre_background" style="display: none"></div>

View file

@ -1,7 +1,34 @@
A 9p filesystem is supported by v86, using a virtio transport. Using
it, files can be exchanged with the guest OS, see `create_file` and `read_file`
in [`starter.js`](https://github.com/copy/v86/blob/master/src/browser/starter.js).
It can be enabled by passing the following options to `V86`:
A 9p filesystem is supported by v86, using a virtio transport. There are several
ways it can be set up.
### Guest mount
In all cases, the filesystem is mounted in the guest system using the `9p`
filesystem type and the `host9p` device tag. Typically you want to be specific
with the version and transport options:
```sh
mount -t 9p -o trans=virtio,version=9p2000.L host9p /mnt/9p/
```
Here are kernel arguments you can use to boot directly off the 9p filesystem:
```
rw root=host9p rootfstype=9p rootflags=trans=virtio,version=9p2000.L
```
The `aname` option can be used to pick the directory from 9p to mount. The `rw`
argument makes this a read-write root filesystem.
### JSON/HTTP Filesystem
This is the standard way to setup the 9p filesystem. It loads files over
HTTP on-demand into an in-memory filesystem in JS. This allows files to be
exchanged with the guest OS. See `create_file` and `read_file` in
[`starter.js`](https://github.com/copy/v86/blob/master/src/browser/starter.js).
This mode is enabled by passing the following options to `V86`:
```javascript
filesystem: {
@ -11,15 +38,60 @@ filesystem: {
```
Here, `basefs` is a json file created using
[fs2json](https://github.com/copy/fs2json). The base url is the prefix of a url
from which the files are available. For instance, if the 9p filesystem has a
file `/bin/sh`, that file must be accessible from
`http://localhost/9p/base/bin/sh`. If `basefs` and `baseurl` are omitted, an
empty 9p filesystem is created.
[fs2json.py](tools/fs2json.py) and the `baseurl` directory is created using
[copy-to-sha256.py](tools/copy-to-sha256.py).
The `mount_tag` of the 9p device is `host9p`. In order to mount it in the
guest, use:
If `basefs` and `baseurl` are omitted, an empty 9p filesystem is created. Unless
you configure one of the alternative modes.
```sh
mount -t 9p host9p /mnt/9p/
### Function Handler
You can handle 9p messages directly in JavaScript yourself by providing a
function as `handle9p` under `filesystem`:
```javascript
filesystem: {
handle9p: async (reqBuf, reply) => {
// reqBuf is a Uint8Array of the entire request frame.
// you can parse these bytes using a library or reading the 9p spec.
// once you formulate a response, you send the reply frame as a
// Uint8Array by passing it to reply: reply(respBuf)
}
}
```
This allows you to implement a 9p server or custom proxy in JS. However, this
filesystem will not be cached (unless cached in the guest OS), functions like
`create_file` and `read_file` will not be available, and you will be responsible
for keeping its state in sync with any machine save states.
### WebSocket Proxy
You can also back the 9p virtio filesystem with a 9p server over WebSocket by
providing a WS proxy URL:
```javascript
filesystem: {
proxy_url: "ws://localhost:8080/"
}
```
Simlar to using `handle9p`, this filesystem will not be available in JS and
will need to be re-mounted after restoring state.
The WS proxy just needs to hand off messages with a connection to a normal 9p
server. Each binary WebSocket message is the full buffer of a request or a
reply.
To implement, request message bytes can just be sent directly to the 9p
connection, but the 9p reply stream needs to be buffered into a single binary
WebSocket message. The proxy must at least parse the first 4 bytes to get the
message size and use it to buffer a full message before sending over WebSocket.
The [wanix](https://github.com/tractordev/wanix) CLI has a `serve` command that
not only serves a directory over HTTP, but also over 9P via WebSocket. You can
see how it [implements a proxy][1] in Go.
[1]: https://github.com/tractordev/wanix/blob/main/cmd/wanix/serve.go#L117-L177

View file

@ -62,6 +62,7 @@ Backends `fetch` and `wisp` support a couple of special settings in `config.net_
| **dns_method** | str | DNS method to use, either `static` or `doh`. `static`: use built-in DNS server, `doh`: use [DNS-over-HTTPS](https://en.wikipedia.org/wiki/DNS_over_HTTPS) (DoH). Defaults to `static` for `fetch` and to `doh` for `wisp` backend. |
| **doh_server** | str | Host name or IP address (and optional port number) of the DoH server if `dns_method` is `doh`. The value is expanded to the URL `https://DOH_SERVER/dns-query`. Default: `cloudflare-dns.com`. |
| **cors_proxy** | str | CORS proxy server URL, do not use a proxy if undefined. Default: undefined (`fetch` backend only). |
| **mtu** | int | The MTU used for the virtual network. Increasing it can improve performance. This only works if the NIC type is `virtio`. Default: `1500` |
#### Example `net_device` settings
@ -128,6 +129,7 @@ Since this backend (including its proxy server) only forwards unmodified etherne
* **[go-websockproxy](https://github.com/gdm85/go-websockproxy)** -- one TAP device for all clients, written in Go, without integraded DHCP but with integrated TLS support
* **[node-relay](https://github.com/krishenriksen/node-relay)** -- like websockproxy but written for NodeJS (dnsmasq/no TLS), see [New websocket ethernet switch built using Node.js #777](https://github.com/copy/v86/discussions/777)
* **[wsnic](https://github.com/chschnell/wsnic)** -- uses a single bridge and one TAP device per client, integrates dnsmasq for DHCP/DNS and stunnel for TLS
* **[RootlessRelay](https://github.com/obegron/rootlessRelay)** -- uses its own network stack that doesn't require TUN/TAP devices, has a built-in reverse proxy and admin interface, see [RootlessRelay #1442](https://github.com/copy/v86/discussions/1442)
[See here](https://github.com/copy/v86/discussions/1199#discussioncomment-12026845) for a benchmark comparing the download performance of these proxy servers.
@ -162,6 +164,8 @@ Starting with PR [#1233](https://github.com/copy/v86/pull/1233), the TCP guest l
v86 guests are isolated from each other when using the `fetch` backend.
v86 guests have HTTP access to the host's `localhost` using the URL `http://<port>.external` (e.g. `1234.external` -> `localhost:1234`).
**CORS proxy server**
* **[cors-anywhere](https://github.com/Rob--W/cors-anywhere)** -- NodeJS

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 -hda hdd.img
qemu-system-i386 -m 128 -M pc,acpi=off -drive file=hdd.img,format=raw
```
- add `-cdrom /path/to/installCD.iso`, if you use a CD version.
- add `-fda /path/to/boot_floppy.img -boot a`, if you use a floppy version or your install CD is non-bootable.
@ -23,16 +23,46 @@ qemu-system-i386 -m 128 -M pc,acpi=off -hda hdd.img
4. To change floppy disk, press *Ctrl+Alt+2* to switch to the QEMU Monitor, run `change floppy0 /path/to/new_floppy_image` and press *Ctrl+Alt+1* to switch to VGA.
5. Follow the installation guide on the screen.
6. (optionally) If "Windows protection" errors appears on startup, apply [FIX95CPU](http://lonecrusader.x10host.com/fix95cpu.html) or [patcher9x](https://github.com/JHRobotics/patcher9x#installation).
> [!TIP]
> For transfer files from host to guest, use [genisoimage](https://wiki.debian.org/genisoimage) ([UltraISO](https://www.ultraiso.com/) and [PowerISO](https://www.poweriso.com/) for Windows and Mac) for creating CD-ISO image or [dosfstools](https://github.com/dosfstools/dosfstools) ([WinImage](https://www.winimage.com/download.htm) for Windows) for creating floppy disk images, then mount the created image to QEMU.
## Troubleshooting
### "Windows protection" errors during startup
Apply [FIX95CPU](http://lonecrusader.x10host.com/fix95cpu.html) or [patcher9x](https://github.com/JHRobotics/patcher9x#installation).
### "VFBACKUP could no load VFD.VXD" on startup (Windows 95)
**Workaround #1**:
*Source: [#1185](https://github.com/copy/v86/issues/1185)*
1. Mount the installation CD (or `Disk 3` for the RTM version on floppy disks).
2. Open the "MS-DOS prompt" and run:
For the CD version:
```bat
extract /a /l C:\Windows\System <cd-rom letter>:\WIN95\WIN95_02.CAB vfd.vxd
```
For the floppy version:
```bat
extract /a /l C:\Windows\System <floppy drive letter>:\WIN95_03.CAB vfd.vxd
```
**Workaround #2**:
*Source: [#289](https://github.com/copy/v86/issues/289)*
1. Open the Start menu, click on "Run" and run `sysedit`.
2. Find `C:\AUTOEXEC.BAT` and add `smartdrv` to the top of the file.
3. Press File -> Save.
## Floppy disk support
Currently, the floppy drive in v86 works only with MS-DOS compatibility mode.
To check this: open the Start menu, click on "Control Panel" and "System", select "Performance" tab.
To check this: open the Start menu, click on "Control Panel" and "System", select "Performance" tab.
If it says *"Drive A is using MS-DOS compatibility mode file system"*, the floppy drive should work properly in v86. If not, try this solution:
1. Click on "Device Manager" in "System Properties".
@ -41,10 +71,12 @@ If it says *"Drive A is using MS-DOS compatibility mode file system"*, the flopp
## Enabling True Color (32 bpp)
The default VGA display driver only supports 640x480x8 video mode, to fix this, install **Universal VBE9x Video Display Driver**.
The default VGA display driver only supports 640x480x4 video mode, to fix this, you can install **Universal VBE9x Video Display Driver** or **VMDisp9x**.
### Universal VBE9x Video Display Driver
> [!WARNING]
> After installing, DOS Mode (and other programs and games that require it) may not work properly.
> After installing, DOS Mode (and other programs and games that require it) may not work properly.
> This is a problem in VBE9x, not v86, see [#110](https://github.com/copy/v86/issues/110).
> Also, this driver doesn't support DirectX, DirectDraw and OpenGL.
@ -56,6 +88,19 @@ The default VGA display driver only supports 640x480x8 video mode, to fix this,
6. Select "VBE Miniport" adapter, press "OK" and "Next".
7. After installing, restart Windows.
### VMDisp9x (Windows 95)
> [!WARNING]
> This driver can run DOS Mode with some graphical glitches. However, DirectX and/or DirectDraw may not work properly with this driver.
> Also, this driver doesn't support OpenGL.
1. Download `vmdisp9x-<...>-driver-2d.img` from https://github.com/JHRobotics/vmdisp9x/releases.
2. Mount as floppy image, right-click on the Desktop, click on "Properties".
3. Click "Advanced" > "Adapter" > "Change".
4. Press "Have Disk...", click "Browse" and go to the floppy.
5. Select "VESA ISA" adapter and press "OK".
6. After installing, restart Windows.
## CPU idling on Windows 95
See about [installing AmnHLT](cpu-idling.md#windows-9x-using-amnhlt).
@ -64,43 +109,43 @@ See about [installing AmnHLT](cpu-idling.md#windows-9x-using-amnhlt).
1. Open the Start menu, click on "Control Panel" and "Add New Hardware".
2. Press "Next", select "No" and select next options:
```
Hardware type: Network adapters
Manufacturers: Novell
Models: NE2000 Compatible
```
| Option | Value |
|:--------------|:------------------|
| Hardware type | Network adapters |
| Manufacturers | Novell |
| Models | NE2000 Compatible |
3. Press "Next" and restart Windows.
4. After restarting, right-click on "My computer", select "Propeties".
5. Open "Device Manager" tab, select "NE2000 Compatible" (in "Network adapters") and press "Properties"
6. Open "Resources", change values by selecting the properties and click on "Change Setting":
```
Interrupt Request: 10
Input/Output Range: 0300 - 031F
```
| Option | Value |
|:-------------------|:------------|
| Interrupt Request | 10 |
| Input/Output Range | 0300 - 031F |
7. In "Control Panel", open "Network", click on "Add", choose "Protocol" and select the following options:
```
Manufacturers: Microsoft
Network Protocols: TCP/IP
```
| Option | Value |
|:------------------|:----------|
| Manufacturers | Microsoft |
| Network Protocols | TCP/IP |
8. (optionally) Set "Primary Network Logon" to `Windows Logon`.
## Enabling sound manually
> [!NOTE]
> If you don't have an install CD, use the Sound Blaster 16 driver from https://www.claunia.com/qemu/drivers/index.html.
> If you don't have an install CD, use the Sound Blaster 16 driver from https://web.archive.org/web/20210814023225/https://www.claunia.com/qemu/drivers/index.html (unpack `sbw9xup.exe` as a zip archive).
1. Open "Start" menu, click on "Control Panel" and "Add New Hardware".
2. Press "Next", select "No" and select the following options:
```
Hardware type: Sound, video and game cotrollers
Manufacturers: Creative Labs
Models: Creative Labs Sound Blaster 16 or AWE-32
```
| Option | Value |
|:--------------|:-----------------------------------------|
| Hardware type | Sound, video and game cotrollers |
| Manufacturers | Creative Labs |
| Models | Creative Labs Sound Blaster 16 or AWE-32 |
3. Restart Windows.

View file

@ -17,14 +17,14 @@
3. Run QEMU with the following settings for installation:
```sh
qemu-system-i386 -m 64 -hda hdd.img -cpu pentium -M pc,acpi=off -cdrom InstallCD.iso
qemu-system-i386 -m 64 -drive file=hdd.img,format=raw -cpu pentium -M pc,acpi=off -cdrom InstallCD.iso
```
4. Run `xcopy /v <CD-ROM letter>:\I386\ C:\install\` in a VM to copy all files, disable the CD-ROM driver.
5. Run QEMU with the following settings:
5. Run QEMU with the following settings:
```sh
qemu-system-i386 -m 64 -hda hdd.img -cpu pentium -M pc,acpi=off
qemu-system-i386 -m 64 -drive file=hdd.img,format=raw -cpu pentium -M pc,acpi=off
```
6. Run `C:\install\winnt /F /C` in a VM.
@ -33,7 +33,7 @@ qemu-system-i386 -m 64 -hda hdd.img -cpu pentium -M pc,acpi=off
## Windows NT 3.51
### Installing
### Installing
> [!NOTE]
> In newer versions of QEMU, the Windows Setup may not work, you can use an older version of QEMU, PCem, 86Box or PCBox instead.
@ -64,10 +64,10 @@ Recommended version: Windows NT 4.0 SP1
### Installing using QEMU
1. Run QEMU with the following settings for installation:
1. Run QEMU with the following settings for installation:
```sh
qemu-system-i386 -m 64 -hda hdd.img -cdrom InstallCD.iso -cpu pentium -M pc,acpi=off
qemu-system-i386 -m 64 -drive file=hdd.img,format=raw -cdrom InstallCD.iso -cpu pentium -M pc,acpi=off
```
2. On setup startup, press F5 and select "Standard PC".
@ -90,10 +90,10 @@ var emulator = new V86({
### Installing using QEMU
1. Run QEMU with the following settings for installation:
1. Run QEMU with the following settings for installation:
```sh
qemu-system-i386 -m 512 -hda hdd.img -cdrom InstallCD.iso
qemu-system-i386 -m 512 -drive file=hdd.img,format=raw -cdrom InstallCD.iso
```
Optional:
@ -107,7 +107,7 @@ After installation, change the computer type to "Standard PC" as described [here
1. Open Start menu, right-click on "My Computer", select "Manage"
2. Open Device Manager, open Computer, right-click on "ACPI Uniprocessor PC"
3. Select "Update Driver..." > "No, not this time"
4. Select "Install from a list or specific location (Advanced)" > Next > "Don't search. I will choose the driver to install."
4. Select "Install from a list or specific location (Advanced)" > Next > "Don't search. I will choose the driver to install."
5. Choose "Standard PC", press Next > Finish.
6. Restart the VM, follow multiple "Found New Hardware Wizard" dialogs with default options.
@ -145,10 +145,10 @@ Models: Sound Blaster 16 or AWE32 or compatible (WDM)
### Installing using QEMU
1. Run QEMU with the following settings for installation:
1. Run QEMU with the following settings for installation:
```sh
qemu-system-i386 -m 1024 -hda hdd.img -cdrom InstallCD.iso
qemu-system-i386 -m 1024 -drive file=hdd.img,format=raw -cdrom InstallCD.iso
```
Optionally add `-accel kvm` (for Linux host), `-accel whpx` (for Windows host) or `-accel hvf` (for MacOS host) to use hypervisor acceleration.

View file

@ -1,5 +1,54 @@
export default [
{
"languageOptions": {
"globals": {
"process": "readonly",
"window": "writable",
"navigator": "writable",
"location": "writable",
"document": "readonly",
"console": "readonly",
"crypto": "readonly",
"alert": "readonly",
"performance": "readonly",
"URL": "readonly",
"WebAssembly": "readonly",
"setTimeout": "readonly",
"setInterval": "readonly",
"clearTimeout": "readonly",
"clearInterval": "readonly",
"requestAnimationFrame": "readonly",
"cancelAnimationFrame": "readonly",
"Buffer": "readonly",
"FileReader": "readonly",
"TextEncoder": "readonly",
"TextDecoder": "readonly",
"fetch": "readonly",
"Headers": "readonly",
"Response": "readonly",
"WebSocket": "readonly",
"Blob": "readonly",
"File": "readonly",
"XMLHttpRequest": "readonly",
"URLSearchParams": "readonly",
"ImageData": "readonly",
"Image": "readonly",
"OffscreenCanvas": "readonly",
"BroadcastChannel": "readonly",
"AudioContext": "readonly",
"AudioWorkletProcessor": "readonly",
"webkitAudioContext": "readonly",
"AudioWorkletNode": "readonly",
"Worker": "readonly",
"postMessage": "readonly",
"importScripts": "readonly",
"DEBUG": "writable"
}
},
rules: {
"eol-last": "error",
//"no-extra-parens": "error",
@ -69,7 +118,7 @@ export default [
"no-shadow-restricted-names": "error",
"no-sparse-arrays": "error",
"no-this-before-super": "error",
//"no-undef": "error",
"no-undef": "error",
"no-unexpected-multiline": "error",
//"no-unreachable": "error",
"no-unsafe-finally": "error",
@ -84,7 +133,8 @@ export default [
"no-with": "error",
"require-yield": "error",
"use-isnan": "error",
"valid-typeof": "error"
"valid-typeof": "error",
"strict": "error"
}
}
];

View file

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

View file

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

View file

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

View file

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

View file

@ -1,16 +1,19 @@
#!/usr/bin/env node
"use strict";
const assert = require("assert").strict;
const fs = require("fs");
const path = require("path");
const x86_table = require("./x86_table");
const rust_ast = require("./rust_ast");
const { hex, mkdirpSync, get_switch_value, get_switch_exist, finalize_table_rust } = require("./util");
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import url from "node:url";
import x86_table from "./x86_table.js";
import * as rust_ast from "./rust_ast.js";
import { hex, get_switch_value, get_switch_exist, finalize_table_rust } from "./util.js";
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
const OUT_DIR = path.join(__dirname, "..", "src/rust/gen/");
mkdirpSync(OUT_DIR);
fs.mkdirSync(OUT_DIR, { recursive: true });
const table_arg = get_switch_value("--table");
const gen_all = get_switch_exist("--all");

View file

@ -1,16 +1,19 @@
#!/usr/bin/env node
"use strict";
const assert = require("assert").strict;
const fs = require("fs");
const path = require("path");
const x86_table = require("./x86_table");
const rust_ast = require("./rust_ast");
const { hex, mkdirpSync, get_switch_value, get_switch_exist, finalize_table_rust } = require("./util");
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import url from "node:url";
import x86_table from "./x86_table.js";
import * as rust_ast from "./rust_ast.js";
import { hex, get_switch_value, get_switch_exist, finalize_table_rust } from "./util.js";
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
const OUT_DIR = path.join(__dirname, "..", "src/rust/gen/");
mkdirpSync(OUT_DIR);
fs.mkdirSync(OUT_DIR, { recursive: true });
const table_arg = get_switch_value("--table");
const gen_all = get_switch_exist("--all");
@ -210,8 +213,9 @@ function gen_instruction_body_after_prefix(encodings, size)
}),
default_case: {
varname: "x",
body: [
`if DEBUG { panic!("Bad instruction at {:x}", *instruction_pointer); }`,
`dbg_log!("#ud ${encoding.opcode.toString(16).toUpperCase()}/{} at {:x}", x, *instruction_pointer);`,
"trigger_ud();",
],
}
@ -410,7 +414,7 @@ function gen_table()
"use crate::cpu::cpu::{after_block_boundary, modrm_resolve};",
"use crate::cpu::cpu::{read_imm8, read_imm8s, read_imm16, read_imm32s, read_moffs};",
"use crate::cpu::cpu::{task_switch_test, trigger_ud, DEBUG};",
"use crate::cpu::cpu::{task_switch_test, trigger_ud};",
"use crate::cpu::instructions;",
"use crate::cpu::global_pointers::{instruction_pointer, prefixes};",
"use crate::prefix;",
@ -475,7 +479,6 @@ function gen_table()
"use crate::cpu::cpu::{after_block_boundary, modrm_resolve};",
"use crate::cpu::cpu::{read_imm8, read_imm16, read_imm32s};",
"use crate::cpu::cpu::{task_switch_test, task_switch_test_mmx, trigger_ud};",
"use crate::cpu::cpu::DEBUG;",
"use crate::cpu::instructions_0f;",
"use crate::cpu::global_pointers::{instruction_pointer, prefixes};",
"use crate::prefix;",

View file

@ -1,16 +1,19 @@
#!/usr/bin/env node
"use strict";
const assert = require("assert").strict;
const fs = require("fs");
const path = require("path");
const x86_table = require("./x86_table");
const rust_ast = require("./rust_ast");
const { hex, mkdirpSync, get_switch_value, get_switch_exist, finalize_table_rust } = require("./util");
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import url from "node:url";
import x86_table from "./x86_table.js";
import * as rust_ast from "./rust_ast.js";
import { hex, get_switch_value, get_switch_exist, finalize_table_rust } from "./util.js";
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
const OUT_DIR = path.join(__dirname, "..", "src/rust/gen/");
mkdirpSync(OUT_DIR);
fs.mkdirSync(OUT_DIR, { recursive: true });
const table_arg = get_switch_value("--table");
const gen_all = get_switch_exist("--all");

View file

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

View file

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

View file

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

View file

@ -2,10 +2,13 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>v86</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no,interactive-widget=resizes-content">
<meta name="description" content="Run KolibriOS, Linux or Windows 98 in your browser">
<script src="build/v86_all.js"></script>
<link rel="manifest" href="manifest.json">
<link rel="apple-touch-icon" href="192.png">
<script src="build/v86_all.js?98e7110c2"></script>
<link rel="stylesheet" href="v86.css">
<div>
@ -54,66 +57,121 @@
<label><input type=checkbox> contains: demos</label>
<label><input type=checkbox> contains: compilers</label>
<label><input type=checkbox> contains: web browsers</label>-->
<div style="text-align: right; float: right"><a href="#setup">Skip to custom settings</a></div>
<div style="text-align: right; float: right">
<a id="reset_filters">Reset filters</a> / <a href="#setup">Skip to custom settings</a>
</div>
<br style="clear: both">
</div>
<hr>
<table id="oses">
<thead>
<tr>
<th>Name</th>
<th>Size</th>
<th>UI</th>
<th>Family</th>
<th>Arch</th>
<th>Status</th>
<th>Source</th>
<th>Lang</th>
<th>Medium</th>
<th>Notes</th>
</tr>
</thead>
<tr id="start_archlinux"><td><a href="?profile=archlinux">Arch Linux</a> <td>15+ MB</td>
<td><span class=tui_icon></span></td> <td>Linux</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>C</td> <td>9pfs</td> <td>Xorg, Firefox, various compilers and more</td></tr>
<tr id="start_dsl"><td><a href="?profile=dsl">Damn Small Linux</a> <td>50 MB</td>
<td><span class=gui_icon></span></td> <td>Linux</td> <td>32-bit</td> <td>Historic</td> <td>Open-source</td> <td>C</td> <td>CD</td> <td>4.11.rc2 with Firefox 2.0</td></tr>
<tr id="start_buildroot"><td><a href="?profile=buildroot">Buildroot Linux</a> <td>4.9 MB</td>
<td><span class=tui_icon></span></td> <td>Linux</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>C</td> <td>bzImage</td> <td>Lua, ping, curl, telnet</td></tr>
<tr id="start_freebsd"><td><a href="?profile=freebsd">FreeBSD</a> <td>16+ MB</td>
<td><span class=tui_icon></span></td> <td>BSD</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>C</td> <td>HD</td> <td>FreeBSD 12.0</td></tr>
<tr id="start_openbsd"><td><a href="?profile=openbsd">OpenBSD</a> <td>11+ MB</td>
<td><span class=tui_icon></span></td> <td>BSD</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>C</td> <td>HD</td> <td>OpenBSD 6.6</td></tr>
<tr id="start_fiwix"><td><a href="?profile=fiwix">FiwixOS</a> <td>15+ MB</td>
<td><span class=tui_icon></span></td> <td>Unix-like</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>C</td> <td>HD</td> <td>With Doom</td></tr>
<tr id="start_serenity"><td><a href="?profile=serenity">SerenityOS</a> <td>16+ MB</td>
<td><span class=gui_icon></span></td> <td>Unix-like</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>C++</td> <td>HD</td> <td>Web browser, various games and demos</td></tr>
<tr id="start_haiku"><td><a href="?profile=haiku">Haiku</a> <td>41+ MB</td>
<td><span class=gui_icon></span></td> <td>BeOS</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>C++</td> <td>HD</td> <td>Networking (WebPositive), OCaml, 2048, NetHack</td></tr>
<tr id="start_tinyaros"><td><a href="?profile=tinyaros">Tiny Aros</a> <td>17+ MB</td>
<td><span class=gui_icon></span></td> <td>AmigaOS</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>C</td> <td>CD</td> <td>AmigaOS-like graphical OS</td></tr>
<tr id="start_reactos"><td><a href="?profile=reactos">ReactOS</a> <td>17+ MB</td>
<td><span class=gui_icon></span></td> <td>Windows-like</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>C++</td> <td>HD</td> <td>QtWeb, LBreakout2, OpenTTD, Bochs, TCC</td></tr>
<tr id="start_windows1"><td><a href="?profile=windows1">Windows 1.01</a> <td>0.7 MB</td>
<td><span class=gui_icon></span></td> <td>Windows</td> <td>16-bit</td> <td>Historic</td> <td>Proprietary</td> <td>ASM, C</td> <td>Floppy</td> <td>Reversi, Paint</td></tr>
<tr id="start_windows95"><td><a href="?profile=windows95">Windows 95</a> <td>19+ MB</td>
<td><span class=gui_icon></span></td> <td>Windows</td> <td>32-bit</td> <td>Historic</td> <td>Proprietary</td> <td>ASM, C</td> <td>HD</td> <td>Age of Empires, FASM, POV-Ray, Hover!</td></tr>
<tr id="start_windows2000"><td><a href="?profile=windows2000">Windows 2000</a> <td>23+ MB</td>
<td><span class=gui_icon></span></td> <td>Windows</td> <td>32-bit</td> <td>Historic</td> <td>Proprietary</td> <td>C++</td> <td>HD</td> <td>IE 5, Pinball</td></tr>
<tr id="start_msdos"><td><a href="?profile=msdos">MS-DOS 6.22</a> <td>2.4+ MB</td>
<td><span class=tui_icon></span></td> <td>DOS</td> <td>16-bit</td> <td>Historic</td> <td>Proprietary</td> <td>ASM</td> <td>HD</td> <td>Doom, Sim City, OCaml 1.0, Turbo C and more</td></tr>
<tr id="start_freedos"><td><a href="?profile=freedos">FreeDOS</a> <td>0.6 MB</td>
<td><span class=tui_icon></span></td> <td>DOS</td> <td>16-bit</td> <td>Modern</td> <td>Open-source</td> <td>ASM, C</td> <td>Floppy</td> <td>nasm, vim, debug.com, Rogue, various demos</td></tr>
<tr id="start_kolibrios"><td><a href="?profile=kolibrios">KolibriOS</a> <td>1.3 MB</td>
<td><span class=gui_icon></span></td> <td>Custom</td> <td>32-bit</td> <td>Modern</td> <td>Open-source</td> <td>ASM</td> <td>Floppy</td> <td>Various apps, games and demos</td></tr>
<tr id="start_qnx"><td><a href="?profile=qnx">QNX 4.05</a> <td>1.4 MB</td>
<td><span class=gui_icon></span></td> <td>Custom</td> <td>32-bit</td> <td>Historic</td> <td>Proprietary</td> <td>C</td> <td>Floppy</td> <td>1999 demo disk</td></tr>
</table>
<div class="table" id="oses">
<div class="th">
<div class="tr">
<span>Name</span>
<span>Size</span>
<span>UI</span>
<span>Family</span>
<span>Arch</span>
<span>Status</span>
<span>Source</span>
<span>Lang</span>
<span>Medium</span>
<span>Notes</span>
</div>
</div>
<a class="tr" href="?profile=android" id="start_android"><span>Android</span> <span>54+ MB</span> <span><div class=gui_icon></div></span> <span>Linux</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C++</span> <span>CD</span> <span>Android x86 1.6-r2</span></a>
<a class="tr" href="?profile=archlinux" id="start_archlinux"><span>Arch Linux</span> <span>15+ MB</span> <span><div class=tui_icon></div></span> <span>Linux</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>9pfs</span> <span>Various packages, including Xorg, Firefox and more</span></a>
<a class="tr" href="?profile=buildroot" id="start_buildroot"><span>Buildroot Linux</span> <span>4.9 MB</span> <span><div class=tui_icon></div></span> <span>Linux</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>bzImage</span> <span>Minimal Linux with Lua, ping, curl, telnet</span></a>
<a class="tr" href="?profile=dsl" id="start_dsl"><span>Damn Small Linux</span> <span>50 MB</span> <span><div class=gui_icon></div></span> <span>Linux</span> <span>32-bit</span> <span>Historic</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>4.11.rc2 with Firefox 2.0</span></a>
<a class="tr" href="?profile=elks" id="start_elks"><span>ELKS</span> <span>1.2 MB</span> <span><div class=tui_icon></div></span> <span>Linux-like</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>Linux for 8086</span></a>
<a class="tr" href="?profile=nodeos" id="start_nodeos"><span>NodeOS</span> <span>14 MB</span> <span><div class=tui_icon></div></span> <span>Linux</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>bzImage</span> <span>Linux with nodejs as /bin/init</span></a>
<a class="tr" href="?profile=tilck" id="start_tilck"><span>Tilck</span> <span>16 MB</span> <span><div class=tui_icon></div></span> <span>Linux-like</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>Tiny Linux-Compatible Kernel</span></a>
<a class="tr" href="?profile=freebsd" id="start_freebsd"><span>FreeBSD</span> <span>16+ MB</span> <span><div class=tui_icon></div></span> <span>BSD</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>FreeBSD 12.0</span></a>
<a class="tr" href="?profile=netbsd" id="start_netbsd"><span>NetBSD</span> <span>23+ MB</span> <span><div class=tui_icon></div></span> <span>BSD</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>NetBSD 4.0 with Xorg</span></a>
<a class="tr" href="?profile=openbsd" id="start_openbsd"><span>OpenBSD</span> <span>11+ MB</span> <span><div class=tui_icon></div></span> <span>BSD</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>OpenBSD 6.6</span></a>
<a class="tr" href="?profile=fiwix" id="start_fiwix"><span>FiwixOS</span> <span>4.2+ MB</span> <span><div class=tui_icon></div></span> <span>Unix-like</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>With Doom</span></a>
<a class="tr" href="?profile=minix" id="start_minix"><span>Minix</span> <span>30+ MB</span> <span><div class=tui_icon></div></span> <span>Unix-like</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>Minix 3.3</span></a>
<a class="tr" href="?profile=redox" id="start_redox"><span>Redox</span> <span>31+ MB</span> <span><div class=gui_icon></div></span> <span>Unix-like</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>Rust</span> <span>HD</span> <span>A Unix-like microkernel OS written in Rust</span></a>
<a class="tr" href="?profile=serenity" id="start_serenity"><span>SerenityOS</span> <span>16+ MB</span> <span><div class=gui_icon></div></span> <span>Unix-like</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C++</span> <span>HD</span> <span>Web browser, various games and demos</span></a>
<a class="tr" href="?profile=sortix" id="start_sortix"><span>Sortix</span> <span>67 MB</span> <span><div class=tui_icon></div></span> <span>Unix-like</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C++</span> <span>CD</span> <span>A small self-hosting Unix-like operating system</span></a>
<a class="tr" href="?profile=soso" id="start_soso"><span>Soso</span> <span>7.6 MB</span> <span><div class=gui_icon></div></span> <span>Unix-like</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span> A Simple Unix-like operating system</span></a>
<a class="tr" href="?profile=syllable" id="start_syllable"><span>Syllable</span> <span>28+ MB</span> <span><div class=gui_icon></div></span> <span>Unix-like</span> <span>32-bit</span> <span>Historic</span> <span>Open-source</span> <span>C++</span> <span>HD</span> <span>A user friendly, POSIX compatible OS</span></a>
<a class="tr" href="?profile=unix-v7" id="start_unix-v7"><span>Unix V7</span> <span>0.5+ MB</span> <span><div class=tui_icon></div></span> <span>Unix</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>C</span> <span>HD</span> <span>Unix V7 port for x86, including Amsterdam Compiler Kit</span></a>
<a class="tr" href="?profile=beos" id="start_beos"><span>BeOS 5</span> <span>34+ MB</span> <span><div class=gui_icon></div></span> <span>BeOS</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>C++</span> <span>HD</span> <span>With Opera 3.62, NetPositive</span></a>
<a class="tr" href="?profile=haiku" id="start_haiku"><span>Haiku</span> <span>41+ MB</span> <span><div class=gui_icon></div></span> <span>BeOS</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C++</span> <span>HD</span> <span>Networking (WebPositive), OCaml, 2048, NetHack</span></a>
<a class="tr" href="?profile=aros-broadway" id="start_aros-broadway"><span>AROS Broadway</span> <span>25+ MB</span> <span><div class=gui_icon></div></span> <span>AmigaOS</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>AmigaOS-like graphical OS</span></a>
<a class="tr" href="?profile=icaros" id="start_icaros"><span>Icaros Desktop</span> <span>60+ MB</span> <span><div class=gui_icon></div></span> <span>AmigaOS</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>AmigaOS-like graphical OS</span></a>
<a class="tr" href="?profile=tinyaros" id="start_tinyaros"><span>Tiny Aros</span> <span>17+ MB</span> <span><div class=gui_icon></div></span> <span>AmigaOS</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>AmigaOS-like graphical OS</span></a>
<a class="tr" href="?profile=9front" id="start_9front"><span>9front</span> <span>5.2+ MB</span> <span><div class=gui_icon></div></span> <span>Plan 9</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>An actively maintained fork of Plan 9</span></a>
<a class="tr" href="?profile=9legacy" id="start_9legacy"><span>9legacy</span> <span>13 MB</span> <span><div class=gui_icon></div></span> <span>Plan 9</span> <span>32-bit</span> <span>Historic</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>A set of patches based on the latest release of Plan 9</span></a>
<a class="tr" href="?profile=reactos" id="start_reactos"><span>ReactOS</span> <span>17+ MB</span> <span><div class=gui_icon></div></span> <span>Windows-like</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C++</span> <span>HD</span> <span>QtWeb, LBreakout2, OpenTTD, Bochs, TCC</span></a>
<a class="tr" href="?profile=windows1" id="start_windows1"><span>Windows 1.01</span> <span>0.7 MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>16-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM, C</span> <span>Floppy</span> <span>The first release version of Windows</span></a>
<a class="tr" href="?profile=windows2" id="start_windows2"><span>Windows 2.03</span> <span>1.8 MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>16-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM, C</span> <span>HD</span> <span>Reversi, Paint</span></a>
<a class="tr" href="?profile=windows30" id="start_windows30"><span>Windows 3.0</span> <span>6.5 MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>16-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM, C</span> <span>CD</span> <span>CorelDRAW! 2.0, Reversi</span></a>
<a class="tr" href="?profile=windows31" id="start_windows31"><span>Windows 3.1</span> <span>17 MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>16-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM, C</span> <span>HD</span> <span>QBasic, Minesweeper, Solitaire</span></a>
<a class="tr" href="?profile=windows95" id="start_windows95"><span>Windows 95</span> <span>19+ MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM, C</span> <span>HD</span> <span>Age of Empires, FASM, POV-Ray, Hover!</span></a>
<a class="tr" href="?profile=windows98" id="start_windows98"><span>Windows 98</span> <span>13+ MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM, C</span> <span>HD</span> <span>FreeCell, Hearts, sheep.exe, IE 5</span></a>
<a class="tr" href="?profile=windows-me" id="start_windows-me"><span>Windows ME</span> <span>14+ MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM, C</span> <span>HD</span> <span>Visual Basic, Office 97</span></a>
<a class="tr" href="?profile=windowsnt3" id="start_windowsnt3"><span>Windows NT 3.1</span> <span>18+ MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>C++</span> <span>HD</span> <span>The first retail version of Windows NT</span></a>
<a class="tr" href="?profile=windowsnt35" id="start_windowsnt35"><span>Windows NT 3.51</span> <span>28+ MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>C++</span> <span>HD</span> <span>Internet Explorer 3 and Visual FoxPro 3.0</span></a>
<a class="tr" href="?profile=windowsnt4" id="start_windowsnt4"><span>Windows NT 4.0</span> <span>16+ MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>C++</span> <span>HD</span> <span>Windows NT 4.0 Service Pack 1</span></a>
<a class="tr" href="?profile=windows2000" id="start_windows2000"><span>Windows 2000</span> <span>28+ MB</span> <span><div class=gui_icon></div></span> <span>Windows</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>C++</span> <span>HD</span> <span>IE 6, K-Meleon, Winamp, Delphi, NetHack and more</span></a>
<a class="tr" href="?profile=86dos" id="start_86dos"><span>86-DOS</span> <span>0.1 MB</span> <span><div class=tui_icon></div></span> <span>DOS</span> <span>16-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM</span> <span>Floppy</span> <span>86-DOS version 1.0</span></a>
<a class="tr" href="?profile=ibm-exploring" id="start_ibm-exploring"><span>Exploring IBM</span> <span>0.1 MB</span> <span><div class=tui_icon></div></span> <span>DOS</span> <span>16-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM</span> <span>Floppy</span> <span>Learning program on how to use a computer</span></a>
<a class="tr" href="?profile=freedos" id="start_freedos"><span>FreeDOS</span> <span>0.6 MB</span> <span><div class=tui_icon></div></span> <span>DOS</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM, C</span> <span>Floppy</span> <span>nasm, vim, debug.com, Rogue, various demos</span></a>
<a class="tr" href="?profile=freegem" id="start_freegem"><span>FreeGEM</span> <span>1.6+ MB</span> <span><div class=gui_icon></div></span> <span>DOS</span> <span>16-bit</span> <span>Historic</span> <span>Open-source</span> <span>ASM, C</span> <span>HD</span> <span>Graphical desktop for FreeDOS</span></a>
<a class="tr" href="?profile=xcom" id="start_xcom"><span>Xcom</span> <span>1.3 MB</span> <span><div class=gui_icon></div></span> <span>DOS</span> <span>16-bit</span> <span>Historic</span> <span>Open-source</span> <span>ASM, C</span> <span>Floppy</span> <span>Graphical desktop for FreeDOS</span></a>
<a class="tr" href="?profile=msdos4" id="start_msdos4"><span>MS-DOS 4</span> <span>0.5 MB</span> <span><div class=tui_icon></div></span> <span>DOS</span> <span>16-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM</span> <span>Floppy</span> <span>Contains EDLIN</span></a>
<a class="tr" href="?profile=msdos" id="start_msdos"><span>MS-DOS 6.22</span> <span>2.4+ MB</span> <span><div class=tui_icon></div></span> <span>DOS</span> <span>16-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM</span> <span>HD</span> <span>Doom, Sim City, OCaml 1.0, Turbo C and more</span></a>
<a class="tr" href="?profile=pcmos" id="start_pcmos"><span>PC-MOS/386</span> <span>0.7 MB</span> <span><div class=tui_icon></div></span> <span>DOS</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>ASM, C</span> <span>Floppy</span> <span>Multi-user and multi-tasking OS</span></a>
<a class="tr" href="?profile=psychdos" id="start_psychdos"><span>PsychDOS</span> <span>4.6+ MB</span> <span><div class=gui_icon></div></span> <span>DOS</span> <span>16-bit</span> <span>Historic</span> <span>Open-source</span> <span>ASM</span> <span>HD</span> <span>ANSI-like graphical desktop for FreeDOS</span></a>
<a class="tr" href="?profile=leetos" id="start_leetos"><span>lEEt/OS</span> <span>0.5 MB</span> <span><div class=tui_icon></div></span> <span>DOS</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM, C</span> <span>Floppy</span> <span>Graphical desktop for ST-DOS</span></a>
<a class="tr" href="?profile=bleskos" id="start_bleskos"><span>BleskOS</span> <span>0.2 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>Alternative OS for older PCs</span></a>
<a class="tr" href="?profile=bluejay" id="start_bluejay"><span>Blue Jay</span> <span>83 KB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Floppy</span> <span>Based on MikeOS</span></a>
<a class="tr" href="?profile=boneos" id="start_boneos"><span>BoneOS</span> <span>3.0 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>Simple hobby OS</span></a>
<a class="tr" href="?profile=bootchess" id="start_bootchess"><span>BootChess</span> <span>4.0 KB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Floppy</span> <span>Chess in a bootsector</span></a>
<a class="tr" href="?profile=catk" id="start_catk"><span>CatK</span> <span>3.2 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>Simple Unix-like kernel</span></a>
<a class="tr" href="?profile=crazierl" id="start_crazierl"><span>Crazierl</span> <span>11 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C, Erlang</span> <span>Multiboot</span> <span>An Erlang Operating System</span></a>
<a class="tr" href="?profile=duskos" id="start_duskos"><span>Dusk OS</span> <span>0.4 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>A 32-bit Forth</span></a>
<a class="tr" href="?profile=floppybird" id="start_floppybird"><span>Floppy Bird</span> <span>6.5 KB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>Floppy</span> <span>Flappy Bird game in a bootsector</span></a>
<a class="tr" href="?profile=helenos" id="start_helenos"><span>HelenOS</span> <span>7.9 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>A microkernel-based multiserver OS</span></a>
<a class="tr" href="?profile=hello-v86" id="start_hello-v86"><span>Hello v86</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>Small bootsector demo for v86</span></a>
<a class="tr" href="?profile=house" id="start_house"><span>House</span> <span>1.1 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>Haskell</span> <span>Floppy</span> <span>Haskell User's Operating System and Environment</span></a>
<a class="tr" href="?profile=jx" id="start_jx"><span>JX</span> <span>1.3 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>Java</span> <span>Floppy</span> <span>Java-based operating system</span></a>
<a class="tr" href="?profile=kolibrios" id="start_kolibrios"><span>KolibriOS</span> <span>1.3 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Floppy</span> <span>Various apps, games and demos</span></a>
<a class="tr" href="?profile=littlekernel" id="start_littlekernel"><span>Little Kernel</span> <span>0.4 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>Multiboot</span> <span>An embedded kernel designed for small systems</span></a>
<a class="tr" href="?profile=mcp" id="start_mcp"><span>M/CP</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>The Minimal Control Program</span></a>
<a class="tr" href="?profile=mikeos" id="start_mikeos"><span>MikeOS</span> <span>0.2 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>CD</span> <span>Contains a FORTH and BASIC interpreter and several games</span></a>
<a class="tr" href="?profile=mobius" id="start_mobius"><span>Mobius</span> <span>1.3 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>Floppy</span> <span>POSIX and Win32 compatible OS</span></a>
<a class="tr" href="?profile=mu" id="start_mu"><span>Mu</span> <span>0.2 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>Minimal-dependency hobbyist computing stack</span></a>
<a class="tr" href="?profile=nanoshell" id="start_nanoshell"><span>NanoShell</span> <span>3.5 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>Multi-tasked 32-bit OS with Win9x-like GUI</span></a>
<a class="tr" href="?profile=newos" id="start_newos"><span>NewOS</span> <span>0.6 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C++</span> <span>Floppy</span> <span>Cross-platform portable OS</span></a>
<a class="tr" href="?profile=nopeos" id="start_nopeos"><span>Nope OS</span> <span>92 KB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>Simple OS with BASIC interpreter</span></a>
<a class="tr" href="?profile=oberon" id="start_oberon"><span>Oberon</span> <span>1.6 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Historic</span> <span>Open-source</span> <span>Oberon</span> <span>HD</span> <span>Native Oberon 2.3.6</span></a>
<a class="tr" href="?profile=qnx" id="start_qnx"><span>QNX 4.05</span> <span>1.4 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>C</span> <span>Floppy</span> <span>1999 demo disk</span></a>
<a class="tr" href="?profile=sectorlisp" id="start_sectorlisp"><span>SectorLISP</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>Bootstrapping LISP in a Boot Sector</span></a>
<a class="tr" href="?profile=skift" id="start_skift"><span>Skift</span> <span>44 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C++</span> <span>CD</span> <span>A hobby OS built from scratch using C/C++</span></a>
<a class="tr" href="?profile=snowdrop" id="start_snowdrop"><span>Snowdrop</span> <span>0.4 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Floppy</span> <span>16-bit hobby OS with GUI</span></a>
<a class="tr" href="?profile=solos" id="start_solos"><span>Sol OS</span> <span>0.3 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Proprietary</span> <span>ASM</span> <span>Floppy</span> <span>Simple graphical OS</span></a>
<a class="tr" href="?profile=stillalive" id="start_stillalive"><span>Still Alive</span> <span>10 KB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>Floppy</span> <span>Bootable demo that plays "Still Alive" from Portal</span></a>
<a class="tr" href="?profile=t3xforth" id="start_t3xforth"><span>T3XFORTH</span> <span>59 KB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Historic</span> <span>Open-source</span> <span>ASM</span> <span>Floppy</span> <span>An old-school, plain vanilla FORTH system</span></a>
<a class="tr" href="?profile=tetros" id="start_tetros"><span>TetrOS</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>Tetris that fits into the boot sector</span></a>
<a class="tr" href="?profile=toaruos" id="start_toaruos"><span>ToaruOS</span> <span>6.3 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>A hobby OS written from scratch</span></a>
<a class="tr" href="?profile=bootbasic" id="start_bootbasic"><span>bootBASIC</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>A BASIC in 512 bytes of x86 machine code</span></a>
<a class="tr" href="?profile=pillman" id="start_pillman"><span>Pillman</span> <span>512 B</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>A yellow thing eats pills and is chased by monsters</span></a>
<a class="tr" href="?profile=bootlogo" id="start_bootlogo"><span>bootLogo</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>Logo language in 508 bytes</span></a>
<a class="tr" href="?profile=bootrogue" id="start_bootrogue"><span>bootRogue</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>A roguelike game that fits in a boot sector</span></a>
<a class="tr" href="?profile=dino" id="start_dino"><span>dino</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>Chrome's t-rex based bootsector game</span></a>
<a class="tr" href="?profile=invaders" id="start_invaders"><span>Invaders</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>Invaders in a bootsector</span></a>
<a class="tr" href="?profile=sanos" id="start_sanos"><span>Sanos</span> <span>0.5 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>Minimalistic 32-bit x86 OS</span></a>
<a class="tr" href="?profile=sectorforth" id="start_sectorforth"><span>sectorforth</span> <span>512 B</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>16-bit</span> <span>Modern</span> <span>Open-source</span> <span>ASM</span> <span>Bootsector</span> <span>16-bit x86 Forth in a bootsector</span></a>
<a class="tr" href="?profile=dancy" id="start_dancy"><span>Dancy</span> <span>1.3 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>Dancy Operating System</span></a>
<a class="tr" href="?profile=curios" id="start_curios"><span>CuriOS</span> <span>6.6 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>HD</span> <span>Simple GUI based OS inspired by AmigaOS</span></a>
<a class="tr" href="?profile=os64" id="start_os64"><span>OS64</span> <span>2.2 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>Commodore 64 emulator OS for x86 (slow)</span></a>
<a class="tr" href="?profile=netboot.xyz" id="start_netboot.xyz"><span>netboot.xyz</span> <span>1.0 MB</span> <span><div class=tui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C</span> <span>CD</span> <span>Netboot into various operating systems (slow)</span></a>
<a class="tr" href="?profile=squeaknos" id="start_squeaknos"><span>SqueakNOS</span> <span>20 MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Modern</span> <span>Open-source</span> <span>C, Smalltalk</span> <span>CD</span> <span>Smalltalk as a Standalone OS</span></a>
<a class="tr" href="?profile=chokanji4" id="start_chokanji4"><span>Chokanji 4</span> <span>13+ MB</span> <span><div class=gui_icon></div></span> <span>Custom</span> <span>32-bit</span> <span>Historic</span> <span>Proprietary</span> <span>C</span> <span>HD</span> <span>A Japanese OS based on the TRON project</span></a>
</div>
<hr>
<h4 id="setup">Setup</h4>
@ -127,12 +185,22 @@
<tr>
<td><label for="floppy_image">Floppy disk image</label></td>
<td> <input type="file" id="floppy_image"><br></td>
<td><input type="file" id="fda_image"> or <a id="fda_toggle_empty_disk">create empty floppy disk</a><br></td>
</tr>
<tr>
<td><label for="floppy_image">Second floppy disk image</label></td>
<td><input type="file" id="fdb_image"> or <a id="fdb_toggle_empty_disk">create empty floppy disk</a><br></td>
</tr>
<tr>
<td><label for="hda_image">Hard disk image</label></td>
<td><input type="file" id="hda_image"><br></td>
<td><input type="file" id="hda_image"> or <a id="hda_toggle_empty_disk">create empty hard disk</a><br></td>
</tr>
<tr>
<td><label for="hdb_image">Second hard disk image</label></td>
<td><input type="file" id="hdb_image"> or <a id="hdb_toggle_empty_disk">create empty hard disk</a><br></td>
</tr>
<!--
@ -153,7 +221,12 @@
<tr>
<td><label for="bios">BIOS</label></td>
<td> <input type="file" id="bios"><br></td>
<td><input type="file" id="bios"><br></td>
</tr>
<tr>
<td><label for="vga_bios">VGA BIOS</label></td>
<td><input type="file" id="vga_bios"><br></td>
</tr>
<tr>
@ -174,6 +247,10 @@
</td>
</tr>
<tr>
<td colspan="2"><hr></td>
</tr>
<tr>
<td><label for="relay_url">Networking proxy</label><br>
Presets: <a id="network_none">none</a>, <a id="network_inbrowser">inbrowser</a>, <a id="network_relay">public relay</a>, <a id="network_wisp">wisp</a>, <a id="network_fetch">fetch</a></td>
@ -182,6 +259,23 @@
</td>
</tr>
<tr>
<td><label for="net_device_type">Network Device Type</label></td>
<td>
<select id="net_device_type">
<option value="ne2k">ne2k</option>
<option value="virtio">virtio</option>
</select>
</td>
</tr>
<tr>
<td><label for="mtu">Network MTU (only used for virtio)</label></td>
<td>
<input id="mtu" type="number" value="1500" min="68" max="65535" step="1"> B<br>
</td>
</tr>
<tr>
<td colspan="2"><hr></td>
</tr>
@ -236,6 +330,8 @@
<input type="button" value="Get second hard disk image" id="get_hdb_image">
<input type="button" value="Get CD-ROM image" id="get_cdrom_image">
<input type="button" value="Insert floppy image" id="change_fda_image">
<input type="button" value="Insert second floppy image" id="change_fdb_image">
<input type="button" value="Insert CD image" id="change_cdrom_image" title="Either a single .iso file or multiple files of any type">
<input type="button" value="Save State" id="save_state">
<input type="button" value="Load State" id="load_state"> <input type="file" style="display: none" id="load_state_input">
<input type="button" value="Memory Dump" id="memory_dump">
@ -245,13 +341,13 @@
<input type="button" value="Go fullscreen" id="fullscreen">
<input type="button" value="Take screenshot" id="take_screenshot">
<input type="button" value="Mute" id="mute">
<input type="button" value="Enable theatre mode" id="toggle_theatre">
<input type="button" style="display: none" value="Enable zoom to fit" id="toggle_zoom_to_fit">
<label>
Scale:
<input type="number" min="0.25" step="0.25" value="1.0" id="scale" style="width: 50px">
</label>
<br><br>
</div>
<pre style="display: none" id="loading"></pre>
</div>
@ -318,8 +414,11 @@
<div id="terminal"></div>
<input type="button" value="Hide UI" id="toggle_ui" style="display: none">
<div id="theatre_background" style="display: none"></div>
<br style="clear: both">
<code>Version: <a href="https://github.com/copy/v86/commits/98e7110c2">98e7110c2</a> (Feb 16, 2021 12:02)</code>
<code>Version: <a id="version" href="https://github.com/copy/v86/commits/98e7110c2">98e7110c2</a> (Feb 16, 2021 12:02)</code>
<hr>
<a href="debug.html">Enable debug</a>

500
lib/9p.js
View file

@ -4,7 +4,23 @@
// Implementation of the 9p filesystem device following the
// 9P2000.L protocol ( https://code.google.com/p/diod/wiki/protocol )
"use strict";
import { LOG_9P } from "./../src/const.js";
import { VirtIO, VIRTIO_F_VERSION_1, VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_RING_INDIRECT_DESC } from "../src/virtio.js";
import { S_IFREG, S_IFDIR, STATUS_UNLINKED } from "./filesystem.js";
import * as marshall from "../lib/marshall.js";
import { dbg_log, dbg_assert } from "../src/log.js";
import { h } from "../src/lib.js";
// For Types Only
import { CPU } from "../src/cpu.js";
import { BusConnector } from "../src/bus.js";
import { FS } from "./filesystem.js";
/**
* @const
* More accurate filenames in 9p debug messages at the cost of performance.
*/
const TRACK_FILENAMES = false;
// Feature bit (bit position) for mount tag.
const VIRTIO_9P_F_MOUNT_TAG = 0;
@ -16,13 +32,13 @@ const MAX_REPLYBUFFER_SIZE = 16 * 1024 * 1024;
// TODO
// flush
var EPERM = 1; /* Operation not permitted */
var ENOENT = 2; /* No such file or directory */
var EEXIST = 17; /* File exists */
var EINVAL = 22; /* Invalid argument */
var EOPNOTSUPP = 95; /* Operation is not supported */
var ENOTEMPTY = 39; /* Directory not empty */
var EPROTO = 71; /* Protocol error */
export const EPERM = 1; /* Operation not permitted */
export const ENOENT = 2; /* No such file or directory */
export const EEXIST = 17; /* File exists */
export const EINVAL = 22; /* Invalid argument */
export const EOPNOTSUPP = 95; /* Operation is not supported */
export const ENOTEMPTY = 39; /* Directory not empty */
export const EPROTO = 71; /* Protocol error */
var P9_SETATTR_MODE = 0x00000001;
var P9_SETATTR_UID = 0x00000002;
@ -49,50 +65,35 @@ var P9_STAT_MODE_SETUID = 0x00080000;
var P9_STAT_MODE_SETGID = 0x00040000;
var P9_STAT_MODE_SETVTX = 0x00010000;
const P9_LOCK_TYPE_RDLCK = 0;
const P9_LOCK_TYPE_WRLCK = 1;
const P9_LOCK_TYPE_UNLCK = 2;
export const P9_LOCK_TYPE_RDLCK = 0;
export const P9_LOCK_TYPE_WRLCK = 1;
export const P9_LOCK_TYPE_UNLCK = 2;
const P9_LOCK_TYPES = ["shared", "exclusive", "unlock"];
const P9_LOCK_FLAGS_BLOCK = 1;
const P9_LOCK_FLAGS_RECLAIM = 2;
const P9_LOCK_SUCCESS = 0;
const P9_LOCK_BLOCKED = 1;
const P9_LOCK_ERROR = 2;
const P9_LOCK_GRACE = 3;
export const P9_LOCK_SUCCESS = 0;
export const P9_LOCK_BLOCKED = 1;
export const P9_LOCK_ERROR = 2;
export const P9_LOCK_GRACE = 3;
var FID_NONE = -1;
var FID_INODE = 1;
var FID_XATTR = 2;
function range(size)
{
return Array.from(Array(size).keys());
}
/**
* @constructor
*
* @param {FS} filesystem
* @param {CPU} cpu
* @param {Function} receive
*/
function Virtio9p(filesystem, cpu, bus) {
/** @type {FS} */
this.fs = filesystem;
/** @const @type {BusConnector} */
this.bus = bus;
//this.configspace = [0x0, 0x4, 0x68, 0x6F, 0x73, 0x74]; // length of string and "host" string
//this.configspace = [0x0, 0x9, 0x2F, 0x64, 0x65, 0x76, 0x2F, 0x72, 0x6F, 0x6F, 0x74 ]; // length of string and "/dev/root" string
this.configspace_tagname = [0x68, 0x6F, 0x73, 0x74, 0x39, 0x70]; // "host9p" string
this.configspace_taglen = this.configspace_tagname.length; // num bytes
this.VERSION = "9P2000.L";
this.BLOCKSIZE = 8192; // Let's define one page.
this.msize = 8192; // maximum message size
this.replybuffer = new Uint8Array(this.msize*2); // Twice the msize to stay on the safe site
this.replybuffersize = 0;
this.fids = [];
/** @type {VirtIO} */
this.virtio = new VirtIO(cpu,
function init_virtio(cpu, configspace_taglen, configspace_tagname, receive)
{
const virtio = new VirtIO(cpu,
{
name: "virtio-9p",
pci_id: 0x06 << 3,
@ -131,12 +132,13 @@ function Virtio9p(filesystem, cpu, bus) {
" (expected queue_id of 0)");
return;
}
while(this.virtqueue.has_request())
const virtqueue = virtio.queues[0];
while(virtqueue.has_request())
{
const bufchain = this.virtqueue.pop_request();
this.ReceiveRequest(bufchain);
const bufchain = virtqueue.pop_request();
receive(bufchain);
}
this.virtqueue.notify_me_after(0);
virtqueue.notify_me_after(0);
// Don't flush replies here: async replies are not completed yet.
},
],
@ -153,21 +155,48 @@ function Virtio9p(filesystem, cpu, bus) {
{
bytes: 2,
name: "mount tag length",
read: () => this.configspace_taglen,
read: () => configspace_taglen,
write: data => { /* read only */ },
},
].concat(v86util.range(VIRTIO_9P_MAX_TAGLEN).map(index =>
].concat(range(VIRTIO_9P_MAX_TAGLEN).map(index =>
({
bytes: 1,
name: "mount tag name " + index,
// Note: configspace_tagname may have changed after set_state
read: () => this.configspace_tagname[index] || 0,
read: () => configspace_tagname[index] || 0,
write: data => { /* read only */ },
})
)),
},
});
return virtio;
}
/**
* @constructor
*
* @param {FS} filesystem
* @param {CPU} cpu
*/
export function Virtio9p(filesystem, cpu, bus) {
/** @type {FS} */
this.fs = filesystem;
/** @const @type {BusConnector} */
this.bus = bus;
this.configspace_tagname = [0x68, 0x6F, 0x73, 0x74, 0x39, 0x70]; // "host9p" string
this.configspace_taglen = this.configspace_tagname.length; // num bytes
this.virtio = init_virtio(cpu, this.configspace_taglen, this.configspace_tagname, this.ReceiveRequest.bind(this));
this.virtqueue = this.virtio.queues[0];
this.VERSION = "9P2000.L";
this.BLOCKSIZE = 8192; // Let's define one page.
this.msize = 8192; // maximum message size
this.replybuffer = new Uint8Array(this.msize*2); // Twice the msize to stay on the safe site
this.replybuffersize = 0;
this.fids = [];
}
Virtio9p.prototype.get_state = function()
@ -209,7 +238,7 @@ Virtio9p.prototype.set_state = function(state)
// Note: dbg_name is only used for debugging messages and may not be the same as the filename,
// since it is not synchronised with renames done outside of 9p. Hard-links, linking and unlinking
// operations also mean that having a single filename no longer makes sense.
// Set TRACK_FILENAMES = true (in config.js) to sync dbg_name during 9p renames.
// Set TRACK_FILENAMES = true to sync dbg_name during 9p renames.
Virtio9p.prototype.Createfid = function(inodeid, type, uid, dbg_name) {
return {inodeid, type, uid, dbg_name};
};
@ -222,17 +251,17 @@ Virtio9p.prototype.update_dbg_name = function(idx, newname)
}
};
Virtio9p.prototype.reset = function() {
Virtio9p.prototype.reset = function()
{
this.fids = [];
this.virtio.reset();
};
Virtio9p.prototype.BuildReply = function(id, tag, payloadsize) {
dbg_assert(payloadsize >= 0, "9P: Negative payload size");
marshall.Marshall(["w", "b", "h"], [payloadsize+7, id+1, tag], this.replybuffer, 0);
if((payloadsize+7) >= this.replybuffer.length) {
message.Debug("Error in 9p: payloadsize exceeds maximum length");
dbg_log("Error in 9p: payloadsize exceeds maximum length", LOG_9P);
}
//for(var i=0; i<payload.length; i++)
// this.replybuffer[7+i] = payload[i];
@ -262,7 +291,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var size = header[0];
var id = header[1];
var tag = header[2];
//message.Debug("size:" + size + " id:" + id + " tag:" + tag);
//dbg_log("size:" + size + " id:" + id + " tag:" + tag, LOG_9P);
switch(id)
{
@ -290,24 +319,18 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var req = marshall.Unmarshall(["w", "w"], buffer, state);
var fid = req[0];
var mode = req[1];
message.Debug("[open] fid=" + fid + ", mode=" + mode);
dbg_log("[open] fid=" + fid + ", mode=" + mode, LOG_9P);
var idx = this.fids[fid].inodeid;
var inode = this.fs.GetInode(idx);
message.Debug("file open " + this.fids[fid].dbg_name);
//if (inode.status === STATUS_LOADING) return;
var ret = this.fs.OpenInode(idx, mode);
dbg_log("file open " + this.fids[fid].dbg_name + " tag:"+tag, LOG_9P);
await this.fs.OpenInode(idx, mode);
this.fs.AddEvent(this.fids[fid].inodeid,
function() {
message.Debug("file opened " + this.fids[fid].dbg_name + " tag:"+tag);
var req = [];
req[0] = inode.qid;
req[1] = this.msize - 24;
marshall.Marshall(["Q", "w"], req, this.replybuffer, 7);
this.BuildReply(id, tag, 13+4);
this.SendReply(bufchain);
}.bind(this)
);
req = [];
req[0] = inode.qid;
req[1] = this.msize - 24;
marshall.Marshall(["Q", "w"], req, this.replybuffer, 7);
this.BuildReply(id, tag, 13+4);
this.SendReply(bufchain);
break;
case 70: // link
@ -315,7 +338,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var dfid = req[0];
var fid = req[1];
var name = req[2];
message.Debug("[link] dfid=" + dfid + ", name=" + name);
dbg_log("[link] dfid=" + dfid + ", name=" + name, LOG_9P);
var ret = this.fs.Link(this.fids[dfid].inodeid, this.fids[fid].inodeid, name);
@ -343,7 +366,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var name = req[1];
var symgt = req[2];
var gid = req[3];
message.Debug("[symlink] fid=" + fid + ", name=" + name + ", symgt=" + symgt + ", gid=" + gid);
dbg_log("[symlink] fid=" + fid + ", name=" + name + ", symgt=" + symgt + ", gid=" + gid, LOG_9P);
var idx = this.fs.CreateSymlink(name, this.fids[fid].inodeid, symgt);
var inode = this.fs.GetInode(idx);
inode.uid = this.fids[fid].uid;
@ -361,7 +384,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var major = req[3];
var minor = req[4];
var gid = req[5];
message.Debug("[mknod] fid=" + fid + ", name=" + name + ", major=" + major + ", minor=" + minor+ "");
dbg_log("[mknod] fid=" + fid + ", name=" + name + ", major=" + major + ", minor=" + minor+ "", LOG_9P);
var idx = this.fs.CreateNode(name, this.fids[fid].inodeid, major, minor);
var inode = this.fs.GetInode(idx);
inode.mode = mode;
@ -378,7 +401,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var req = marshall.Unmarshall(["w"], buffer, state);
var fid = req[0];
var inode = this.fs.GetInode(this.fids[fid].inodeid);
message.Debug("[readlink] fid=" + fid + " name=" + this.fids[fid].dbg_name + " target=" + inode.symlink);
dbg_log("[readlink] fid=" + fid + " name=" + this.fids[fid].dbg_name + " target=" + inode.symlink, LOG_9P);
size = marshall.Marshall(["s"], [inode.symlink], this.replybuffer, 7);
this.BuildReply(id, tag, size);
this.SendReply(bufchain);
@ -391,7 +414,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var name = req[1];
var mode = req[2];
var gid = req[3];
message.Debug("[mkdir] fid=" + fid + ", name=" + name + ", mode=" + mode + ", gid=" + gid);
dbg_log("[mkdir] fid=" + fid + ", name=" + name + ", mode=" + mode + ", gid=" + gid, LOG_9P);
var idx = this.fs.CreateDirectory(name, this.fids[fid].inodeid);
var inode = this.fs.GetInode(idx);
inode.mode = mode | S_IFDIR;
@ -410,7 +433,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var mode = req[3];
var gid = req[4];
this.bus.send("9p-create", [name, this.fids[fid].inodeid]);
message.Debug("[create] fid=" + fid + ", name=" + name + ", flags=" + flags + ", mode=" + mode + ", gid=" + gid);
dbg_log("[create] fid=" + fid + ", name=" + name + ", flags=" + flags + ", mode=" + mode + ", gid=" + gid, LOG_9P);
var idx = this.fs.CreateFile(name, this.fids[fid].inodeid);
this.fids[fid].inodeid = idx;
this.fids[fid].type = FID_INODE;
@ -430,7 +453,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var flags = req[2];
var lock_length = req[4] === 0 ? Infinity : req[4];
var lock_request = this.fs.DescribeLock(req[1], req[3], lock_length, req[5], req[6]);
message.Debug("[lock] fid=" + fid +
dbg_log("[lock] fid=" + fid +
", type=" + P9_LOCK_TYPES[lock_request.type] + ", start=" + lock_request.start +
", length=" + lock_request.length + ", proc_id=" + lock_request.proc_id);
@ -446,7 +469,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var fid = req[0];
var lock_length = req[3] === 0 ? Infinity : req[3];
var lock_request = this.fs.DescribeLock(req[1], req[2], lock_length, req[4], req[5]);
message.Debug("[getlock] fid=" + fid +
dbg_log("[getlock] fid=" + fid +
", type=" + P9_LOCK_TYPES[lock_request.type] + ", start=" + lock_request.start +
", length=" + lock_request.length + ", proc_id=" + lock_request.proc_id);
@ -472,10 +495,10 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var req = marshall.Unmarshall(["w", "d"], buffer, state);
var fid = req[0];
var inode = this.fs.GetInode(this.fids[fid].inodeid);
message.Debug("[getattr]: fid=" + fid + " name=" + this.fids[fid].dbg_name + " request mask=" + req[1]);
dbg_log("[getattr]: fid=" + fid + " name=" + this.fids[fid].dbg_name + " request mask=" + req[1], LOG_9P);
if(!inode || inode.status === STATUS_UNLINKED)
{
message.Debug("getattr: unlinked");
dbg_log("getattr: unlinked", LOG_9P);
this.SendError(tag, "No such file or directory", ENOENT);
this.SendReply(bufchain);
break;
@ -528,7 +551,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
], buffer, state);
var fid = req[0];
var inode = this.fs.GetInode(this.fids[fid].inodeid);
message.Debug("[setattr]: fid=" + fid + " request mask=" + req[1] + " name=" + this.fids[fid].dbg_name);
dbg_log("[setattr]: fid=" + fid + " request mask=" + req[1] + " name=" + this.fids[fid].dbg_name, LOG_9P);
if(req[1] & P9_SETATTR_MODE) {
// XXX: check mode (S_IFREG or S_IFDIR or similar should be set)
inode.mode = req[2];
@ -575,11 +598,11 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var offset = req[1];
var count = req[2];
var inode = this.fs.GetInode(this.fids[fid].inodeid);
if(id === 40) message.Debug("[treaddir]: fid=" + fid + " offset=" + offset + " count=" + count);
if(id === 116) message.Debug("[read]: fid=" + fid + " (" + this.fids[fid].dbg_name + ") offset=" + offset + " count=" + count + " fidtype=" + this.fids[fid].type);
if(id === 40) dbg_log("[treaddir]: fid=" + fid + " offset=" + offset + " count=" + count, LOG_9P);
if(id === 116) dbg_log("[read]: fid=" + fid + " (" + this.fids[fid].dbg_name + ") offset=" + offset + " count=" + count + " fidtype=" + this.fids[fid].type, LOG_9P);
if(!inode || inode.status === STATUS_UNLINKED)
{
message.Debug("read/treaddir: unlinked");
dbg_log("read/treaddir: unlinked", LOG_9P);
this.SendError(tag, "No such file or directory", ENOENT);
this.SendReply(bufchain);
break;
@ -592,7 +615,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
this.BuildReply(id, tag, 4 + count);
this.SendReply(bufchain);
} else {
this.fs.OpenInode(this.fids[fid].inodeid, undefined);
await this.fs.OpenInode(this.fids[fid].inodeid, undefined);
const inodeid = this.fids[fid].inodeid;
count = Math.min(count, this.replybuffer.length - (7 + 4));
@ -633,7 +656,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
const filename = this.fids[fid].dbg_name;
message.Debug("[write]: fid=" + fid + " (" + filename + ") offset=" + offset + " count=" + count + " fidtype=" + this.fids[fid].type);
dbg_log("[write]: fid=" + fid + " (" + filename + ") offset=" + offset + " count=" + count + " fidtype=" + this.fids[fid].type, LOG_9P);
if(this.fids[fid].type === FID_XATTR)
{
// XXX: xattr not supported yet. Ignore write.
@ -660,7 +683,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var oldname = req[1];
var newdirfid = req[2];
var newname = req[3];
message.Debug("[renameat]: oldname=" + oldname + " newname=" + newname);
dbg_log("[renameat]: oldname=" + oldname + " newname=" + newname, LOG_9P);
var ret = await this.fs.Rename(this.fids[olddirfid].inodeid, oldname, this.fids[newdirfid].inodeid, newname);
if(ret < 0) {
let error_message = "";
@ -690,7 +713,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var dirfd = req[0];
var name = req[1];
var flags = req[2];
message.Debug("[unlink]: dirfd=" + dirfd + " name=" + name + " flags=" + flags);
dbg_log("[unlink]: dirfd=" + dirfd + " name=" + name + " flags=" + flags, LOG_9P);
var fid = this.fs.Search(this.fids[dirfd].inodeid, name);
if(fid === -1) {
this.SendError(tag, "No such file or directory", ENOENT);
@ -717,7 +740,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
case 100: // version
var version = marshall.Unmarshall(["w", "s"], buffer, state);
message.Debug("[version]: msize=" + version[0] + " version=" + version[1]);
dbg_log("[version]: msize=" + version[0] + " version=" + version[1], LOG_9P);
if(this.msize !== version[0])
{
this.msize = version[0];
@ -733,7 +756,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var req = marshall.Unmarshall(["w", "w", "s", "s", "w"], buffer, state);
var fid = req[0];
var uid = req[4];
message.Debug("[attach]: fid=" + fid + " afid=" + hex8(req[1]) + " uname=" + req[2] + " aname=" + req[3]);
dbg_log("[attach]: fid=" + fid + " afid=" + h(req[1]) + " uname=" + req[2] + " aname=" + req[3], LOG_9P);
this.fids[fid] = this.Createfid(0, FID_INODE, uid, "");
var inode = this.fs.GetInode(this.fids[fid].inodeid);
marshall.Marshall(["Q"], [inode.qid], this.replybuffer, 7);
@ -745,7 +768,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
case 108: // tflush
var req = marshall.Unmarshall(["h"], buffer, state);
var oldtag = req[0];
message.Debug("[flush] " + tag);
dbg_log("[flush] " + tag, LOG_9P);
//marshall.Marshall(["Q"], [inode.qid], this.replybuffer, 7);
this.BuildReply(id, tag, 0);
this.SendReply(bufchain);
@ -757,7 +780,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var fid = req[0];
var nwfid = req[1];
var nwname = req[2];
message.Debug("[walk]: fid=" + req[0] + " nwfid=" + req[1] + " nwname=" + nwname);
dbg_log("[walk]: fid=" + req[0] + " nwfid=" + req[1] + " nwname=" + nwname, LOG_9P);
if(nwname === 0) {
this.fids[nwfid] = this.Createfid(this.fids[fid].inodeid, FID_INODE, this.fids[fid].uid, this.fids[fid].dbg_name);
//this.fids[nwfid].inodeid = this.fids[fid].inodeid;
@ -775,17 +798,17 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var offset = 7+2;
var nwidx = 0;
//console.log(idx, this.fs.GetInode(idx));
message.Debug("walk in dir " + this.fids[fid].dbg_name + " to: " + walk.toString());
dbg_log("walk in dir " + this.fids[fid].dbg_name + " to: " + walk.toString(), LOG_9P);
for(var i=0; i<nwname; i++) {
idx = this.fs.Search(idx, walk[i]);
if(idx === -1) {
message.Debug("Could not find: " + walk[i]);
dbg_log("Could not find: " + walk[i], LOG_9P);
break;
}
offset += marshall.Marshall(["Q"], [this.fs.GetInode(idx).qid], this.replybuffer, offset);
nwidx++;
//message.Debug(this.fids[nwfid].inodeid);
//dbg_log(this.fids[nwfid].inodeid, LOG_9P);
//this.fids[nwfid].inodeid = idx;
//this.fids[nwfid].type = FID_INODE;
this.fids[nwfid] = this.Createfid(idx, FID_INODE, this.fids[fid].uid, walk[i]);
@ -797,7 +820,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
case 120: // clunk
var req = marshall.Unmarshall(["w"], buffer, state);
message.Debug("[clunk]: fid=" + req[0]);
dbg_log("[clunk]: fid=" + req[0], LOG_9P);
if(this.fids[req[0]] && this.fids[req[0]].inodeid >= 0) {
await this.fs.CloseInode(this.fids[req[0]].inodeid);
this.fids[req[0]].inodeid = -1;
@ -813,7 +836,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var name = req[1];
var attr_size = req[2];
var flags = req[3];
message.Debug("[txattrcreate]: fid=" + fid + " name=" + name + " attr_size=" + attr_size + " flags=" + flags);
dbg_log("[txattrcreate]: fid=" + fid + " name=" + name + " attr_size=" + attr_size + " flags=" + flags, LOG_9P);
// XXX: xattr not supported yet. E.g. checks corresponding to the flags needed.
this.fids[fid].type = FID_XATTR;
@ -829,7 +852,7 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
var fid = req[0];
var newfid = req[1];
var name = req[2];
message.Debug("[xattrwalk]: fid=" + req[0] + " newfid=" + req[1] + " name=" + req[2]);
dbg_log("[xattrwalk]: fid=" + req[0] + " newfid=" + req[1] + " name=" + req[2], LOG_9P);
// Workaround for Linux restarts writes until full blocksize
this.SendError(tag, "Setxattr not supported", EOPNOTSUPP);
@ -850,8 +873,8 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
break;
default:
message.Debug("Error in Virtio9p: Unknown id " + id + " received");
message.Abort();
dbg_log("Error in Virtio9p: Unknown id " + id + " received", LOG_9P);
dbg_assert(false);
//this.SendError(tag, "Operation i not supported", EOPNOTSUPP);
//this.SendReply(bufchain);
break;
@ -860,3 +883,288 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
//consistency checks if there are problems with the filesystem
//this.fs.Check();
};
/** @typedef {function(Uint8Array, function(Uint8Array):void):void} */
let P9Handler;
/**
* @constructor
*
* @param {P9Handler} handle_fn
* @param {CPU} cpu
*/
export function Virtio9pHandler(handle_fn, cpu) {
/** @type {P9Handler} */
this.handle_fn = handle_fn;
this.tag_bufchain = new Map();
this.configspace_tagname = [0x68, 0x6F, 0x73, 0x74, 0x39, 0x70]; // "host9p" string
this.configspace_taglen = this.configspace_tagname.length; // num bytes
this.virtio = init_virtio(
cpu,
this.configspace_taglen,
this.configspace_tagname,
async (bufchain) => {
// TODO: split into header + data blobs to avoid unnecessary copying.
const reqbuf = new Uint8Array(bufchain.length_readable);
bufchain.get_next_blob(reqbuf);
var reqheader = marshall.Unmarshall(["w", "b", "h"], reqbuf, { offset : 0 });
var reqtag = reqheader[2];
this.tag_bufchain.set(reqtag, bufchain);
this.handle_fn(reqbuf, (replybuf) => {
var replyheader = marshall.Unmarshall(["w", "b", "h"], replybuf, { offset: 0 });
var replytag = replyheader[2];
const bufchain = this.tag_bufchain.get(replytag);
if(!bufchain)
{
console.error("No bufchain found for tag: " + replytag);
return;
}
bufchain.set_next_blob(replybuf);
this.virtqueue.push_reply(bufchain);
this.virtqueue.flush_replies();
this.tag_bufchain.delete(replytag);
});
}
);
this.virtqueue = this.virtio.queues[0];
}
Virtio9pHandler.prototype.get_state = function()
{
var state = [];
state[0] = this.configspace_tagname;
state[1] = this.configspace_taglen;
state[2] = this.virtio;
state[3] = this.tag_bufchain;
return state;
};
Virtio9pHandler.prototype.set_state = function(state)
{
this.configspace_tagname = state[0];
this.configspace_taglen = state[1];
this.virtio.set_state(state[2]);
this.virtqueue = this.virtio.queues[0];
this.tag_bufchain = state[3];
};
Virtio9pHandler.prototype.reset = function()
{
this.virtio.reset();
};
/**
* @constructor
*
* @param {string} url
* @param {CPU} cpu
*/
export function Virtio9pProxy(url, cpu)
{
this.socket = undefined;
this.cpu = cpu;
// TODO: circular buffer?
this.send_queue = [];
this.url = url;
this.reconnect_interval = 10000;
this.last_connect_attempt = Date.now() - this.reconnect_interval;
this.send_queue_limit = 64;
this.destroyed = false;
this.tag_bufchain = new Map();
this.configspace_tagname = [0x68, 0x6F, 0x73, 0x74, 0x39, 0x70]; // "host9p" string
this.configspace_taglen = this.configspace_tagname.length; // num bytes
this.virtio = init_virtio(
cpu,
this.configspace_taglen,
this.configspace_tagname,
async (bufchain) => {
// TODO: split into header + data blobs to avoid unnecessary copying.
const reqbuf = new Uint8Array(bufchain.length_readable);
bufchain.get_next_blob(reqbuf);
const reqheader = marshall.Unmarshall(["w", "b", "h"], reqbuf, { offset : 0 });
const reqtag = reqheader[2];
this.tag_bufchain.set(reqtag, bufchain);
this.send(reqbuf);
}
);
this.virtqueue = this.virtio.queues[0];
}
Virtio9pProxy.prototype.get_state = function()
{
var state = [];
state[0] = this.configspace_tagname;
state[1] = this.configspace_taglen;
state[2] = this.virtio;
state[3] = this.tag_bufchain;
return state;
};
Virtio9pProxy.prototype.set_state = function(state)
{
this.configspace_tagname = state[0];
this.configspace_taglen = state[1];
this.virtio.set_state(state[2]);
this.virtqueue = this.virtio.queues[0];
this.tag_bufchain = state[3];
};
Virtio9pProxy.prototype.reset = function() {
this.virtio.reset();
};
Virtio9pProxy.prototype.handle_message = function(e)
{
const replybuf = new Uint8Array(e.data);
const replyheader = marshall.Unmarshall(["w", "b", "h"], replybuf, { offset: 0 });
const replytag = replyheader[2];
const bufchain = this.tag_bufchain.get(replytag);
if(!bufchain)
{
console.error("Virtio9pProxy: No bufchain found for tag: " + replytag);
return;
}
bufchain.set_next_blob(replybuf);
this.virtqueue.push_reply(bufchain);
this.virtqueue.flush_replies();
this.tag_bufchain.delete(replytag);
};
Virtio9pProxy.prototype.handle_close = function(e)
{
//console.log("onclose", e);
if(!this.destroyed)
{
this.connect();
setTimeout(this.connect.bind(this), this.reconnect_interval);
}
};
Virtio9pProxy.prototype.handle_open = function(e)
{
//console.log("open", e);
for(var i = 0; i < this.send_queue.length; i++)
{
this.send(this.send_queue[i]);
}
this.send_queue = [];
};
Virtio9pProxy.prototype.handle_error = function(e)
{
//console.log("onerror", e);
};
Virtio9pProxy.prototype.destroy = function()
{
this.destroyed = true;
if(this.socket)
{
this.socket.close();
}
};
Virtio9pProxy.prototype.connect = function()
{
if(typeof WebSocket === "undefined")
{
return;
}
if(this.socket)
{
var state = this.socket.readyState;
if(state === 0 || state === 1)
{
// already or almost there
return;
}
}
var now = Date.now();
if(this.last_connect_attempt + this.reconnect_interval > now)
{
return;
}
this.last_connect_attempt = Date.now();
try
{
this.socket = new WebSocket(this.url);
}
catch(e)
{
console.error(e);
return;
}
this.socket.binaryType = "arraybuffer";
this.socket.onopen = this.handle_open.bind(this);
this.socket.onmessage = this.handle_message.bind(this);
this.socket.onclose = this.handle_close.bind(this);
this.socket.onerror = this.handle_error.bind(this);
};
Virtio9pProxy.prototype.send = function(data)
{
//console.log("send", data);
if(!this.socket || this.socket.readyState !== 1)
{
this.send_queue.push(data);
if(this.send_queue.length > 2 * this.send_queue_limit)
{
this.send_queue = this.send_queue.slice(-this.send_queue_limit);
}
this.connect();
}
else
{
this.socket.send(data);
}
};
Virtio9pProxy.prototype.change_proxy = function(url)
{
this.url = url;
if(this.socket)
{
this.socket.onclose = function() {};
this.socket.onerror = function() {};
this.socket.close();
this.socket = undefined;
}
};

View file

@ -3,16 +3,25 @@
// -------------------------------------------------
// Implementation of a unix filesystem in memory.
"use strict";
var S_IRWXUGO = 0x1FF;
var S_IFMT = 0xF000;
var S_IFSOCK = 0xC000;
var S_IFLNK = 0xA000;
var S_IFREG = 0x8000;
var S_IFBLK = 0x6000;
var S_IFDIR = 0x4000;
var S_IFCHR = 0x2000;
import { LOG_9P } from "../src/const.js";
import { h } from "../src/lib.js";
import { dbg_assert, dbg_log } from "../src/log.js";
import * as marshall from "../lib/marshall.js";
import { EEXIST, ENOTEMPTY, ENOENT, EPERM } from "./9p.js";
import { P9_LOCK_SUCCESS, P9_LOCK_BLOCKED, P9_LOCK_TYPE_UNLCK, P9_LOCK_TYPE_WRLCK, P9_LOCK_TYPE_RDLCK } from "./9p.js";
// For Types Only
import { FileStorageInterface } from "../src/browser/filestorage.js";
export const S_IRWXUGO = 0x1FF;
export const S_IFMT = 0xF000;
export const S_IFSOCK = 0xC000;
export const S_IFLNK = 0xA000;
export const S_IFREG = 0x8000;
export const S_IFBLK = 0x6000;
export const S_IFDIR = 0x4000;
export const S_IFCHR = 0x2000;
//var S_IFIFO 0010000
//var S_ISUID 0004000
@ -24,12 +33,13 @@ var O_WRONLY = 0x0001; // open for writing only
var O_RDWR = 0x0002; // open for reading and writing
var O_ACCMODE = 0x0003; // mask for above modes
var STATUS_INVALID = -0x1;
var STATUS_OK = 0x0;
var STATUS_ON_STORAGE = 0x2;
var STATUS_UNLINKED = 0x4;
var STATUS_FORWARDING = 0x5;
export const STATUS_INVALID = -0x1;
export const STATUS_OK = 0x0;
export const STATUS_ON_STORAGE = 0x2;
export const STATUS_UNLINKED = 0x4;
export const STATUS_FORWARDING = 0x5;
const texten = new TextEncoder();
/** @const */ var JSONFS_VERSION = 3;
@ -49,10 +59,9 @@ var STATUS_FORWARDING = 0x5;
* @param {!FileStorageInterface} storage
* @param {{ last_qidnumber: number }=} qidcounter Another fs's qidcounter to synchronise with.
*/
function FS(storage, qidcounter) {
export function FS(storage, qidcounter) {
/** @type {Array.<!Inode>} */
this.inodes = [];
this.events = [];
this.storage = storage;
@ -129,47 +138,13 @@ FS.prototype.set_state = function(state)
// -----------------------------------------------------
FS.prototype.AddEvent = function(id, OnEvent) {
var inode = this.inodes[id];
if(inode.status === STATUS_OK || inode.status === STATUS_ON_STORAGE) {
OnEvent();
}
else if(this.is_forwarder(inode))
{
this.follow_fs(inode).AddEvent(inode.foreign_id, OnEvent);
}
else
{
this.events.push({id: id, OnEvent: OnEvent});
}
};
FS.prototype.HandleEvent = function(id) {
const inode = this.inodes[id];
if(this.is_forwarder(inode))
{
this.follow_fs(inode).HandleEvent(inode.foreign_id);
}
//message.Debug("number of events: " + this.events.length);
var newevents = [];
for(var i=0; i<this.events.length; i++) {
if(this.events[i].id === id) {
this.events[i].OnEvent();
} else {
newevents.push(this.events[i]);
}
}
this.events = newevents;
};
FS.prototype.load_from_json = function(fs)
{
dbg_assert(fs, "Invalid fs passed to load_from_json");
if(fs["version"] !== JSONFS_VERSION)
{
throw "The filesystem JSON format has changed. " +
"Please update your fs2json (https://github.com/copy/fs2json) and recreate the filesystem JSON.";
throw "The filesystem JSON format has changed. Please recreate the filesystem JSON.";
}
var fsroot = fs["fsroot"];
@ -223,7 +198,7 @@ FS.prototype.LoadRecursive = function(data, parentid)
}
else
{
dbg_log("Unexpected ifmt: " + h(ifmt) + " (" + name + ")");
dbg_log("Unexpected ifmt: " + h(ifmt) + " (" + name + ")", LOG_9P);
}
};
@ -339,9 +314,7 @@ FS.prototype.PushInode = function(inode, parentid, name) {
}
}
message.Debug("Error in Filesystem: Pushed inode with name = "+ name + " has no parent");
message.Abort();
dbg_assert(false, "Error in Filesystem: Pushed inode with name = "+ name + " has no parent");
};
/** @constructor */
@ -688,11 +661,11 @@ FS.prototype.CreateBinaryFile = async function(filename, parentid, buffer) {
};
FS.prototype.OpenInode = function(id, mode) {
FS.prototype.OpenInode = async function(id, mode) {
var inode = this.inodes[id];
if(this.is_forwarder(inode))
{
return this.follow_fs(inode).OpenInode(inode.foreign_id, mode);
return await this.follow_fs(inode).OpenInode(inode.foreign_id, mode);
}
if((inode.mode&S_IFMT) === S_IFDIR) {
this.FillDirectory(id);
@ -706,12 +679,11 @@ FS.prototype.OpenInode = function(id, mode) {
case S_IFCHR: type = "Character Device"; break;
}
*/
//message.Debug("open:" + this.GetFullPath(id) + " type: " + inode.mode + " status:" + inode.status);
return true;
//dbg_log("open:" + this.GetFullPath(id) + " type: " + inode.mode + " status:" + inode.status, LOG_9P);
};
FS.prototype.CloseInode = async function(id) {
//message.Debug("close: " + this.GetFullPath(id));
//dbg_log("close: " + this.GetFullPath(id), LOG_9P);
var inode = this.inodes[id];
if(this.is_forwarder(inode))
{
@ -722,7 +694,7 @@ FS.prototype.CloseInode = async function(id) {
this.storage.uncache(inode.sha256sum);
}
if(inode.status === STATUS_UNLINKED) {
//message.Debug("Filesystem: Delete unlinked file");
//dbg_log("Filesystem: Delete unlinked file", LOG_9P);
inode.status = STATUS_INVALID;
await this.DeleteData(id);
}
@ -732,7 +704,7 @@ FS.prototype.CloseInode = async function(id) {
* @return {!Promise<number>} 0 if success, or -errno if failured.
*/
FS.prototype.Rename = async function(olddirid, oldname, newdirid, newname) {
// message.Debug("Rename " + oldname + " to " + newname);
// dbg_log("Rename " + oldname + " to " + newname, LOG_9P);
if((olddirid === newdirid) && (oldname === newname)) {
return 0;
}
@ -1036,7 +1008,7 @@ FS.prototype.Unlink = function(parentid, name) {
const idx = this.Search(parentid, name);
const inode = this.inodes[idx];
const parent_inode = this.inodes[parentid];
//message.Debug("Unlink " + inode.name);
//dbg_log("Unlink " + inode.name, LOG_9P);
// forward if necessary
if(this.is_forwarder(parent_inode))
@ -1096,7 +1068,7 @@ FS.prototype.get_buffer = async function(idx)
else if(inode.status === STATUS_ON_STORAGE)
{
dbg_assert(inode.sha256sum, "Filesystem get_data: found inode on server without sha256sum");
return await this.storage.read(inode.sha256sum, 0, inode.size);
return await this.storage.read(inode.sha256sum, 0, inode.size, inode.size);
}
else
{
@ -1123,7 +1095,7 @@ FS.prototype.get_data = async function(idx, offset, count)
else if(inode.status === STATUS_ON_STORAGE)
{
dbg_assert(inode.sha256sum, "Filesystem get_data: found inode on server without sha256sum");
return await this.storage.read(inode.sha256sum, offset, count);
return await this.storage.read(inode.sha256sum, offset, count, inode.size);
}
else
{
@ -1169,7 +1141,7 @@ FS.prototype.ChangeSize = async function(idx, newsize)
{
var inode = this.GetInode(idx);
var temp = await this.get_data(idx, 0, inode.size);
//message.Debug("change size to: " + newsize);
//dbg_log("change size to: " + newsize, LOG_9P);
if(newsize === inode.size) return;
var data = new Uint8Array(newsize);
inode.size = newsize;
@ -1296,24 +1268,24 @@ FS.prototype.Check = function() {
var inode = this.GetInode(i);
if(inode.nlinks < 0) {
message.Debug("Error in filesystem: negative nlinks=" + inode.nlinks + " at id =" + i);
dbg_log("Error in filesystem: negative nlinks=" + inode.nlinks + " at id =" + i, LOG_9P);
}
if(this.IsDirectory(i))
{
const inode = this.GetInode(i);
if(this.IsDirectory(i) && this.GetParent(i) < 0) {
message.Debug("Error in filesystem: negative parent id " + i);
dbg_log("Error in filesystem: negative parent id " + i, LOG_9P);
}
for(const [name, id] of inode.direntries)
{
if(name.length === 0) {
message.Debug("Error in filesystem: inode with no name and id " + id);
dbg_log("Error in filesystem: inode with no name and id " + id, LOG_9P);
}
for(const c of name) {
if(c < 32) {
message.Debug("Error in filesystem: Unallowed char in filename");
dbg_log("Error in filesystem: Unallowed char in filename", LOG_9P);
}
}
}

View file

@ -1,107 +0,0 @@
"use strict";
// jor1k compatibility
var VIRTIO_MAGIC_REG = 0x0;
var VIRTIO_VERSION_REG = 0x4;
var VIRTIO_DEVICE_REG = 0x8;
var VIRTIO_VENDOR_REG = 0xc;
var VIRTIO_HOSTFEATURES_REG = 0x10;
var VIRTIO_HOSTFEATURESSEL_REG = 0x14;
var VIRTIO_GUESTFEATURES_REG = 0x20;
var VIRTIO_GUESTFEATURESSEL_REG = 0x24;
var VIRTIO_GUEST_PAGE_SIZE_REG = 0x28;
var VIRTIO_QUEUESEL_REG = 0x30;
var VIRTIO_QUEUENUMMAX_REG = 0x34;
var VIRTIO_QUEUENUM_REG = 0x38;
var VIRTIO_QUEUEALIGN_REG = 0x3C;
var VIRTIO_QUEUEPFN_REG = 0x40;
var VIRTIO_QUEUENOTIFY_REG = 0x50;
var VIRTIO_INTERRUPTSTATUS_REG = 0x60;
var VIRTIO_INTERRUPTACK_REG = 0x64;
var VIRTIO_STATUS_REG = 0x70;
/** @const */
var VRING_DESC_F_NEXT = 1; /* This marks a buffer as continuing via the next field. */
/** @const */
var VRING_DESC_F_WRITE = 2; /* This marks a buffer as write-only (otherwise read-only). */
/** @const */
var VRING_DESC_F_INDIRECT = 4; /* This means the buffer contains a list of buffer descriptors. */
function hex8(n)
{
return h(n);
}
var message = {};
/** @param {...string} log */
message.Debug = function(log)
{
dbg_log([].slice.apply(arguments).join(" "), LOG_9P);
};
message.Abort = function()
{
if(DEBUG)
{
throw new Error("message.Abort()");
}
};
// XXX: Should go through emulator interface
var LoadBinaryResource;
if(typeof XMLHttpRequest !== "undefined")
{
LoadBinaryResource = function(url, OnSuccess, OnError) {
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.responseType = "arraybuffer";
req.onreadystatechange = function () {
if(req.readyState !== 4) {
return;
}
if((req.status !== 200) && (req.status !== 0)) {
OnError("Error: Could not load file " + url);
return;
}
var arrayBuffer = req.response;
if(arrayBuffer) {
OnSuccess(arrayBuffer);
} else {
OnError("Error: No data received from: " + url);
}
};
/*
req.onload = function(e)
{
var arrayBuffer = req.response;
if (arrayBuffer) {
OnLoadFunction(arrayBuffer);
}
};
*/
req.send(null);
};
}
else
{
LoadBinaryResource = function(url, OnSuccess, OnError)
{
//console.log(url);
require("fs")["readFile"](url, function(err, data)
{
if(err)
{
OnError(err);
}
else
{
OnSuccess(data.buffer);
}
});
};
}

View file

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

13
manifest.json Normal file
View file

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

View file

@ -1,97 +0,0 @@
import vm from "node:vm";
import url from "node:url";
import fs from "node:fs";
import path from "node:path";
import crypto from "node:crypto";
import perf_hooks from "node:perf_hooks";
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
let files = [
"src/const.js",
"src/config.js",
"src/log.js",
"src/cpu.js",
"src/debug.js",
"src/io.js",
"src/main.js",
"src/lib.js",
"src/buffer.js",
"src/ide.js",
"src/pci.js",
"src/floppy.js",
"src/memory.js",
"src/dma.js",
"src/pit.js",
"src/vga.js",
"src/ps2.js",
"src/rtc.js",
"src/uart.js",
"src/acpi.js",
"src/apic.js",
"src/ioapic.js",
"src/state.js",
"src/ne2k.js",
"src/sb16.js",
"src/virtio.js",
"src/virtio_console.js",
"src/virtio_net.js",
"src/virtio_balloon.js",
"src/bus.js",
"src/debug.js",
"src/elf.js",
"src/kernel.js",
"lib/9p.js",
"lib/filesystem.js",
"lib/jor1k.js",
"lib/marshall.js",
"src/browser/screen.js",
"src/browser/keyboard.js",
"src/browser/mouse.js",
"src/browser/speaker.js",
"src/browser/serial.js",
"src/browser/network.js",
"src/browser/fake_network.js",
"src/browser/fetch_network.js",
"src/browser/starter.js",
"src/browser/worker_bus.js",
"src/browser/dummy_screen.js",
"src/browser/print_stats.js",
"src/browser/filestorage.js"
];
let globals = Object.create(globalThis);
let v86 = {};
let ctx = vm.createContext(globals);
globals.DEBUG = false;
globals.module = {exports:v86};
Object.defineProperty(globals, "crypto", {value: crypto});
globals.require = (what) => {
return ({
perf_hooks,
fs
})[what];
};
for( let f of files ) {
vm.runInContext(fs.readFileSync(path.join(__dirname, f), "utf8"), ctx, {
filename: f
});
}
export let {
FetchNetworkAdapter,
MemoryFileStorage,
ServerFileStorageWrapper,
} = globals;
export default globals.V86;

View file

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

View file

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

View file

@ -1,648 +0,0 @@
"use strict";
// See Intel's System Programming Guide
/** @const */
var APIC_LOG_VERBOSE = false;
/** @const */
var APIC_ADDRESS = 0xFEE00000;
/** @const */
var APIC_TIMER_MODE_MASK = 3 << 17;
/** @const */
var APIC_TIMER_MODE_ONE_SHOT = 0;
/** @const */
var APIC_TIMER_MODE_PERIODIC = 1 << 17;
/** @const */
var APIC_TIMER_MODE_TSC = 2 << 17;
/** @const */
var DELIVERY_MODES = [
"Fixed (0)",
"Lowest Prio (1)",
"SMI (2)",
"Reserved (3)",
"NMI (4)",
"INIT (5)",
"Reserved (6)",
"ExtINT (7)",
];
/** @const */
var DESTINATION_MODES = ["physical", "logical"];
/**
* @constructor
* @param {CPU} cpu
*/
function APIC(cpu)
{
/** @type {CPU} */
this.cpu = cpu;
this.apic_id = 0;
this.timer_divider = 0;
this.timer_divider_shift = 1;
this.timer_initial_count = 0;
this.timer_current_count = 0;
this.next_tick = v86.microtick();
this.lvt_timer = IOAPIC_CONFIG_MASKED;
this.lvt_thermal_sensor = IOAPIC_CONFIG_MASKED;
this.lvt_perf_counter = IOAPIC_CONFIG_MASKED;
this.lvt_int0 = IOAPIC_CONFIG_MASKED;
this.lvt_int1 = IOAPIC_CONFIG_MASKED;
this.lvt_error = IOAPIC_CONFIG_MASKED;
this.tpr = 0;
this.icr0 = 0;
this.icr1 = 0;
this.irr = new Int32Array(8);
this.isr = new Int32Array(8);
this.tmr = new Int32Array(8);
this.spurious_vector = 0xFE;
this.destination_format = -1;
this.local_destination = 0;
this.error = 0;
this.read_error = 0;
cpu.io.mmap_register(APIC_ADDRESS, 0x100000,
(addr) =>
{
dbg_log("Unsupported read8 from apic: " + h(addr >>> 0), LOG_APIC);
var off = addr & 3;
addr &= ~3;
return this.read32(addr) >> (off * 8) & 0xFF;
},
(addr, value) =>
{
dbg_log("Unsupported write8 from apic: " + h(addr) + " <- " + h(value), LOG_APIC);
dbg_trace();
dbg_assert(false);
},
(addr) => this.read32(addr),
(addr, value) => this.write32(addr, value)
);
}
APIC.prototype.read32 = function(addr)
{
addr = addr - APIC_ADDRESS | 0;
switch(addr)
{
case 0x20:
dbg_log("APIC read id", LOG_APIC);
return this.apic_id;
case 0x30:
// version
dbg_log("APIC read version", LOG_APIC);
return 0x50014;
case 0x80:
APIC_LOG_VERBOSE && dbg_log("APIC read tpr", LOG_APIC);
return this.tpr;
case 0xD0:
dbg_log("Read local destination", LOG_APIC);
return this.local_destination;
case 0xE0:
dbg_log("Read destination format", LOG_APIC);
return this.destination_format;
case 0xF0:
return this.spurious_vector;
case 0x100:
case 0x110:
case 0x120:
case 0x130:
case 0x140:
case 0x150:
case 0x160:
case 0x170:
var index = addr - 0x100 >> 4;
dbg_log("Read isr " + index + ": " + h(this.isr[index] >>> 0, 8), LOG_APIC);
return this.isr[index];
case 0x180:
case 0x190:
case 0x1A0:
case 0x1B0:
case 0x1C0:
case 0x1D0:
case 0x1E0:
case 0x1F0:
var index = addr - 0x180 >> 4;
dbg_log("Read tmr " + index + ": " + h(this.tmr[index] >>> 0, 8), LOG_APIC);
return this.tmr[index];
case 0x200:
case 0x210:
case 0x220:
case 0x230:
case 0x240:
case 0x250:
case 0x260:
case 0x270:
var index = addr - 0x200 >> 4;
dbg_log("Read irr " + index + ": " + h(this.irr[index] >>> 0, 8), LOG_APIC);
return this.irr[index];
case 0x280:
dbg_log("Read error: " + h(this.read_error >>> 0, 8), LOG_APIC);
return this.read_error;
case 0x300:
APIC_LOG_VERBOSE && dbg_log("APIC read icr0", LOG_APIC);
return this.icr0;
case 0x310:
dbg_log("APIC read icr1", LOG_APIC);
return this.icr1;
case 0x320:
dbg_log("read timer lvt", LOG_APIC);
return this.lvt_timer;
case 0x330:
dbg_log("read lvt thermal sensor", LOG_APIC);
return this.lvt_thermal_sensor;
case 0x340:
dbg_log("read lvt perf counter", LOG_APIC);
return this.lvt_perf_counter;
case 0x350:
dbg_log("read lvt int0", LOG_APIC);
return this.lvt_int0;
case 0x360:
dbg_log("read lvt int1", LOG_APIC);
return this.lvt_int1;
case 0x370:
dbg_log("read lvt error", LOG_APIC);
return this.lvt_error;
case 0x3E0:
// divider
dbg_log("read timer divider", LOG_APIC);
return this.timer_divider;
case 0x380:
dbg_log("read timer initial count", LOG_APIC);
return this.timer_initial_count;
case 0x390:
dbg_log("read timer current count: " + h(this.timer_current_count >>> 0, 8), LOG_APIC);
return this.timer_current_count;
default:
dbg_log("APIC read " + h(addr), LOG_APIC);
dbg_assert(false);
return 0;
}
};
APIC.prototype.write32 = function(addr, value)
{
addr = addr - APIC_ADDRESS | 0;
switch(addr)
{
case 0x20:
dbg_log("APIC write id: " + h(value >>> 8, 8), LOG_APIC);
this.apic_id = value;
break;
case 0x30:
// version
dbg_log("APIC write version: " + h(value >>> 0, 8) + ", ignored", LOG_APIC);
break;
case 0x80:
APIC_LOG_VERBOSE && dbg_log("Set tpr: " + h(value & 0xFF, 2), LOG_APIC);
this.tpr = value & 0xFF;
this.check_vector();
break;
case 0xB0:
var highest_isr = this.highest_isr();
if(highest_isr !== -1)
{
APIC_LOG_VERBOSE && dbg_log("eoi: " + h(value >>> 0, 8) + " for vector " + h(highest_isr), LOG_APIC);
this.register_clear_bit(this.isr, highest_isr);
if(this.register_get_bit(this.tmr, highest_isr))
{
// Send eoi to all IO APICs
this.cpu.devices.ioapic.remote_eoi(highest_isr);
}
this.check_vector();
}
else
{
dbg_log("Bad eoi: No isr set", LOG_APIC);
}
break;
case 0xD0:
dbg_log("Set local destination: " + h(value >>> 0, 8), LOG_APIC);
this.local_destination = value & 0xFF000000;
break;
case 0xE0:
dbg_log("Set destination format: " + h(value >>> 0, 8), LOG_APIC);
this.destination_format = value | 0xFFFFFF;
break;
case 0xF0:
dbg_log("Set spurious vector: " + h(value >>> 0, 8), LOG_APIC);
this.spurious_vector = value;
break;
case 0x280:
// updated readable error register with real error
dbg_log("Write error: " + h(value >>> 0, 8), LOG_APIC);
this.read_error = this.error;
this.error = 0;
break;
case 0x300:
var vector = value & 0xFF;
var delivery_mode = value >> 8 & 7;
var destination_mode = value >> 11 & 1;
var is_level = value >> 15 & 1;
var destination_shorthand = value >> 18 & 3;
var destination = this.icr1 >>> 24;
dbg_log("APIC write icr0: " + h(value, 8) + " vector=" + h(vector, 2) + " " +
"destination_mode=" + DESTINATION_MODES[destination_mode] + " delivery_mode=" + DELIVERY_MODES[delivery_mode] + " " +
"destination_shorthand=" + ["no", "self", "all with self", "all without self"][destination_shorthand], LOG_APIC);
value &= ~(1 << 12);
this.icr0 = value;
if(destination_shorthand === 0)
{
// no shorthand
this.route(vector, delivery_mode, is_level, destination, destination_mode);
}
else if(destination_shorthand === 1)
{
// self
this.deliver(vector, IOAPIC_DELIVERY_FIXED, is_level);
}
else if(destination_shorthand === 2)
{
// all including self
this.deliver(vector, delivery_mode, is_level);
}
else if(destination_shorthand === 3)
{
// all but self
}
else
{
dbg_assert(false);
}
break;
case 0x310:
dbg_log("APIC write icr1: " + h(value >>> 0, 8), LOG_APIC);
this.icr1 = value;
break;
case 0x320:
dbg_log("timer lvt: " + h(value >>> 0, 8), LOG_APIC);
this.lvt_timer = value;
break;
case 0x330:
dbg_log("lvt thermal sensor: " + h(value >>> 0, 8), LOG_APIC);
this.lvt_thermal_sensor = value;
break;
case 0x340:
dbg_log("lvt perf counter: " + h(value >>> 0, 8), LOG_APIC);
this.lvt_perf_counter = value;
break;
case 0x350:
dbg_log("lvt int0: " + h(value >>> 0, 8), LOG_APIC);
this.lvt_int0 = value;
break;
case 0x360:
dbg_log("lvt int1: " + h(value >>> 0, 8), LOG_APIC);
this.lvt_int1 = value;
break;
case 0x370:
dbg_log("lvt error: " + h(value >>> 0, 8), LOG_APIC);
this.lvt_error = value;
break;
case 0x3E0:
dbg_log("timer divider: " + h(value >>> 0, 8), LOG_APIC);
this.timer_divider = value;
var divide_shift = value & 0b11 | (value & 0b1000) >> 1;
this.timer_divider_shift = divide_shift === 0b111 ? 0 : divide_shift + 1;
break;
case 0x380:
dbg_log("timer initial: " + h(value >>> 0, 8), LOG_APIC);
this.timer_initial_count = value >>> 0;
this.timer_current_count = value >>> 0;
this.next_tick = v86.microtick();
this.timer_active = true;
break;
case 0x390:
dbg_log("timer current: " + h(value >>> 0, 8), LOG_APIC);
dbg_assert(false, "read-only register");
break;
default:
dbg_log("APIC write32 " + h(addr) + " <- " + h(value >>> 0, 8), LOG_APIC);
dbg_assert(false);
}
};
APIC.prototype.timer = function(now)
{
if(this.timer_current_count === 0)
{
return 100;
}
const freq = APIC_TIMER_FREQ / (1 << this.timer_divider_shift);
const steps = (now - this.next_tick) * freq >>> 0;
this.next_tick += steps / freq;
this.timer_current_count -= steps;
if(this.timer_current_count <= 0)
{
var mode = this.lvt_timer & APIC_TIMER_MODE_MASK;
if(mode === APIC_TIMER_MODE_PERIODIC)
{
this.timer_current_count = this.timer_current_count % this.timer_initial_count;
if(this.timer_current_count <= 0)
{
this.timer_current_count += this.timer_initial_count;
}
dbg_assert(this.timer_current_count !== 0);
if((this.lvt_timer & IOAPIC_CONFIG_MASKED) === 0)
{
this.deliver(this.lvt_timer & 0xFF, IOAPIC_DELIVERY_FIXED, false);
}
}
else if(mode === APIC_TIMER_MODE_ONE_SHOT)
{
this.timer_current_count = 0;
dbg_log("APIC timer one shot end", LOG_APIC);
if((this.lvt_timer & IOAPIC_CONFIG_MASKED) === 0)
{
this.deliver(this.lvt_timer & 0xFF, IOAPIC_DELIVERY_FIXED, false);
}
}
}
return Math.max(0, this.timer_current_count / freq);
};
APIC.prototype.route = function(vector, mode, is_level, destination, destination_mode)
{
// TODO
this.deliver(vector, mode, is_level);
};
APIC.prototype.deliver = function(vector, mode, is_level)
{
APIC_LOG_VERBOSE && dbg_log("Deliver " + h(vector, 2) + " mode=" + mode + " level=" + is_level, LOG_APIC);
if(mode === IOAPIC_DELIVERY_INIT)
{
// TODO
return;
}
if(mode === IOAPIC_DELIVERY_NMI)
{
// TODO
return;
}
if(vector < 0x10 || vector === 0xFF)
{
dbg_assert(false, "TODO: Invalid vector");
}
if(this.register_get_bit(this.irr, vector))
{
dbg_log("Not delivered: irr already set, vector=" + h(vector, 2), LOG_APIC);
return;
}
this.register_set_bit(this.irr, vector);
if(is_level)
{
this.register_set_bit(this.tmr, vector);
}
else
{
this.register_clear_bit(this.tmr, vector);
}
this.check_vector();
};
APIC.prototype.highest_irr = function()
{
var highest = this.register_get_highest_bit(this.irr);
dbg_assert(highest !== 0xFF);
dbg_assert(highest >= 0x10 || highest === -1);
return highest;
};
APIC.prototype.highest_isr = function()
{
var highest = this.register_get_highest_bit(this.isr);
dbg_assert(highest !== 0xFF);
dbg_assert(highest >= 0x10 || highest === -1);
return highest;
};
APIC.prototype.check_vector = function()
{
var highest_irr = this.highest_irr();
if(highest_irr === -1)
{
return;
}
var highest_isr = this.highest_isr();
if(highest_isr >= highest_irr)
{
APIC_LOG_VERBOSE && dbg_log("Higher isr, isr=" + h(highest_isr) + " irr=" + h(highest_irr), LOG_APIC);
return;
}
if((highest_irr & 0xF0) <= (this.tpr & 0xF0))
{
APIC_LOG_VERBOSE && dbg_log("Higher tpr, tpr=" + h(this.tpr & 0xF0) + " irr=" + h(highest_irr), LOG_APIC);
return;
}
this.cpu.handle_irqs();
};
APIC.prototype.acknowledge_irq = function()
{
var highest_irr = this.highest_irr();
if(highest_irr === -1)
{
//dbg_log("Spurious", LOG_APIC);
return -1;
}
var highest_isr = this.highest_isr();
if(highest_isr >= highest_irr)
{
APIC_LOG_VERBOSE && dbg_log("Higher isr, isr=" + h(highest_isr) + " irr=" + h(highest_irr), LOG_APIC);
return -1;
}
if((highest_irr & 0xF0) <= (this.tpr & 0xF0))
{
APIC_LOG_VERBOSE && dbg_log("Higher tpr, tpr=" + h(this.tpr & 0xF0) + " irr=" + h(highest_irr), LOG_APIC);
return -1;
}
this.register_clear_bit(this.irr, highest_irr);
this.register_set_bit(this.isr, highest_irr);
APIC_LOG_VERBOSE && dbg_log("Calling vector " + h(highest_irr), LOG_APIC);
this.check_vector();
return highest_irr;
};
APIC.prototype.get_state = function()
{
var state = [];
state[0] = this.apic_id;
state[1] = this.timer_divider;
state[2] = this.timer_divider_shift;
state[3] = this.timer_initial_count;
state[4] = this.timer_current_count;
state[5] = this.next_tick;
state[6] = this.lvt_timer;
state[7] = this.lvt_perf_counter;
state[8] = this.lvt_int0;
state[9] = this.lvt_int1;
state[10] = this.lvt_error;
state[11] = this.tpr;
state[12] = this.icr0;
state[13] = this.icr1;
state[14] = this.irr;
state[15] = this.isr;
state[16] = this.tmr;
state[17] = this.spurious_vector;
state[18] = this.destination_format;
state[19] = this.local_destination;
state[20] = this.error;
state[21] = this.read_error;
state[22] = this.lvt_thermal_sensor;
return state;
};
APIC.prototype.set_state = function(state)
{
this.apic_id = state[0];
this.timer_divider = state[1];
this.timer_divider_shift = state[2];
this.timer_initial_count = state[3];
this.timer_current_count = state[4];
this.next_tick = state[5];
this.lvt_timer = state[6];
this.lvt_perf_counter = state[7];
this.lvt_int0 = state[8];
this.lvt_int1 = state[9];
this.lvt_error = state[10];
this.tpr = state[11];
this.icr0 = state[12];
this.icr1 = state[13];
this.irr = state[14];
this.isr = state[15];
this.tmr = state[16];
this.spurious_vector = state[17];
this.destination_format = state[18];
this.local_destination = state[19];
this.error = state[20];
this.read_error = state[21];
this.lvt_thermal_sensor = state[22] || IOAPIC_CONFIG_MASKED;
};
// functions operating on 256-bit registers (for irr, isr, tmr)
APIC.prototype.register_get_bit = function(v, bit)
{
dbg_assert(bit >= 0 && bit < 256);
return v[bit >> 5] >> (bit & 31) & 1;
};
APIC.prototype.register_set_bit = function(v, bit)
{
dbg_assert(bit >= 0 && bit < 256);
v[bit >> 5] |= 1 << (bit & 31);
};
APIC.prototype.register_clear_bit = function(v, bit)
{
dbg_assert(bit >= 0 && bit < 256);
v[bit >> 5] &= ~(1 << (bit & 31));
};
APIC.prototype.register_get_highest_bit = function(v)
{
for(var i = 7; i >= 0; i--)
{
var word = v[i];
if(word)
{
return v86util.int_log2(word >>> 0) | i << 5;
}
}
return -1;
};

View file

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

View file

@ -1,4 +1,6 @@
"use strict";
import { LOG_FETCH } from "../const.js";
import { h } from "../lib.js";
import { dbg_assert, dbg_log } from "../log.js";
// https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
const ETHERTYPE_IPV4 = 0x0800;
@ -23,17 +25,17 @@ const V86_ASCII = [118, 56, 54];
*
* State TIME_WAIT is not needed, we can skip it and transition directly to CLOSED instead.
*/
const TCP_STATE_CLOSED = "closed";
const TCP_STATE_SYN_RECEIVED = "syn-received";
const TCP_STATE_SYN_SENT = "syn-sent";
const TCP_STATE_SYN_PROBE = "syn-probe";
export const TCP_STATE_CLOSED = "closed";
export const TCP_STATE_SYN_RECEIVED = "syn-received";
export const TCP_STATE_SYN_SENT = "syn-sent";
export const TCP_STATE_SYN_PROBE = "syn-probe";
//const TCP_STATE_LISTEN = "listen";
const TCP_STATE_ESTABLISHED = "established";
const TCP_STATE_FIN_WAIT_1 = "fin-wait-1";
const TCP_STATE_CLOSE_WAIT = "close-wait";
const TCP_STATE_FIN_WAIT_2 = "fin-wait-2";
const TCP_STATE_LAST_ACK = "last-ack";
const TCP_STATE_CLOSING = "closing";
export const TCP_STATE_ESTABLISHED = "established";
export const TCP_STATE_FIN_WAIT_1 = "fin-wait-1";
export const TCP_STATE_CLOSE_WAIT = "close-wait";
export const TCP_STATE_FIN_WAIT_2 = "fin-wait-2";
export const TCP_STATE_LAST_ACK = "last-ack";
export const TCP_STATE_CLOSING = "closing";
//const TCP_STATE_TIME_WAIT = "time-wait";
// source: RFC6335, 6. Port Number Ranges
@ -43,18 +45,14 @@ const TCP_DYNAMIC_PORT_RANGE = TCP_DYNAMIC_PORT_END - TCP_DYNAMIC_PORT_START;
const ETH_HEADER_SIZE = 14;
const ETH_PAYLOAD_OFFSET = ETH_HEADER_SIZE;
const ETH_PAYLOAD_SIZE = 1500;
const MTU_DEFAULT = 1500;
const ETH_TRAILER_SIZE = 4;
const ETH_FRAME_SIZE = ETH_HEADER_SIZE + ETH_PAYLOAD_SIZE + ETH_TRAILER_SIZE;
const IPV4_HEADER_SIZE = 20;
const IPV4_PAYLOAD_OFFSET = ETH_PAYLOAD_OFFSET + IPV4_HEADER_SIZE;
const IPV4_PAYLOAD_SIZE = ETH_PAYLOAD_SIZE - IPV4_HEADER_SIZE;
const UDP_HEADER_SIZE = 8;
const UDP_PAYLOAD_OFFSET = IPV4_PAYLOAD_OFFSET + UDP_HEADER_SIZE;
const UDP_PAYLOAD_SIZE = IPV4_PAYLOAD_SIZE - UDP_HEADER_SIZE;
const TCP_HEADER_SIZE = 20;
const TCP_PAYLOAD_OFFSET = IPV4_PAYLOAD_OFFSET + TCP_HEADER_SIZE;
const TCP_PAYLOAD_SIZE = IPV4_PAYLOAD_SIZE - TCP_HEADER_SIZE;
const ICMP_HEADER_SIZE = 4;
const DEFAULT_DOH_SERVER = "cloudflare-dns.com";
@ -92,6 +90,7 @@ class GrowableRingbuffer
const total_length = this.length + src_length;
let capacity = this.buffer.length;
if(capacity < total_length) {
dbg_assert(capacity > 0);
while(capacity < total_length) {
capacity *= 2;
}
@ -158,15 +157,19 @@ class GrowableRingbuffer
}
}
function create_eth_encoder_buf()
export function create_eth_encoder_buf(mtu = MTU_DEFAULT)
{
const ETH_FRAME_SIZE = ETH_HEADER_SIZE + mtu + ETH_TRAILER_SIZE;
const IPV4_PAYLOAD_SIZE = mtu - IPV4_HEADER_SIZE;
const UDP_PAYLOAD_SIZE = IPV4_PAYLOAD_SIZE - UDP_HEADER_SIZE;
const eth_frame = new Uint8Array(ETH_FRAME_SIZE);
const buffer = eth_frame.buffer;
const offset = eth_frame.byteOffset;
return {
eth_frame: eth_frame,
eth_frame_view: new DataView(buffer),
eth_payload_view: new DataView(buffer, offset + ETH_PAYLOAD_OFFSET, ETH_PAYLOAD_SIZE),
eth_payload_view: new DataView(buffer, offset + ETH_PAYLOAD_OFFSET, mtu),
ipv4_payload_view: new DataView(buffer, offset + IPV4_PAYLOAD_OFFSET, IPV4_PAYLOAD_SIZE),
udp_payload_view: new DataView(buffer, offset + UDP_PAYLOAD_OFFSET, UDP_PAYLOAD_SIZE),
text_encoder: new TextEncoder()
@ -187,6 +190,17 @@ function view_set_array(offset, data, view, out)
return data.length;
}
/**
* Write zeros into the view starting at offset
* @param {number} offset
* @param {number} length
* @param {DataView} view
*/
function view_set_zeros(offset, length, view, out)
{
out.eth_frame.fill(0, view.byteOffset + offset, view.byteOffset + offset + length);
}
/**
* UTF8-encode given string into view starting at offset, return number of bytes written.
*
@ -219,8 +233,8 @@ function calc_inet_checksum(length, checksum, view, out)
if(length & 1) {
checksum += eth_frame[uint16_end] << 8;
}
while(checksum >> 16) {
checksum = (checksum & 0xffff) + (checksum >> 16);
while(checksum >>> 16) {
checksum = (checksum & 0xffff) + (checksum >>> 16);
}
return ~checksum & 0xffff;
}
@ -240,13 +254,30 @@ function handle_fake_tcp(packet, adapter)
{
const tuple = `${packet.ipv4.src.join(".")}:${packet.tcp.sport}:${packet.ipv4.dest.join(".")}:${packet.tcp.dport}`;
if(packet.tcp.syn) {
if(packet.tcp.syn && !packet.tcp.ack) {
if(adapter.tcp_conn[tuple]) {
dbg_log("SYN to already opened port", LOG_FETCH);
delete adapter.tcp_conn[tuple];
}
if(adapter.on_tcp_connection(packet, tuple)) {
return;
let conn = new TCPConnection(adapter);
conn.state = TCP_STATE_SYN_RECEIVED;
conn.tuple = tuple;
conn.last = packet;
conn.hsrc = packet.eth.dest;
conn.psrc = packet.ipv4.dest;
conn.sport = packet.tcp.dport;
conn.hdest = packet.eth.src;
conn.dport = packet.tcp.sport;
conn.pdest = packet.ipv4.src;
adapter.bus.pair.send("tcp-connection", conn);
if(adapter.on_tcp_connection) {
adapter.on_tcp_connection(conn, packet);
}
if(adapter.tcp_conn[tuple]) return;
}
if(!adapter.tcp_conn[tuple]) {
@ -445,7 +476,7 @@ function handle_fake_dhcp(packet, adapter) {
adapter.receive(make_packet(adapter.eth_encoder_buf, reply));
}
function handle_fake_networking(data, adapter) {
export function handle_fake_networking(data, adapter) {
let packet = {};
parse_eth(data, packet);
@ -946,19 +977,31 @@ function write_tcp(spec, out) {
if(tcp.ece) flags |= 0x40;
if(tcp.cwr) flags |= 0x80;
const doff = TCP_HEADER_SIZE >> 2; // header length in 32-bit words
let doff = TCP_HEADER_SIZE;
if(tcp.options) {
if(tcp.options.mss) {
view.setUint8(doff, 0x02); //mss option type
view.setUint8(doff + 1, 0x04); //option length
view.setUint16(doff + 2, tcp.options.mss);
doff += 4;
}
}
let total_length = Math.ceil(doff / 4) * 4; // needs to a multiple of 4 bytes
if(tcp.options && total_length - doff > 0) {
view_set_zeros(doff, total_length - doff, view, out); //write zeros into remaining space for options
}
view.setUint16(0, tcp.sport);
view.setUint16(2, tcp.dport);
view.setUint32(4, tcp.seq);
view.setUint32(8, tcp.ackn);
view.setUint8(12, doff << 4);
view.setUint8(12, (total_length >> 2) << 4); // header length in 32-bit words
view.setUint8(13, flags);
view.setUint16(14, tcp.winsize);
view.setUint16(16, 0); // checksum initially zero before calculation
view.setUint16(18, tcp.urgent || 0);
let total_length = TCP_HEADER_SIZE;
if(spec.tcp_data) {
total_length += view_set_array(TCP_HEADER_SIZE, spec.tcp_data, view, out);
}
@ -974,7 +1017,7 @@ function write_tcp(spec, out) {
return total_length;
}
function fake_tcp_connect(dport, adapter)
export function fake_tcp_connect(dport, adapter)
{
const vm_ip_str = adapter.vm_ip.join(".");
const router_ip_str = adapter.router_ip.join(".");
@ -988,7 +1031,7 @@ function fake_tcp_connect(dport, adapter)
throw new Error("pool of dynamic TCP port numbers exhausted, connection aborted");
}
let conn = new TCPConnection();
let conn = new TCPConnection(adapter);
conn.tuple = tuple;
conn.hsrc = adapter.router_mac;
@ -997,13 +1040,12 @@ function fake_tcp_connect(dport, adapter)
conn.hdest = adapter.vm_mac;
conn.dport = dport;
conn.pdest = adapter.vm_ip;
conn.net = adapter;
adapter.tcp_conn[tuple] = conn;
conn.connect();
return conn;
}
function fake_tcp_probe(dport, adapter) {
export function fake_tcp_probe(dport, adapter) {
return new Promise((res, rej) => {
let handle = fake_tcp_connect(dport, adapter);
handle.state = TCP_STATE_SYN_PROBE;
@ -1014,10 +1056,14 @@ function fake_tcp_probe(dport, adapter) {
/**
* @constructor
*/
function TCPConnection()
export function TCPConnection(adapter)
{
this.mtu = (adapter.mtu || MTU_DEFAULT);
const IPV4_PAYLOAD_SIZE = this.mtu - IPV4_HEADER_SIZE;
const TCP_PAYLOAD_SIZE = IPV4_PAYLOAD_SIZE - TCP_HEADER_SIZE;
this.state = TCP_STATE_CLOSED;
this.net = null; // The adapter is stored here
this.net = adapter; // The adapter is stored here
this.send_buffer = new GrowableRingbuffer(2048, 0);
this.send_chunk_buf = new Uint8Array(TCP_PAYLOAD_SIZE);
this.in_active_close = false;
@ -1080,7 +1126,9 @@ TCPConnection.prototype.connect = function() {
this.ack = 1;
this.start_seq = 0;
this.winsize = 64240;
this.state = TCP_STATE_SYN_SENT;
if(this.state !== TCP_STATE_SYN_PROBE) {
this.state = TCP_STATE_SYN_SENT;
}
let reply = this.ipv4_reply();
reply.ipv4.id = 2345;
@ -1096,16 +1144,12 @@ TCPConnection.prototype.connect = function() {
};
TCPConnection.prototype.accept = function(packet) {
TCPConnection.prototype.accept = function(packet=undefined) {
packet = packet || this.last;
this.net.tcp_conn[this.tuple] = this;
this.seq = 1338;
this.ack = packet.tcp.seq + 1;
this.start_seq = packet.tcp.seq;
this.hsrc = this.net.router_mac;
this.psrc = packet.ipv4.dest;
this.sport = packet.tcp.dport;
this.hdest = packet.eth.src;
this.dport = packet.tcp.sport;
this.pdest = packet.ipv4.src;
this.winsize = packet.tcp.winsize;
let reply = this.ipv4_reply();
@ -1116,7 +1160,10 @@ TCPConnection.prototype.accept = function(packet) {
ackn: this.ack,
winsize: packet.tcp.winsize,
syn: true,
ack: true
ack: true,
options: {
mss: (this.mtu - TCP_HEADER_SIZE - IPV4_HEADER_SIZE)
}
};
// dbg_log(`TCP[${this.tuple}]: accept(): sending SYN+ACK in state "${this.state}", next "${TCP_STATE_ESTABLISHED}"`, LOG_FETCH);
this.state = TCP_STATE_ESTABLISHED;
@ -1124,6 +1171,7 @@ TCPConnection.prototype.accept = function(packet) {
};
TCPConnection.prototype.process = function(packet) {
this.last = packet;
if(this.state === TCP_STATE_CLOSED) {
// dbg_log(`TCP[${this.tuple}]: WARNING: connection already closed, packet dropped`, LOG_FETCH);
const reply = this.packet_reply(packet, {rst: true});

View file

@ -1,13 +1,25 @@
"use strict";
import { LOG_FETCH } from "../const.js";
import { dbg_log } from "../log.js";
import {
create_eth_encoder_buf,
handle_fake_networking,
TCPConnection,
TCP_STATE_SYN_RECEIVED,
fake_tcp_connect,
fake_tcp_probe
} from "./fake_network.js";
// For Types Only
import { BusConnector } from "../bus.js";
/**
* @constructor
*
* @param {BusConnector} bus
* @param {*=} config
* @export
*/
function FetchNetworkAdapter(bus, config)
export function FetchNetworkAdapter(bus, config)
{
config = config || {};
this.bus = bus;
@ -20,7 +32,8 @@ function FetchNetworkAdapter(bus, config)
this.dns_method = config.dns_method || "static";
this.doh_server = config.doh_server;
this.tcp_conn = {};
this.eth_encoder_buf = create_eth_encoder_buf();
this.mtu = config.mtu;
this.eth_encoder_buf = create_eth_encoder_buf(this.mtu);
this.fetch = (...args) => fetch(...args);
// Ex: 'https://corsproxy.io/?'
@ -33,27 +46,18 @@ function FetchNetworkAdapter(bus, config)
{
this.send(data);
}, this);
this.bus.register("tcp-connection", (conn) => {
if(conn.sport === 80) {
conn.on("data", on_data_http);
conn.accept();
}
}, this);
}
FetchNetworkAdapter.prototype.destroy = function()
{
};
FetchNetworkAdapter.prototype.on_tcp_connection = function(packet, tuple)
{
if(packet.tcp.dport === 80) {
let conn = new TCPConnection();
conn.state = TCP_STATE_SYN_RECEIVED;
conn.net = this;
conn.on("data", on_data_http);
conn.tuple = tuple;
conn.accept(packet);
this.tcp_conn[tuple] = conn;
return true;
}
return false;
};
FetchNetworkAdapter.prototype.connect = function(port)
{
return fake_tcp_connect(port, this);
@ -97,13 +101,27 @@ async function on_data_http(data)
const header = this.net.parse_http_header(headers[i]);
if(!header) {
console.warn('The request contains an invalid header: "%s"', headers[i]);
this.write(new TextEncoder().encode("HTTP/1.1 400 Bad Request\r\nContent-Length: 0"));
this.net.respond_text_and_close(this, 400, "Bad Request", `Invalid header in request: ${headers[i]}`);
return;
}
if( header.key.toLowerCase() === "host" ) target.host = header.value;
else req_headers.append(header.key, header.value);
}
if(!this.net.cors_proxy && /^\d+\.external$/.test(target.hostname)) {
dbg_log("Request to localhost: " + target.href, LOG_FETCH);
const localport = parseInt(target.hostname.split(".")[0], 10);
if(!isNaN(localport) && localport > 0 && localport < 65536) {
target.protocol = "http:";
target.hostname = "localhost";
target.port = localport.toString(10);
} else {
console.warn('Unknown port for localhost: "%s"', target.href);
this.net.respond_text_and_close(this, 400, "Bad Request", `Unknown port for localhost: ${target.href}`);
return;
}
}
dbg_log("HTTP Dispatch: " + target.href, LOG_FETCH);
this.name = target.href;
let opts = {
@ -117,19 +135,17 @@ async function on_data_http(data)
const fetch_url = this.net.cors_proxy ? this.net.cors_proxy + encodeURIComponent(target.href) : target.href;
const encoder = new TextEncoder();
let response_started = false;
this.net.fetch(fetch_url, opts).then((resp) => {
const header_lines = [
`HTTP/1.1 ${resp.status} ${resp.statusText}`,
`x-was-fetch-redirected: ${!!resp.redirected}`,
`x-fetch-resp-url: ${resp.url}`,
"Connection: closed"
];
for(const [key, value] of resp.headers.entries()) {
if(!["content-encoding", "connection", "content-length", "transfer-encoding"].includes(key.toLowerCase())) {
header_lines.push(`${key}: ${value}`);
}
}
this.write(encoder.encode(header_lines.join("\r\n") + "\r\n\r\n"));
let handler = (resp) => {
let resp_headers = new Headers(resp.headers);
resp_headers.delete("content-encoding");
resp_headers.delete("keep-alive");
resp_headers.delete("content-length");
resp_headers.delete("transfer-encoding");
resp_headers.set("x-was-fetch-redirected", `${!!resp.redirected}`);
resp_headers.set("x-fetch-resp-url", resp.url);
resp_headers.set("connection", "close");
this.write(this.net.form_response_head(resp.status, resp.statusText, resp_headers));
response_started = true;
if(resp.body && resp.body.getReader) {
@ -152,18 +168,13 @@ async function on_data_http(data)
this.close();
});
}
})
};
this.net.fetch(fetch_url, opts).then(handler)
.catch((e) => {
console.warn("Fetch Failed: " + fetch_url + "\n" + e);
if(!response_started) {
const body = encoder.encode(`Fetch ${fetch_url} failed:\n\n${e.stack || e.message}`);
const header_lines = [
"HTTP/1.1 502 Fetch Error",
"Content-Type: text/plain",
`Content-Length: ${body.length}`,
"Connection: closed"
];
this.writev([encoder.encode(header_lines.join("\r\n") + "\r\n\r\n"), body]);
this.net.respond_text_and_close(this, 502, "Fetch Error", `Fetch ${fetch_url} failed:\n\n${e.stack || e.message}`);
}
this.close();
});
@ -183,19 +194,41 @@ FetchNetworkAdapter.prototype.fetch = async function(url, options)
catch(e)
{
console.warn("Fetch Failed: " + url + "\n" + e);
let headers = new Headers();
headers.set("Content-Type", "text/plain");
return [
{
status: 502,
statusText: "Fetch Error",
headers: headers,
headers: new Headers({ "Content-Type": "text/plain" }),
},
new TextEncoder().encode(`Fetch ${url} failed:\n\n${e.stack}`).buffer
];
}
};
FetchNetworkAdapter.prototype.form_response_head = function(status_code, status_text, headers)
{
let lines = [
`HTTP/1.1 ${status_code} ${status_text}`
];
for(const [key, value] of headers.entries()) {
lines.push(`${key}: ${value}`);
}
return new TextEncoder().encode(lines.join("\r\n") + "\r\n\r\n");
};
FetchNetworkAdapter.prototype.respond_text_and_close = function(conn, status_code, status_text, body)
{
const headers = new Headers({
"content-type": "text/plain",
"content-length": body.length.toString(10),
"connection": "close"
});
conn.writev([this.form_response_head(status_code, status_text, headers), new TextEncoder().encode(body)]);
conn.close();
};
FetchNetworkAdapter.prototype.parse_http_header = function(header)
{
const parts = header.match(/^([^:]*):(.*)$/);

View file

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

View file

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

View file

@ -1,12 +1,9 @@
"use strict";
// For Types Only
import { BusConnector } from "../bus.js";
/** @const */
var SHIFT_SCAN_CODE = 0x2A;
const SHIFT_SCAN_CODE = 0x2A;
const SCAN_CODE_RELEASE = 0x80;
/** @const */
var SCAN_CODE_RELEASE = 0x80;
/** @const */
const PLATFOM_WINDOWS = typeof window !== "undefined" && window.navigator.platform.toString().toLowerCase().search("win") >= 0;
/**
@ -14,7 +11,7 @@ const PLATFOM_WINDOWS = typeof window !== "undefined" && window.navigator.platfo
*
* @param {BusConnector} bus
*/
function KeyboardAdapter(bus)
export function KeyboardAdapter(bus)
{
var
/**
@ -48,12 +45,9 @@ function KeyboardAdapter(bus)
*/
this.emu_enabled = true;
/**
* Format:
* Javascript event.keyCode -> make code
* @const
*/
var charmap = new Uint16Array([
// Format:
// Javascript event.keyCode -> make code
const charmap = new Uint16Array([
0, 0, 0, 0, 0, 0, 0, 0,
// 0x08: backspace, tab, enter
0x0E, 0x0F, 0, 0, 0, 0x1C, 0, 0,
@ -128,12 +122,9 @@ function KeyboardAdapter(bus)
]);
/**
* ascii -> javascript event code (US layout)
* @const
*/
var asciimap = {8: 8, 10: 13, 32: 32, 39: 222, 44: 188, 45: 189, 46: 190, 47: 191, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52, 53: 53, 54: 54, 55: 55, 56: 56, 57: 57, 59: 186, 61: 187, 91: 219, 92: 220, 93: 221, 96: 192, 97: 65, 98: 66, 99: 67, 100: 68, 101: 69, 102: 70, 103: 71, 104: 72, 105: 73, 106: 74, 107: 75, 108: 76, 109: 77, 110: 78, 111: 79, 112: 80, 113: 81, 114: 82, 115: 83, 116: 84, 117: 85, 118: 86, 119: 87, 120: 88, 121: 89, 122: 90};
var asciimap_shift = {33: 49, 34: 222, 35: 51, 36: 52, 37: 53, 38: 55, 40: 57, 41: 48, 42: 56, 43: 187, 58: 186, 60: 188, 62: 190, 63: 191, 64: 50, 65: 65, 66: 66, 67: 67, 68: 68, 69: 69, 70: 70, 71: 71, 72: 72, 73: 73, 74: 74, 75: 75, 76: 76, 77: 77, 78: 78, 79: 79, 80: 80, 81: 81, 82: 82, 83: 83, 84: 84, 85: 85, 86: 86, 87: 87, 88: 88, 89: 89, 90: 90, 94: 54, 95: 189, 123: 219, 124: 220, 125: 221, 126: 192};
// ascii -> javascript event code (US layout)
const asciimap = {8: 8, 10: 13, 32: 32, 39: 222, 44: 188, 45: 189, 46: 190, 47: 191, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52, 53: 53, 54: 54, 55: 55, 56: 56, 57: 57, 59: 186, 61: 187, 91: 219, 92: 220, 93: 221, 96: 192, 97: 65, 98: 66, 99: 67, 100: 68, 101: 69, 102: 70, 103: 71, 104: 72, 105: 73, 106: 74, 107: 75, 108: 76, 109: 77, 110: 78, 111: 79, 112: 80, 113: 81, 114: 82, 115: 83, 116: 84, 117: 85, 118: 86, 119: 87, 120: 88, 121: 89, 122: 90};
const asciimap_shift = {33: 49, 34: 222, 35: 51, 36: 52, 37: 53, 38: 55, 40: 57, 41: 48, 42: 56, 43: 187, 58: 186, 60: 188, 62: 190, 63: 191, 64: 50, 65: 65, 66: 66, 67: 67, 68: 68, 69: 69, 70: 70, 71: 71, 72: 72, 73: 73, 74: 74, 75: 75, 76: 76, 77: 77, 78: 78, 79: 79, 80: 80, 81: 81, 82: 82, 83: 83, 84: 84, 85: 85, 86: 86, 87: 87, 88: 88, 89: 89, 90: 90, 94: 54, 95: 189, 123: 219, 124: 220, 125: 221, 126: 192};
// From:
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code#Code_values_on_Linux_%28X11%29_%28When_scancode_is_available%29
@ -246,7 +237,9 @@ function KeyboardAdapter(bus)
"Insert": 0xe052,
"Delete": 0xe053,
"MetaLeft": 0xe05b,
"OSLeft": 0xe05b,
"MetaRight": 0xe05c,
"OSRight": 0xe05c,
"ContextMenu": 0xe05d,
};
@ -260,6 +253,7 @@ function KeyboardAdapter(bus)
window.removeEventListener("keyup", keyup_handler, false);
window.removeEventListener("keydown", keydown_handler, false);
window.removeEventListener("blur", blur_handler, false);
window.removeEventListener("input", input_handler, false);
}
};
@ -274,6 +268,7 @@ function KeyboardAdapter(bus)
window.addEventListener("keyup", keyup_handler, false);
window.addEventListener("keydown", keydown_handler, false);
window.addEventListener("blur", blur_handler, false);
window.addEventListener("input", input_handler, false);
};
this.init();
@ -386,6 +381,37 @@ function KeyboardAdapter(bus)
keys_pressed = {};
}
function input_handler(e)
{
if(!keyboard.bus)
{
return;
}
if(!may_handle(e))
{
return;
}
switch(e.inputType)
{
case "insertText":
for(var i = 0; i < e.data.length; i++)
{
keyboard.simulate_char(e.data[i]);
}
break;
case "insertLineBreak":
keyboard.simulate_press(13); // enter
break;
case "deleteContentBackward":
keyboard.simulate_press(8); // backspace
break;
}
}
/**
* @param {KeyboardEvent|Object} e
* @param {boolean} keydown
@ -402,6 +428,12 @@ function KeyboardAdapter(bus)
return;
}
if(e.code === "" || e.key === "Process" || e.key === "Unidentified" || e.keyCode === 229)
{
// Handling mobile browsers and virtual keyboards
return;
}
e.preventDefault && e.preventDefault();
if(PLATFOM_WINDOWS)

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -1,287 +1,281 @@
"use strict";
import { pads } from "../lib.js";
/**
* @export
*/
const print_stats = {
stats_to_string: function(cpu)
export function stats_to_string(cpu)
{
return print_misc_stats(cpu) + print_instruction_counts(cpu);
}
function print_misc_stats(cpu)
{
let text = "";
const stat_names = [
"COMPILE",
"COMPILE_SKIPPED_NO_NEW_ENTRY_POINTS",
"COMPILE_WRONG_ADDRESS_SPACE",
"COMPILE_CUT_OFF_AT_END_OF_PAGE",
"COMPILE_WITH_LOOP_SAFETY",
"COMPILE_PAGE",
"COMPILE_PAGE/COMPILE",
"COMPILE_BASIC_BLOCK",
"COMPILE_DUPLICATED_BASIC_BLOCK",
"COMPILE_WASM_BLOCK",
"COMPILE_WASM_LOOP",
"COMPILE_DISPATCHER",
"COMPILE_ENTRY_POINT",
"COMPILE_WASM_TOTAL_BYTES",
"COMPILE_WASM_TOTAL_BYTES/COMPILE_PAGE",
"RUN_INTERPRETED",
"RUN_INTERPRETED_NEW_PAGE",
"RUN_INTERPRETED_PAGE_HAS_CODE",
"RUN_INTERPRETED_PAGE_HAS_ENTRY_AFTER_PAGE_WALK",
"RUN_INTERPRETED_NEAR_END_OF_PAGE",
"RUN_INTERPRETED_DIFFERENT_STATE",
"RUN_INTERPRETED_DIFFERENT_STATE_CPL3",
"RUN_INTERPRETED_DIFFERENT_STATE_FLAT",
"RUN_INTERPRETED_DIFFERENT_STATE_IS32",
"RUN_INTERPRETED_DIFFERENT_STATE_SS32",
"RUN_INTERPRETED_MISSED_COMPILED_ENTRY_RUN_INTERPRETED",
"RUN_INTERPRETED_STEPS",
"RUN_FROM_CACHE",
"RUN_FROM_CACHE_STEPS",
"RUN_FROM_CACHE_STEPS/RUN_FROM_CACHE",
"RUN_FROM_CACHE_STEPS/RUN_INTERPRETED_STEPS",
"DIRECT_EXIT",
"INDIRECT_JUMP",
"INDIRECT_JUMP_NO_ENTRY",
"NORMAL_PAGE_CHANGE",
"NORMAL_FALLTHRU",
"NORMAL_FALLTHRU_WITH_TARGET_BLOCK",
"NORMAL_BRANCH",
"NORMAL_BRANCH_WITH_TARGET_BLOCK",
"CONDITIONAL_JUMP",
"CONDITIONAL_JUMP_PAGE_CHANGE",
"CONDITIONAL_JUMP_EXIT",
"CONDITIONAL_JUMP_FALLTHRU",
"CONDITIONAL_JUMP_FALLTHRU_WITH_TARGET_BLOCK",
"CONDITIONAL_JUMP_BRANCH",
"CONDITIONAL_JUMP_BRANCH_WITH_TARGET_BLOCK",
"DISPATCHER_SMALL",
"DISPATCHER_LARGE",
"LOOP",
"LOOP_SAFETY",
"CONDITION_OPTIMISED",
"CONDITION_UNOPTIMISED",
"CONDITION_UNOPTIMISED_PF",
"CONDITION_UNOPTIMISED_UNHANDLED_L",
"CONDITION_UNOPTIMISED_UNHANDLED_LE",
"FAILED_PAGE_CHANGE",
"SAFE_READ_FAST",
"SAFE_READ_SLOW_PAGE_CROSSED",
"SAFE_READ_SLOW_NOT_VALID",
"SAFE_READ_SLOW_NOT_USER",
"SAFE_READ_SLOW_IN_MAPPED_RANGE",
"SAFE_WRITE_FAST",
"SAFE_WRITE_SLOW_PAGE_CROSSED",
"SAFE_WRITE_SLOW_NOT_VALID",
"SAFE_WRITE_SLOW_NOT_USER",
"SAFE_WRITE_SLOW_IN_MAPPED_RANGE",
"SAFE_WRITE_SLOW_READ_ONLY",
"SAFE_WRITE_SLOW_HAS_CODE",
"SAFE_READ_WRITE_FAST",
"SAFE_READ_WRITE_SLOW_PAGE_CROSSED",
"SAFE_READ_WRITE_SLOW_NOT_VALID",
"SAFE_READ_WRITE_SLOW_NOT_USER",
"SAFE_READ_WRITE_SLOW_IN_MAPPED_RANGE",
"SAFE_READ_WRITE_SLOW_READ_ONLY",
"SAFE_READ_WRITE_SLOW_HAS_CODE",
"PAGE_FAULT",
"TLB_MISS",
"MAIN_LOOP",
"MAIN_LOOP_IDLE",
"DO_MANY_CYCLES",
"CYCLE_INTERNAL",
"INVALIDATE_ALL_MODULES_NO_FREE_WASM_INDICES",
"INVALIDATE_MODULE_WRITTEN_WHILE_COMPILED",
"INVALIDATE_MODULE_UNUSED_AFTER_OVERWRITE",
"INVALIDATE_MODULE_DIRTY_PAGE",
"INVALIDATE_PAGE_HAD_CODE",
"INVALIDATE_PAGE_HAD_ENTRY_POINTS",
"DIRTY_PAGE_DID_NOT_HAVE_CODE",
"RUN_FROM_CACHE_EXIT_SAME_PAGE",
"RUN_FROM_CACHE_EXIT_NEAR_END_OF_PAGE",
"RUN_FROM_CACHE_EXIT_DIFFERENT_PAGE",
"CLEAR_TLB",
"FULL_CLEAR_TLB",
"TLB_FULL",
"TLB_GLOBAL_FULL",
"MODRM_SIMPLE_REG",
"MODRM_SIMPLE_REG_WITH_OFFSET",
"MODRM_SIMPLE_CONST_OFFSET",
"MODRM_COMPLEX",
"SEG_OFFSET_OPTIMISED",
"SEG_OFFSET_NOT_OPTIMISED",
"SEG_OFFSET_NOT_OPTIMISED_ES",
"SEG_OFFSET_NOT_OPTIMISED_FS",
"SEG_OFFSET_NOT_OPTIMISED_GS",
"SEG_OFFSET_NOT_OPTIMISED_NOT_FLAT",
];
let j = 0;
const stat_values = {};
for(let i = 0; i < stat_names.length; i++)
{
return print_stats.print_misc_stats(cpu) +
print_stats.print_instruction_counts(cpu);
},
const name = stat_names[i];
let value;
if(name.includes("/"))
{
j++; // skip profiler_stat_get
const [left, right] = name.split("/");
value = stat_values[left] / stat_values[right];
}
else
{
const stat = stat_values[name] = cpu.wm.exports["profiler_stat_get"](i - j);
value = stat >= 100e6 ? Math.round(stat / 1e6) + "m" : stat >= 100e3 ? Math.round(stat / 1e3) + "k" : stat;
}
text += name + "=" + value + "\n";
}
print_misc_stats: function(cpu)
text += "\n";
const tlb_entries = cpu.wm.exports["get_valid_tlb_entries_count"]();
const global_tlb_entries = cpu.wm.exports["get_valid_global_tlb_entries_count"]();
const nonglobal_tlb_entries = tlb_entries - global_tlb_entries;
text += "TLB_ENTRIES=" + tlb_entries + " (" + global_tlb_entries + " global, " + nonglobal_tlb_entries + " non-global)\n";
text += "WASM_TABLE_FREE=" + cpu.wm.exports["jit_get_wasm_table_index_free_list_count"]() + "\n";
text += "JIT_CACHE_SIZE=" + cpu.wm.exports["jit_get_cache_size"]() + "\n";
text += "FLAT_SEGMENTS=" + cpu.wm.exports["has_flat_segmentation"]() + "\n";
text += "wasm memory size: " + (cpu.wasm_memory.buffer.byteLength >> 20) + "m\n";
text += "Config:\n";
text += "JIT_DISABLED=" + cpu.wm.exports["get_jit_config"](0) + "\n";
text += "MAX_PAGES=" + cpu.wm.exports["get_jit_config"](1) + "\n";
text += "JIT_USE_LOOP_SAFETY=" + Boolean(cpu.wm.exports["get_jit_config"](2)) + "\n";
text += "MAX_EXTRA_BASIC_BLOCKS=" + cpu.wm.exports["get_jit_config"](3) + "\n";
return text;
}
function print_instruction_counts(cpu)
{
return [
print_instruction_counts_offset(cpu, false, false, false, false),
print_instruction_counts_offset(cpu, true, false, false, false),
print_instruction_counts_offset(cpu, false, true, false, false),
print_instruction_counts_offset(cpu, false, false, true, false),
print_instruction_counts_offset(cpu, false, false, false, true),
].join("\n\n");
}
function print_instruction_counts_offset(cpu, compiled, jit_exit, unguarded_register, wasm_size)
{
let text = "";
const counts = [];
const label =
compiled ? "compiled" :
jit_exit ? "jit exit" :
unguarded_register ? "unguarded register" :
wasm_size ? "wasm size" :
"executed";
for(let opcode = 0; opcode < 0x100; opcode++)
{
let text = "";
const stat_names = [
"COMPILE",
"COMPILE_SKIPPED_NO_NEW_ENTRY_POINTS",
"COMPILE_WRONG_ADDRESS_SPACE",
"COMPILE_CUT_OFF_AT_END_OF_PAGE",
"COMPILE_WITH_LOOP_SAFETY",
"COMPILE_PAGE",
"COMPILE_PAGE/COMPILE",
"COMPILE_BASIC_BLOCK",
"COMPILE_DUPLICATED_BASIC_BLOCK",
"COMPILE_WASM_BLOCK",
"COMPILE_WASM_LOOP",
"COMPILE_DISPATCHER",
"COMPILE_ENTRY_POINT",
"COMPILE_WASM_TOTAL_BYTES",
"COMPILE_WASM_TOTAL_BYTES/COMPILE_PAGE",
"RUN_INTERPRETED",
"RUN_INTERPRETED_NEW_PAGE",
"RUN_INTERPRETED_PAGE_HAS_CODE",
"RUN_INTERPRETED_PAGE_HAS_ENTRY_AFTER_PAGE_WALK",
"RUN_INTERPRETED_NEAR_END_OF_PAGE",
"RUN_INTERPRETED_DIFFERENT_STATE",
"RUN_INTERPRETED_DIFFERENT_STATE_CPL3",
"RUN_INTERPRETED_DIFFERENT_STATE_FLAT",
"RUN_INTERPRETED_DIFFERENT_STATE_IS32",
"RUN_INTERPRETED_DIFFERENT_STATE_SS32",
"RUN_INTERPRETED_MISSED_COMPILED_ENTRY_RUN_INTERPRETED",
"RUN_INTERPRETED_STEPS",
"RUN_FROM_CACHE",
"RUN_FROM_CACHE_STEPS",
"RUN_FROM_CACHE_STEPS/RUN_FROM_CACHE",
"RUN_FROM_CACHE_STEPS/RUN_INTERPRETED_STEPS",
"DIRECT_EXIT",
"INDIRECT_JUMP",
"INDIRECT_JUMP_NO_ENTRY",
"NORMAL_PAGE_CHANGE",
"NORMAL_FALLTHRU",
"NORMAL_FALLTHRU_WITH_TARGET_BLOCK",
"NORMAL_BRANCH",
"NORMAL_BRANCH_WITH_TARGET_BLOCK",
"CONDITIONAL_JUMP",
"CONDITIONAL_JUMP_PAGE_CHANGE",
"CONDITIONAL_JUMP_EXIT",
"CONDITIONAL_JUMP_FALLTHRU",
"CONDITIONAL_JUMP_FALLTHRU_WITH_TARGET_BLOCK",
"CONDITIONAL_JUMP_BRANCH",
"CONDITIONAL_JUMP_BRANCH_WITH_TARGET_BLOCK",
"DISPATCHER_SMALL",
"DISPATCHER_LARGE",
"LOOP",
"LOOP_SAFETY",
"CONDITION_OPTIMISED",
"CONDITION_UNOPTIMISED",
"CONDITION_UNOPTIMISED_PF",
"CONDITION_UNOPTIMISED_UNHANDLED_L",
"CONDITION_UNOPTIMISED_UNHANDLED_LE",
"FAILED_PAGE_CHANGE",
"SAFE_READ_FAST",
"SAFE_READ_SLOW_PAGE_CROSSED",
"SAFE_READ_SLOW_NOT_VALID",
"SAFE_READ_SLOW_NOT_USER",
"SAFE_READ_SLOW_IN_MAPPED_RANGE",
"SAFE_WRITE_FAST",
"SAFE_WRITE_SLOW_PAGE_CROSSED",
"SAFE_WRITE_SLOW_NOT_VALID",
"SAFE_WRITE_SLOW_NOT_USER",
"SAFE_WRITE_SLOW_IN_MAPPED_RANGE",
"SAFE_WRITE_SLOW_READ_ONLY",
"SAFE_WRITE_SLOW_HAS_CODE",
"SAFE_READ_WRITE_FAST",
"SAFE_READ_WRITE_SLOW_PAGE_CROSSED",
"SAFE_READ_WRITE_SLOW_NOT_VALID",
"SAFE_READ_WRITE_SLOW_NOT_USER",
"SAFE_READ_WRITE_SLOW_IN_MAPPED_RANGE",
"SAFE_READ_WRITE_SLOW_READ_ONLY",
"SAFE_READ_WRITE_SLOW_HAS_CODE",
"PAGE_FAULT",
"TLB_MISS",
"MAIN_LOOP",
"MAIN_LOOP_IDLE",
"DO_MANY_CYCLES",
"CYCLE_INTERNAL",
"INVALIDATE_ALL_MODULES_NO_FREE_WASM_INDICES",
"INVALIDATE_MODULE_WRITTEN_WHILE_COMPILED",
"INVALIDATE_MODULE_UNUSED_AFTER_OVERWRITE",
"INVALIDATE_MODULE_DIRTY_PAGE",
"INVALIDATE_PAGE_HAD_CODE",
"INVALIDATE_PAGE_HAD_ENTRY_POINTS",
"DIRTY_PAGE_DID_NOT_HAVE_CODE",
"RUN_FROM_CACHE_EXIT_SAME_PAGE",
"RUN_FROM_CACHE_EXIT_NEAR_END_OF_PAGE",
"RUN_FROM_CACHE_EXIT_DIFFERENT_PAGE",
"CLEAR_TLB",
"FULL_CLEAR_TLB",
"TLB_FULL",
"TLB_GLOBAL_FULL",
"MODRM_SIMPLE_REG",
"MODRM_SIMPLE_REG_WITH_OFFSET",
"MODRM_SIMPLE_CONST_OFFSET",
"MODRM_COMPLEX",
"SEG_OFFSET_OPTIMISED",
"SEG_OFFSET_NOT_OPTIMISED",
"SEG_OFFSET_NOT_OPTIMISED_ES",
"SEG_OFFSET_NOT_OPTIMISED_FS",
"SEG_OFFSET_NOT_OPTIMISED_GS",
"SEG_OFFSET_NOT_OPTIMISED_NOT_FLAT",
];
let j = 0;
const stat_values = {};
for(let i = 0; i < stat_names.length; i++)
for(let fixed_g = 0; fixed_g < 8; fixed_g++)
{
const name = stat_names[i];
let value;
if(name.includes("/"))
for(const is_mem of [false, true])
{
j++; // skip profiler_stat_get
const [left, right] = name.split("/");
value = stat_values[left] / stat_values[right];
const count = cpu.wm.exports["get_opstats_buffer"](compiled, jit_exit, unguarded_register, wasm_size, opcode, false, is_mem, fixed_g);
counts.push({ opcode, count, is_mem, fixed_g });
const count_0f = cpu.wm.exports["get_opstats_buffer"](compiled, jit_exit, unguarded_register, wasm_size, opcode, true, is_mem, fixed_g);
counts.push({ opcode: 0x0f00 | opcode, count: count_0f, is_mem, fixed_g });
}
else
{
const stat = stat_values[name] = cpu.wm.exports["profiler_stat_get"](i - j);
value = stat >= 100e6 ? Math.round(stat / 1e6) + "m" : stat >= 100e3 ? Math.round(stat / 1e3) + "k" : stat;
}
text += name + "=" + value + "\n";
}
}
text += "\n";
const tlb_entries = cpu.wm.exports["get_valid_tlb_entries_count"]();
const global_tlb_entries = cpu.wm.exports["get_valid_global_tlb_entries_count"]();
const nonglobal_tlb_entries = tlb_entries - global_tlb_entries;
text += "TLB_ENTRIES=" + tlb_entries + " (" + global_tlb_entries + " global, " + nonglobal_tlb_entries + " non-global)\n";
text += "WASM_TABLE_FREE=" + cpu.wm.exports["jit_get_wasm_table_index_free_list_count"]() + "\n";
text += "JIT_CACHE_SIZE=" + cpu.wm.exports["jit_get_cache_size"]() + "\n";
text += "FLAT_SEGMENTS=" + cpu.wm.exports["has_flat_segmentation"]() + "\n";
text += "wasm memory size: " + (cpu.wasm_memory.buffer.byteLength >> 20) + "m\n";
text += "Config:\n";
text += "JIT_DISABLED=" + cpu.wm.exports["get_jit_config"](0) + "\n";
text += "MAX_PAGES=" + cpu.wm.exports["get_jit_config"](1) + "\n";
text += "JIT_USE_LOOP_SAFETY=" + Boolean(cpu.wm.exports["get_jit_config"](2)) + "\n";
text += "MAX_EXTRA_BASIC_BLOCKS=" + cpu.wm.exports["get_jit_config"](3) + "\n";
return text;
},
print_instruction_counts: function(cpu)
let total = 0;
const prefixes = new Set([
0x26, 0x2E, 0x36, 0x3E,
0x64, 0x65, 0x66, 0x67,
0xF0, 0xF2, 0xF3,
]);
for(const { count, opcode } of counts)
{
return [
print_stats.print_instruction_counts_offset(cpu, false, false, false, false),
print_stats.print_instruction_counts_offset(cpu, true, false, false, false),
print_stats.print_instruction_counts_offset(cpu, false, true, false, false),
print_stats.print_instruction_counts_offset(cpu, false, false, true, false),
print_stats.print_instruction_counts_offset(cpu, false, false, false, true),
].join("\n\n");
},
if(!prefixes.has(opcode))
{
total += count;
}
}
print_instruction_counts_offset: function(cpu, compiled, jit_exit, unguarded_register, wasm_size)
if(total === 0)
{
let text = "";
return "";
}
const counts = [];
const per_opcode = new Uint32Array(0x100);
const per_opcode0f = new Uint32Array(0x100);
const label =
compiled ? "compiled" :
jit_exit ? "jit exit" :
unguarded_register ? "unguarded register" :
wasm_size ? "wasm size" :
"executed";
for(let opcode = 0; opcode < 0x100; opcode++)
for(const { opcode, count } of counts)
{
if((opcode & 0xFF00) === 0x0F00)
{
for(let fixed_g = 0; fixed_g < 8; fixed_g++)
{
for(const is_mem of [false, true])
{
const count = cpu.wm.exports["get_opstats_buffer"](compiled, jit_exit, unguarded_register, wasm_size, opcode, false, is_mem, fixed_g);
counts.push({ opcode, count, is_mem, fixed_g });
const count_0f = cpu.wm.exports["get_opstats_buffer"](compiled, jit_exit, unguarded_register, wasm_size, opcode, true, is_mem, fixed_g);
counts.push({ opcode: 0x0f00 | opcode, count: count_0f, is_mem, fixed_g });
}
}
per_opcode0f[opcode & 0xFF] += count;
}
let total = 0;
const prefixes = new Set([
0x26, 0x2E, 0x36, 0x3E,
0x64, 0x65, 0x66, 0x67,
0xF0, 0xF2, 0xF3,
]);
for(const { count, opcode } of counts)
else
{
if(!prefixes.has(opcode))
{
total += count;
}
per_opcode[opcode & 0xFF] += count;
}
}
if(total === 0)
{
return "";
}
text += "------------------\n";
text += "Total: " + total + "\n";
const per_opcode = new Uint32Array(0x100);
const per_opcode0f = new Uint32Array(0x100);
const factor = total > 1e7 ? 1000 : 1;
for(const { opcode, count } of counts)
{
if((opcode & 0xFF00) === 0x0F00)
{
per_opcode0f[opcode & 0xFF] += count;
}
else
{
per_opcode[opcode & 0xFF] += count;
}
}
const max_count = Math.max.apply(Math,
counts.map(({ count }) => Math.round(count / factor))
);
const pad_length = String(max_count).length;
text += "------------------\n";
text += "Total: " + total + "\n";
text += `Instruction counts ${label} (in ${factor}):\n`;
const factor = total > 1e7 ? 1000 : 1;
for(let i = 0; i < 0x100; i++)
{
text += i.toString(16).padStart(2, "0") + ":" + pads(Math.round(per_opcode[i] / factor), pad_length);
const max_count = Math.max.apply(Math,
counts.map(({ count }) => Math.round(count / factor))
);
const pad_length = String(max_count).length;
if(i % 16 === 15)
text += "\n";
else
text += " ";
}
text += `Instruction counts ${label} (in ${factor}):\n`;
text += "\n";
text += `Instruction counts ${label} (0f, in ${factor}):\n`;
for(let i = 0; i < 0x100; i++)
{
text += i.toString(16).padStart(2, "0") + ":" + v86util.pads(Math.round(per_opcode[i] / factor), pad_length);
for(let i = 0; i < 0x100; i++)
{
text += (i & 0xFF).toString(16).padStart(2, "0") + ":" + pads(Math.round(per_opcode0f[i] / factor), pad_length);
if(i % 16 === 15)
text += "\n";
else
text += " ";
}
if(i % 16 === 15)
text += "\n";
else
text += " ";
}
text += "\n";
text += "\n";
text += `Instruction counts ${label} (0f, in ${factor}):\n`;
const top_counts = counts.filter(({ count }) => count).sort(({ count: count1 }, { count: count2 }) => count2 - count1);
for(let i = 0; i < 0x100; i++)
{
text += (i & 0xFF).toString(16).padStart(2, "0") + ":" + v86util.pads(Math.round(per_opcode0f[i] / factor), pad_length);
for(const { opcode, is_mem, fixed_g, count } of top_counts.slice(0, 200))
{
const opcode_description = opcode.toString(16) + "_" + fixed_g + (is_mem ? "_m" : "_r");
text += opcode_description + ":" + (count / total * 100).toFixed(2) + " ";
}
text += "\n";
if(i % 16 === 15)
text += "\n";
else
text += " ";
}
text += "\n";
const top_counts = counts.filter(({ count }) => count).sort(({ count: count1 }, { count: count2 }) => count2 - count1);
for(const { opcode, is_mem, fixed_g, count } of top_counts.slice(0, 200))
{
const opcode_description = opcode.toString(16) + "_" + fixed_g + (is_mem ? "_m" : "_r");
text += opcode_description + ":" + (count / total * 100).toFixed(2) + " ";
}
text += "\n";
return text;
},
};
return text;
}

View file

@ -1,11 +1,16 @@
"use strict";
import { dbg_assert } from "../log.js";
import { get_charmap } from "../lib.js";
// Draws entire buffer and visualizes the layers that would be drawn
export const DEBUG_SCREEN_LAYERS = DEBUG && false;
/**
* Adapter to use visual screen in browsers (in contrast to node)
* @constructor
* @param {Object} options
* @param {function()} screen_fill_buffer
*/
function ScreenAdapter(options, screen_fill_buffer)
export function ScreenAdapter(options, screen_fill_buffer)
{
const screen_container = options.container;
this.screen_fill_buffer = screen_fill_buffer;
@ -92,9 +97,8 @@ function ScreenAdapter(options, screen_fill_buffer)
cursor_end,
cursor_enabled,
// 8-bit Unicode character maps
charmap_default = [],
charmap = charmap_default,
// 8-bit-text to Unicode character map
charmap = get_charmap(options.encoding),
// render loop state
timer_id = 0,
@ -314,52 +318,6 @@ function ScreenAdapter(options, screen_fill_buffer)
this.init = function()
{
// map 8-bit DOS codepage 437 character range 0-31 to 16-bit Unicode codepoints
const charmap_low = new Uint16Array([
0x20, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
0x25BA, 0x25C4, 0x2195, 0x203C, 0xB6, 0xA7, 0x25AC, 0x21A8,
0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC
]);
// map 8-bit DOS codepage 437 character range 127-255 to 16-bit Unicode codepoints
const charmap_high = new Uint16Array([
0x2302,
0xC7, 0xFC, 0xE9, 0xE2, 0xE4, 0xE0, 0xE5, 0xE7,
0xEA, 0xEB, 0xE8, 0xEF, 0xEE, 0xEC, 0xC4, 0xC5,
0xC9, 0xE6, 0xC6, 0xF4, 0xF6, 0xF2, 0xFB, 0xF9,
0xFF, 0xD6, 0xDC, 0xA2, 0xA3, 0xA5, 0x20A7, 0x192,
0xE1, 0xED, 0xF3, 0xFA, 0xF1, 0xD1, 0xAA, 0xBA,
0xBF, 0x2310, 0xAC, 0xBD, 0xBC, 0xA1, 0xAB, 0xBB,
0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
0x3B1, 0xDF, 0x393, 0x3C0, 0x3A3, 0x3C3, 0xB5, 0x3C4,
0x3A6, 0x398, 0x3A9, 0x3B4, 0x221E, 0x3C6, 0x3B5, 0x2229,
0x2261, 0xB1, 0x2265, 0x2264, 0x2320, 0x2321, 0xF7,
0x2248, 0xB0, 0x2219, 0xB7, 0x221A, 0x207F, 0xB2, 0x25A0, 0xA0
]);
// initialize 8-bit DOS codepage 437 map charmap[256] (Uint8 -> String[1])
for(var i = 0, chr; i < 256; i++)
{
if(i > 126)
{
chr = charmap_high[i - 0x7F];
}
else if(i < 32)
{
chr = charmap_low[i];
}
else
{
chr = i;
}
charmap_default.push(String.fromCharCode(chr));
}
// setup text mode cursor DOM element
cursor_element.classList.add("cursor");
cursor_element.style.position = "absolute";
@ -713,11 +671,6 @@ function ScreenAdapter(options, screen_fill_buffer)
update_scale_graphic();
};
this.set_charmap = function(text_charmap)
{
charmap = text_charmap || charmap_default;
};
this.set_scale = function(s_x, s_y)
{
scale_x = s_x;
@ -885,10 +838,10 @@ function ScreenAdapter(options, screen_fill_buffer)
text_mode_data[offset + BG_COLOR_INDEX] === bg_color &&
text_mode_data[offset + FG_COLOR_INDEX] === fg_color)
{
var ascii = text_mode_data[offset + CHARACTER_INDEX];
const chr = charmap[text_mode_data[offset + CHARACTER_INDEX]];
text += charmap[ascii];
dbg_assert(charmap[ascii]);
text += chr;
dbg_assert(chr);
i++;
offset += TEXT_BUF_COMPONENT_SIZE;
@ -968,16 +921,14 @@ function ScreenAdapter(options, screen_fill_buffer)
this.get_text_row = function(y)
{
let result = "";
for(let x = 0; x < text_mode_width; x++)
const begin = y * text_mode_width * TEXT_BUF_COMPONENT_SIZE + CHARACTER_INDEX;
const end = begin + text_mode_width * TEXT_BUF_COMPONENT_SIZE;
let row = "";
for(let i = begin; i < end; i += TEXT_BUF_COMPONENT_SIZE)
{
const index = (y * text_mode_width + x) * TEXT_BUF_COMPONENT_SIZE;
const character = text_mode_data[index + CHARACTER_INDEX];
result += charmap[character];
row += charmap[text_mode_data[i]];
}
return result;
return row;
};
this.init();

View file

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

View file

@ -1,16 +1,25 @@
"use strict";
import {
MIXER_CHANNEL_BOTH, MIXER_CHANNEL_LEFT, MIXER_CHANNEL_RIGHT,
MIXER_SRC_PCSPEAKER, MIXER_SRC_DAC, MIXER_SRC_MASTER,
} from "../const.js";
import { dbg_assert, dbg_log } from "../log.js";
import { OSCILLATOR_FREQ } from "../pit.js";
import { dump_file } from "../lib.js";
/** @const */
var DAC_QUEUE_RESERVE = 0.2;
// For Types Only
import { BusConnector } from "../bus.js";
/** @const */
var AUDIOBUFFER_MINIMUM_SAMPLING_RATE = 8000;
/* global registerProcessor, sampleRate */
const DAC_QUEUE_RESERVE = 0.2;
const AUDIOBUFFER_MINIMUM_SAMPLING_RATE = 8000;
/**
* @constructor
* @param {!BusConnector} bus
*/
function SpeakerAdapter(bus)
export function SpeakerAdapter(bus)
{
if(typeof window === "undefined")
{
@ -462,14 +471,9 @@ function SpeakerWorkletDAC(bus, audio_context, mixer)
function worklet()
{
/** @const */
var RENDER_QUANTUM = 128;
/** @const */
var MINIMUM_BUFFER_SIZE = 2 * RENDER_QUANTUM;
/** @const */
var QUEUE_RESERVE = 1024;
const RENDER_QUANTUM = 128;
const MINIMUM_BUFFER_SIZE = 2 * RENDER_QUANTUM;
const QUEUE_RESERVE = 1024;
function sinc(x)
{

View file

@ -1,100 +1,31 @@
"use strict";
import { v86 } from "../main.js";
import { LOG_CPU, WASM_TABLE_OFFSET, WASM_TABLE_SIZE } from "../const.js";
import { get_rand_int, load_file, read_sized_string_from_mem } from "../lib.js";
import { dbg_assert, dbg_trace, dbg_log, set_log_level } from "../log.js";
import * as print_stats from "./print_stats.js";
import { Bus } from "../bus.js";
import { BOOT_ORDER_FD_FIRST, BOOT_ORDER_HD_FIRST, BOOT_ORDER_CD_FIRST } from "../rtc.js";
import { EEXIST, ENOENT } from "../../lib/9p.js";
import { SpeakerAdapter } from "./speaker.js";
import { NetworkAdapter } from "./network.js";
import { FetchNetworkAdapter } from "./fetch_network.js";
import { WispNetworkAdapter } from "./wisp_network.js";
import { KeyboardAdapter } from "./keyboard.js";
import { MouseAdapter } from "./mouse.js";
import { ScreenAdapter } from "./screen.js";
import { DummyScreenAdapter } from "./dummy_screen.js";
import { SerialAdapter, SerialAdapterXtermJS } from "./serial.js";
import { InBrowserNetworkAdapter } from "./inbrowser_network.js";
import { MemoryFileStorage, ServerFileStorageWrapper } from "./filestorage.js";
import { SyncBuffer, buffer_from_object } from "../buffer.js";
import { FS } from "../../lib/filesystem.js";
/**
* Constructor for emulator instances.
*
* Usage: `new V86(options);`
*
* Options can have the following properties (all optional, default in parenthesis):
*
* - `memory_size number` (64 * 1024 * 1024) - The memory size in bytes, should
* be a power of 2.
* - `vga_memory_size number` (8 * 1024 * 1024) - VGA memory size in bytes.
*
* - `autostart boolean` (false) - If emulation should be started when emulator
* is ready.
*
* - `disable_keyboard boolean` (false) - If the keyboard should be disabled.
* - `disable_mouse boolean` (false) - If the mouse should be disabled.
*
* - `network_relay_url string` (No network card) - The url of a server running
* websockproxy. See [networking.md](networking.md). Setting this will
* enable an emulated ne2k network card. Only provided for backwards
* compatibility, use `net_device` instead.
*
* - `net_device Object` (null) - An object with the following properties:
* - `relay_url: string` - See above
* - `type: "ne2k" | "virtio"` - the type of the emulated cards
*
* - `net_devices Array<Object>` - Like `net_device`, but allows specifying
* more than one network card (up to 4). (currently not implemented)
*
* - `bios Object` (No bios) - Either a url pointing to a bios or an
* ArrayBuffer, see below.
* - `vga_bios Object` (No VGA bios) - VGA bios, see below.
* - `hda Object` (No hard disk) - First hard disk, see below.
* - `fda Object` (No floppy disk) - First floppy disk, see below.
* - `cdrom Object` (No CD) - See below.
*
* - `bzimage Object` - A Linux kernel image to boot (only bzimage format), see below.
* - `initrd Object` - A Linux ramdisk image, see below.
* - `bzimage_initrd_from_filesystem boolean` - Automatically fetch bzimage and
* initrd from the specified `filesystem`.
*
* - `initial_state Object` (Normal boot) - An initial state to load, see
* [`restore_state`](#restore_statearraybuffer-state) and below.
*
* - `filesystem Object` (No 9p filesystem) - A 9p filesystem, see
* [filesystem.md](filesystem.md).
*
* - `serial_container HTMLTextAreaElement` (No serial terminal) - A textarea
* that will receive and send data to the emulated serial terminal.
* Alternatively the serial terminal can also be accessed programatically,
* see [serial.html](../examples/serial.html).
*
* - `screen_container HTMLElement` (No screen) - An HTMLElement. This should
* have a certain structure, see [basic.html](../examples/basic.html). Only
* provided for backwards compatibility, use `screen` instead.
*
* - `screen Object` (No screen) - An object with the following properties:
* - `container HTMLElement` - An HTMLElement, see above.
* - `scale` (1) - Set initial scale_x and scale_y, if 0 disable automatic upscaling and dpi-adaption
*
* ***
*
* There are two ways to load images (`bios`, `vga_bios`, `cdrom`, `hda`, ...):
*
* - Pass an object that has a url. Optionally, `async: true` and `size:
* size_in_bytes` can be added to the object, so that sectors of the image
* are loaded on demand instead of being loaded before boot (slower, but
* strongly recommended for big files). In that case, the `Range: bytes=...`
* header must be supported on the server.
*
* ```javascript
* // download file before boot
* bios: {
* url: "bios/seabios.bin"
* }
* // download file sectors as requested, size is required
* hda: {
* url: "disk/linux.iso",
* async: true,
* size: 16 * 1024 * 1024
* }
* ```
*
* - Pass an `ArrayBuffer` or `File` object as `buffer` property.
*
* ```javascript
* // use <input type=file>
* bios: {
* buffer: document.all.hd_image.files[0]
* }
* // start with empty hard disk
* hda: {
* buffer: new ArrayBuffer(16 * 1024 * 1024)
* }
* ```
* For API usage, see v86.d.ts in the root of this repository.
*
* @param {{
disable_mouse: (boolean|undefined),
@ -105,14 +36,13 @@
} | undefined),
}} options
* @constructor
* @export
*/
function V86(options)
export function V86(options)
{
if(typeof options.log_level === "number")
{
// XXX: Shared between all emulator instances
LOG_LEVEL = options.log_level;
set_log_level(options.log_level);
}
//var worker = new Worker("src/browser/worker.js");
@ -122,7 +52,7 @@ function V86(options)
this.cpu_exception_hook = function(n) {};
const bus = Bus.create();
const adapter_bus = this.bus = bus[0];
this.bus = bus[0];
this.emulator_bus = bus[1];
var cpu;
@ -136,8 +66,7 @@ function V86(options)
"cpu_event_halt": () => { this.emulator_bus.send("cpu-event-halt"); },
"abort": function() { dbg_assert(false); },
"microtick": v86.microtick,
"get_rand_int": function() { return v86util.get_rand_int(); },
"apic_acknowledge_irq": function() { return cpu.devices.apic.acknowledge_irq(); },
"get_rand_int": function() { return get_rand_int(); },
"stop_idling": function() { return cpu.stop_idling(); },
"io_port_read8": function(addr) { return cpu.io.port_read8(addr); },
@ -159,11 +88,11 @@ function V86(options)
},
"log_from_wasm": function(offset, len) {
const str = v86util.read_sized_string_from_mem(wasm_memory, offset, len);
const str = read_sized_string_from_mem(wasm_memory, offset, len);
dbg_log(str, LOG_CPU);
},
"console_log_from_wasm": function(offset, len) {
const str = v86util.read_sized_string_from_mem(wasm_memory, offset, len);
const str = read_sized_string_from_mem(wasm_memory, offset, len);
console.error(str);
},
"dbg_trace_from_wasm": function() {
@ -185,6 +114,8 @@ function V86(options)
{
wasm_fn = env =>
{
/* global __dirname */
return new Promise(resolve => {
let v86_bin = DEBUG ? "v86-debug.wasm" : "v86.wasm";
let v86_bin_fallback = "v86-fallback.wasm";
@ -192,9 +123,7 @@ function V86(options)
if(options.wasm_path)
{
v86_bin = options.wasm_path;
const slash = v86_bin.lastIndexOf("/");
const dir = slash === -1 ? "" : v86_bin.substr(0, slash);
v86_bin_fallback = dir + "/" + v86_bin_fallback;
v86_bin_fallback = v86_bin.replace("v86.wasm", "v86-fallback.wasm");
}
else if(typeof window === "undefined" && typeof __dirname === "string")
{
@ -207,7 +136,7 @@ function V86(options)
v86_bin_fallback = "build/" + v86_bin_fallback;
}
v86util.load_file(v86_bin, {
load_file(v86_bin, {
done: async bytes =>
{
try
@ -218,7 +147,7 @@ function V86(options)
}
catch(err)
{
v86util.load_file(v86_bin_fallback, {
load_file(v86_bin_fallback, {
done: async bytes => {
const { instance } = await WebAssembly.instantiate(bytes, env);
this.wasm_source = bytes;
@ -306,8 +235,6 @@ V86.prototype.continue_init = async function(emulator, options)
settings.cpuid_level = options.cpuid_level;
settings.virtio_balloon = options.virtio_balloon;
settings.virtio_console = options.virtio_console;
settings.virtio_net = options.virtio_net;
settings.screen_options = options.screen_options;
const relay_url = options.network_relay_url || options.net_device && options.net_device.relay_url;
if(relay_url)
@ -357,21 +284,20 @@ V86.prototype.continue_init = async function(emulator, options)
}
else
{
this.screen_adapter = new DummyScreenAdapter();
this.screen_adapter = new DummyScreenAdapter(screen_options);
}
settings.screen = this.screen_adapter;
settings.screen_options = screen_options;
if(options.serial_container)
{
this.serial_adapter = new SerialAdapter(options.serial_container, this.bus);
//this.recording_adapter = new SerialRecordingAdapter(this.bus);
}
if(options.serial_container_xtermjs)
{
this.serial_adapter = new SerialAdapterXtermJS(options.serial_container_xtermjs, this.bus);
}
else if(options.serial_container)
{
this.serial_adapter = new SerialAdapter(options.serial_container, this.bus);
//this.recording_adapter = new SerialRecordingAdapter(this.bus);
}
if(!options.disable_speaker)
{
@ -471,7 +397,7 @@ V86.prototype.continue_init = async function(emulator, options)
{
files_to_load.push({
name,
loadable: v86util.buffer_from_object(file, this.zstd_decompress_worker.bind(this)),
loadable: buffer_from_object(file, this.zstd_decompress_worker.bind(this)),
});
}
};
@ -493,7 +419,15 @@ V86.prototype.continue_init = async function(emulator, options)
add_file("bzimage", options.bzimage);
add_file("initrd", options.initrd);
if(options.filesystem)
if(options.filesystem && options.filesystem.handle9p)
{
settings.handle9p = options.filesystem.handle9p;
}
else if(options.filesystem && options.filesystem.proxy_url)
{
settings.proxy9p = options.filesystem.proxy_url;
}
else if(options.filesystem)
{
var fs_url = options.filesystem.basefs;
var base_url = options.filesystem.baseurl;
@ -502,7 +436,7 @@ V86.prototype.continue_init = async function(emulator, options)
if(base_url)
{
file_storage = new ServerFileStorageWrapper(file_storage, base_url);
file_storage = new ServerFileStorageWrapper(file_storage, base_url, this.zstd_decompress.bind(this));
}
settings.fs9p = this.fs9p = new FS(file_storage);
@ -552,7 +486,7 @@ V86.prototype.continue_init = async function(emulator, options)
}
else
{
v86util.load_file(f.url, {
load_file(f.url, {
done: function(result)
{
if(f.url.endsWith(".zst") && f.name !== "initial_state")
@ -561,7 +495,7 @@ V86.prototype.continue_init = async function(emulator, options)
result = this.zstd_decompress(f.size, new Uint8Array(result));
}
put_on_settings.call(this, f.name, f.as_json ? result : new v86util.SyncBuffer(result));
put_on_settings.call(this, f.name, f.as_json ? result : new SyncBuffer(result));
cont(index + 1);
}.bind(this),
progress: function progress(e)
@ -618,8 +552,8 @@ V86.prototype.continue_init = async function(emulator, options)
settings.fs9p.read_file(initrd_path),
settings.fs9p.read_file(bzimage_path),
]);
put_on_settings.call(this, "initrd", new v86util.SyncBuffer(initrd.buffer));
put_on_settings.call(this, "bzimage", new v86util.SyncBuffer(bzimage.buffer));
put_on_settings.call(this, "initrd", new SyncBuffer(initrd.buffer));
put_on_settings.call(this, "bzimage", new SyncBuffer(bzimage.buffer));
}
}
else
@ -700,8 +634,7 @@ V86.prototype.zstd_decompress_worker = async function(decompressed_size, src)
{
const env = Object.fromEntries([
"cpu_exception_hook", "run_hardware_timers",
"cpu_event_halt", "microtick", "get_rand_int",
"apic_acknowledge_irq", "stop_idling",
"cpu_event_halt", "microtick", "get_rand_int", "stop_idling",
"io_port_read8", "io_port_read16", "io_port_read32",
"io_port_write8", "io_port_write16", "io_port_write32",
"mmap_read8", "mmap_read16", "mmap_read32",
@ -713,7 +646,7 @@ V86.prototype.zstd_decompress_worker = async function(decompressed_size, src)
env["__indirect_function_table"] = new WebAssembly.Table({ element: "anyfunc", initial: 1024 });
env["abort"] = () => { throw new Error("zstd worker aborted"); };
env["log_from_wasm"] = env["console_log_from_wasm"] = (off, len) => {
console.log(String.fromCharCode(...new Uint8Array(wasm.exports.memory.buffer, off, len)));
console.log(read_sized_string_from_mem(wasm.exports.memory.buffer, off, len));
};
env["dbg_trace_from_wasm"] = () => console.trace();
@ -795,9 +728,7 @@ V86.prototype.get_bzimage_initrd_from_filesystem = function(filesystem)
};
/**
* Start emulation. Do nothing if emulator is running already. Can be
* asynchronous.
* @export
* Start emulation. Do nothing if emulator is running already. Can be asynchronous.
*/
V86.prototype.run = async function()
{
@ -806,7 +737,6 @@ V86.prototype.run = async function()
/**
* Stop emulation. Do nothing if emulator is not running. Can be asynchronous.
* @export
*/
V86.prototype.stop = async function()
{
@ -826,8 +756,7 @@ V86.prototype.stop = async function()
};
/**
* @ignore
* @export
* Free resources associated with this instance
*/
V86.prototype.destroy = async function()
{
@ -844,7 +773,6 @@ V86.prototype.destroy = async function()
/**
* Restart (force a reboot).
* @export
*/
V86.prototype.restart = function()
{
@ -852,14 +780,12 @@ V86.prototype.restart = function()
};
/**
* Add an event listener (the emulator is an event emitter). A list of events
* can be found at [events.md](events.md).
* Add an event listener (the emulator is an event emitter).
*
* The callback function gets a single argument which depends on the event.
*
* @param {string} event Name of the event.
* @param {function(?)} listener The callback function.
* @export
*/
V86.prototype.add_listener = function(event, listener)
{
@ -871,7 +797,6 @@ V86.prototype.add_listener = function(event, listener)
*
* @param {string} event
* @param {function(*)} listener
* @export
*/
V86.prototype.remove_listener = function(event, listener)
{
@ -891,7 +816,6 @@ V86.prototype.remove_listener = function(event, listener)
* state buffer.
*
* @param {ArrayBuffer} state
* @export
*/
V86.prototype.restore_state = async function(state)
{
@ -903,7 +827,6 @@ V86.prototype.restore_state = async function(state)
* Asynchronously save the current state of the emulator.
*
* @return {Promise<ArrayBuffer>}
* @export
*/
V86.prototype.save_state = async function()
{
@ -914,7 +837,6 @@ V86.prototype.save_state = async function()
/**
* @return {number}
* @ignore
* @export
*/
V86.prototype.get_instruction_counter = function()
{
@ -931,7 +853,6 @@ V86.prototype.get_instruction_counter = function()
/**
* @return {boolean}
* @export
*/
V86.prototype.is_running = function()
{
@ -941,37 +862,128 @@ V86.prototype.is_running = function()
/**
* Set the image inserted in the floppy drive. Can be changed at runtime, as
* when physically changing the floppy disk.
* @export
*/
V86.prototype.set_fda = async function(file)
{
const fda = this.v86.cpu.devices.fdc.drives[0];
if(file.url && !file.async)
{
v86util.load_file(file.url, {
done: result =>
{
this.v86.cpu.devices.fdc.set_fda(new v86util.SyncBuffer(result));
},
await new Promise(resolve => {
load_file(file.url, {
done: result =>
{
fda.insert_disk(new SyncBuffer(result));
resolve();
}
});
});
}
else
{
const image = v86util.buffer_from_object(file, this.zstd_decompress_worker.bind(this));
const image = buffer_from_object(file, this.zstd_decompress_worker.bind(this));
image.onload = () =>
{
this.v86.cpu.devices.fdc.set_fda(image);
fda.insert_disk(image);
};
await image.load();
}
};
/**
* Eject the floppy drive.
* @export
* Set the image inserted in the second floppy drive, also at runtime.
*/
V86.prototype.set_fdb = async function(file)
{
const fdb = this.v86.cpu.devices.fdc.drives[1];
if(file.url && !file.async)
{
await new Promise(resolve => {
load_file(file.url, {
done: result =>
{
fdb.insert_disk(new SyncBuffer(result));
resolve();
}
});
});
}
else
{
const image = buffer_from_object(file, this.zstd_decompress_worker.bind(this));
image.onload = () =>
{
fdb.insert_disk(image);
};
await image.load();
}
};
/**
* Eject floppy drive fda.
*/
V86.prototype.eject_fda = function()
{
this.v86.cpu.devices.fdc.eject_fda();
this.v86.cpu.devices.fdc.drives[0].eject_disk();
};
/**
* Eject second floppy drive fdb.
*/
V86.prototype.eject_fdb = function()
{
this.v86.cpu.devices.fdc.drives[1].eject_disk();
};
/**
* Return buffer object of floppy disk of drive fda or null if the drive is empty.
* @return {Uint8Array|null}
*/
V86.prototype.get_disk_fda = function()
{
return this.v86.cpu.devices.fdc.drives[0].get_buffer();
};
/**
* Return buffer object of second floppy disk of drive fdb or null if the drive is empty.
* @return {Uint8Array|null}
*/
V86.prototype.get_disk_fdb = function()
{
return this.v86.cpu.devices.fdc.drives[1].get_buffer();
};
/**
* Set the image inserted in the CD-ROM drive. Can be changed at runtime, as
* when physically changing the CD-ROM.
*/
V86.prototype.set_cdrom = async function(file)
{
if(file.url && !file.async)
{
load_file(file.url, {
done: result =>
{
this.v86.cpu.devices.cdrom.set_cdrom(new SyncBuffer(result));
},
});
}
else
{
const image = buffer_from_object(file, this.zstd_decompress_worker.bind(this));
image.onload = () =>
{
this.v86.cpu.devices.cdrom.set_cdrom(image);
};
await image.load();
}
};
/**
* Eject the CD-ROM.
*/
V86.prototype.eject_cdrom = function()
{
this.v86.cpu.devices.cdrom.eject();
};
/**
@ -980,47 +992,47 @@ V86.prototype.eject_fda = function()
* Do nothing if there is no keyboard controller.
*
* @param {Array.<number>} codes
* @export
* @param {number=} delay
*/
V86.prototype.keyboard_send_scancodes = function(codes)
V86.prototype.keyboard_send_scancodes = async function(codes, delay)
{
for(var i = 0; i < codes.length; i++)
{
this.bus.send("keyboard-code", codes[i]);
if(delay) await new Promise(resolve => setTimeout(resolve, delay));
}
};
/**
* Send translated keys
* @ignore
* @export
* @param {Array.<number>} codes
* @param {number=} delay
*/
V86.prototype.keyboard_send_keys = function(codes)
V86.prototype.keyboard_send_keys = async function(codes, delay)
{
for(var i = 0; i < codes.length; i++)
{
this.keyboard_adapter.simulate_press(codes[i]);
if(delay) await new Promise(resolve => setTimeout(resolve, delay));
}
};
/**
* Send text
* @ignore
* @export
* Send text, assuming the guest OS uses a US keyboard layout
* @param {string} string
* @param {number=} delay
*/
V86.prototype.keyboard_send_text = function(string)
V86.prototype.keyboard_send_text = async function(string, delay)
{
for(var i = 0; i < string.length; i++)
{
this.keyboard_adapter.simulate_char(string[i]);
if(delay) await new Promise(resolve => setTimeout(resolve, delay));
}
};
/**
* Download a screenshot.
*
* @ignore
* @export
* Download a screenshot (returns an <img> element, only works in browsers)
*/
V86.prototype.screen_make_screenshot = function()
{
@ -1036,9 +1048,6 @@ V86.prototype.screen_make_screenshot = function()
*
* @param {number} sx
* @param {number} sy
*
* @ignore
* @export
*/
V86.prototype.screen_set_scale = function(sx, sy)
{
@ -1049,10 +1058,7 @@ V86.prototype.screen_set_scale = function(sx, sy)
};
/**
* Go fullscreen.
*
* @ignore
* @export
* Go fullscreen (only browsers)
*/
V86.prototype.screen_go_fullscreen = function()
{
@ -1094,21 +1100,21 @@ V86.prototype.screen_go_fullscreen = function()
/**
* Lock the mouse cursor: It becomes invisble and is not moved out of the
* browser window.
*
* @ignore
* @export
*/
V86.prototype.lock_mouse = function()
V86.prototype.lock_mouse = async function()
{
var elem = document.body;
const elem = document.body;
var fn = elem["requestPointerLock"] ||
elem["mozRequestPointerLock"] ||
elem["webkitRequestPointerLock"];
if(fn)
try
{
fn.call(elem);
await elem.requestPointerLock({
unadjustedMovement: true,
});
}
catch(e)
{
// as per MDN, retry without unadjustedMovement option
await elem.requestPointerLock();
}
};
@ -1117,34 +1123,33 @@ V86.prototype.lock_mouse = function()
*
* @param {boolean} enabled
*/
V86.prototype.mouse_set_status = function(enabled)
V86.prototype.mouse_set_enabled = function(enabled)
{
if(this.mouse_adapter)
{
this.mouse_adapter.emu_enabled = enabled;
}
};
V86.prototype.mouse_set_status = V86.prototype.mouse_set_enabled;
/**
* Enable or disable sending keyboard events to the emulated PS2 controller.
*
* @param {boolean} enabled
* @export
*/
V86.prototype.keyboard_set_status = function(enabled)
V86.prototype.keyboard_set_enabled = function(enabled)
{
if(this.keyboard_adapter)
{
this.keyboard_adapter.emu_enabled = enabled;
}
};
V86.prototype.keyboard_set_status = V86.prototype.keyboard_set_enabled;
/**
* Send a string to the first emulated serial terminal.
*
* @param {string} data
* @export
*/
V86.prototype.serial0_send = function(data)
{
@ -1158,7 +1163,6 @@ V86.prototype.serial0_send = function(data)
* Send bytes to a serial port (to be received by the emulated PC).
*
* @param {Uint8Array} data
* @export
*/
V86.prototype.serial_send_bytes = function(serial, data)
{
@ -1208,52 +1212,12 @@ V86.prototype.serial_set_clear_to_send = function(serial, status)
this.bus.send("serial" + serial + "-clear-to-send-input", status);
};
/**
* Mount another filesystem to the current filesystem.
* @param {string} path Path for the mount point
* @param {string|undefined} baseurl
* @param {string|undefined} basefs As a JSON string
* @export
*/
V86.prototype.mount_fs = async function(path, baseurl, basefs)
{
let file_storage = new MemoryFileStorage();
if(baseurl)
{
file_storage = new ServerFileStorageWrapper(file_storage, baseurl);
}
const newfs = new FS(file_storage, this.fs9p.qidcounter);
if(baseurl)
{
dbg_assert(typeof basefs === "object", "Filesystem: basefs must be a JSON object");
newfs.load_from_json(basefs);
}
const idx = this.fs9p.Mount(path, newfs);
if(idx === -ENOENT)
{
throw new FileNotFoundError();
}
else if(idx === -EEXIST)
{
throw new FileExistsError();
}
else if(idx < 0)
{
dbg_assert(false, "Unexpected error code: " + (-idx));
throw new Error("Failed to mount. Error number: " + (-idx));
}
};
/**
* Write to a file in the 9p filesystem. Nothing happens if no filesystem has
* been initialized.
*
* @param {string} file
* @param {Uint8Array} data
* @export
*/
V86.prototype.create_file = async function(file, data)
{
@ -1287,7 +1251,6 @@ V86.prototype.create_file = async function(file, data)
* initialized.
*
* @param {string} file
* @export
*/
V86.prototype.read_file = async function(file)
{
@ -1369,52 +1332,110 @@ V86.prototype.automatically = function(steps)
run(steps);
};
V86.prototype.wait_until_vga_screen_contains = function(text)
/**
* Wait until expected text is present on the VGA text screen.
*
* Returns immediately if the expected text is already present on screen
* at the time this funtion is called.
*
* An optional timeout may be specified in `options.timeout_msec`, returns
* false if the timeout expires before the expected text could be detected.
*
* Expected text (or texts, see below) must be of type string or RegExp,
* strings are tested against the beginning of a screen line, regular
* expressions against the full line but may use wildcards for partial
* matching.
*
* Two methods of text detection are supported depending on the type of the
* argument `expected`:
*
* 1. If `expected` is a string or RegExp then the given text string or
* regular expression may match any line on screen for this function
* to succeed.
*
* 2. If `expected` is an array of strings and/or RegExp objects then the
* list of expected lines must match exactly at "the bottom" of the
* screen. The "bottom" line is the first non-empty line starting from
* the screen's end.
* Expected lines should not contain any trailing whitespace and/or
* newline characters. Expecting an empty line is valid.
*
* Returns `true` on success and `false` when the timeout has expired.
*
* @param {string|RegExp|Array<string|RegExp>} expected
* @param {{timeout_msec:(number|undefined)}=} options
*/
V86.prototype.wait_until_vga_screen_contains = async function(expected, options)
{
return new Promise(resolve =>
const match_multi = Array.isArray(expected);
const timeout_msec = options?.timeout_msec || 0;
const changed_rows = new Set();
const screen_put_char = args => changed_rows.add(args[0]);
const contains_expected = (screen_line, pattern) => pattern.test ? pattern.test(screen_line) : screen_line.startsWith(pattern);
const screen_lines = [];
this.add_listener("screen-put-char", screen_put_char);
for(const screen_line of this.screen_adapter.get_text_screen())
{
function test_line(line)
if(match_multi)
{
return typeof text === "string" ? line.includes(text) : text.test(line);
screen_lines.push(screen_line.trimRight());
}
for(const line of this.screen_adapter.get_text_screen())
else if(contains_expected(screen_line, expected))
{
if(test_line(line))
this.remove_listener("screen-put-char", screen_put_char);
return true;
}
}
let succeeded = false;
const end = timeout_msec ? performance.now() + timeout_msec : 0;
loop: while(!end || performance.now() < end)
{
if(match_multi)
{
let screen_height = screen_lines.length;
while(screen_height > 0 && screen_lines[screen_height - 1] === "")
{
resolve(true);
return;
screen_height--;
}
}
const changed_rows = new Set();
function put_char(args)
{
const [row, col, char] = args;
changed_rows.add(row);
}
const check = () =>
{
for(const row of changed_rows)
const screen_offset = screen_height - expected.length;
if(screen_offset >= 0)
{
const line = this.screen_adapter.get_text_row(row);
if(test_line(line))
let matches = true;
for(let i = 0; i < expected.length && matches; i++)
{
this.remove_listener("screen-put-char", put_char);
resolve();
return;
matches = contains_expected(screen_lines[screen_offset + i], expected[i]);
}
if(matches)
{
succeeded = true;
break;
}
}
}
changed_rows.clear();
setTimeout(check, 100);
};
check();
await new Promise(resolve => setTimeout(resolve, 100));
this.add_listener("screen-put-char", put_char);
});
for(const row of changed_rows)
{
const screen_line = this.screen_adapter.get_text_row(row);
if(match_multi)
{
screen_lines[row] = screen_line.trimRight();
}
else if(contains_expected(screen_line, expected))
{
succeeded = true;
break loop;
}
}
changed_rows.clear();
}
this.remove_listener("screen-put-char", screen_put_char);
return succeeded;
};
/**
@ -1447,6 +1468,11 @@ V86.prototype.set_serial_container_xtermjs = function(element)
this.serial_adapter.show();
};
V86.prototype.get_instruction_stats = function()
{
return print_stats.stats_to_string(this.v86.cpu);
};
/**
* @ignore
* @constructor
@ -1470,3 +1496,19 @@ function FileNotFoundError(message)
this.message = message || "File not found";
}
FileNotFoundError.prototype = Error.prototype;
/* global module, self */
if(typeof module !== "undefined" && typeof module.exports !== "undefined")
{
module.exports["V86"] = V86;
}
else if(typeof window !== "undefined")
{
window["V86"] = V86;
}
else if(typeof importScripts === "function")
{
// web worker
self["V86"] = V86;
}

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

5
src/cjs.js Normal file
View file

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

View file

@ -1,53 +0,0 @@
"use strict";
/*
* Compile time configuration, some only relevant for debug mode
*/
/**
* @define {boolean}
* Overridden for production by closure compiler
*/
var DEBUG = true;
/** @const */
var LOG_TO_FILE = false;
/**
* @const
* Enables logging all IO port reads and writes. Very verbose
*/
var LOG_ALL_IO = false;
/**
* @const
*/
var DUMP_GENERATED_WASM = false;
/**
* @const
*/
var DUMP_UNCOMPILED_ASSEMBLY = false;
/**
* @const
* More accurate filenames in 9p debug messages at the cost of performance.
*/
var TRACK_FILENAMES = false;
var LOG_LEVEL = LOG_ALL & ~LOG_PS2 & ~LOG_PIT & ~LOG_VIRTIO & ~LOG_9P & ~LOG_PIC &
~LOG_DMA & ~LOG_SERIAL & ~LOG_NET & ~LOG_FLOPPY & ~LOG_DISK & ~LOG_VGA & ~LOG_SB16;
/**
* @const
* Draws entire buffer and visualizes the layers that would be drawn
*/
var DEBUG_SCREEN_LAYERS = DEBUG && false;
/**
* @const
* How many ticks the TSC does per millisecond
*/
var TSC_RATE = 1 * 1000 * 1000;
/** @const */
var APIC_TIMER_FREQ = TSC_RATE;

View file

@ -1,41 +1,38 @@
"use strict";
export const
LOG_ALL = -1,
LOG_NONE = 0,
var
/** @const */ LOG_ALL = -1,
/** @const */ LOG_NONE = 0,
/** @const */ LOG_OTHER = 0x0000001,
/** @const */ LOG_CPU = 0x0000002,
/** @const */ LOG_FPU = 0x0000004,
/** @const */ LOG_MEM = 0x0000008,
/** @const */ LOG_DMA = 0x0000010,
/** @const */ LOG_IO = 0x0000020,
/** @const */ LOG_PS2 = 0x0000040,
/** @const */ LOG_PIC = 0x0000080,
/** @const */ LOG_VGA = 0x0000100,
/** @const */ LOG_PIT = 0x0000200,
/** @const */ LOG_MOUSE = 0x0000400,
/** @const */ LOG_PCI = 0x0000800,
/** @const */ LOG_BIOS = 0x0001000,
/** @const */ LOG_FLOPPY = 0x0002000,
/** @const */ LOG_SERIAL = 0x0004000,
/** @const */ LOG_DISK = 0x0008000,
/** @const */ LOG_RTC = 0x0010000,
// unused 0x0020000,
/** @const */ LOG_ACPI = 0x0040000,
/** @const */ LOG_APIC = 0x0080000,
/** @const */ LOG_NET = 0x0100000,
/** @const */ LOG_VIRTIO = 0x0200000,
/** @const */ LOG_9P = 0x0400000,
/** @const */ LOG_SB16 = 0x0800000,
/** @const */ LOG_FETCH = 0x1000000;
LOG_OTHER = 0x0000001,
LOG_CPU = 0x0000002,
LOG_FPU = 0x0000004,
LOG_MEM = 0x0000008,
LOG_DMA = 0x0000010,
LOG_IO = 0x0000020,
LOG_PS2 = 0x0000040,
LOG_PIC = 0x0000080,
LOG_VGA = 0x0000100,
LOG_PIT = 0x0000200,
LOG_MOUSE = 0x0000400,
LOG_PCI = 0x0000800,
LOG_BIOS = 0x0001000,
LOG_FLOPPY = 0x0002000,
LOG_SERIAL = 0x0004000,
LOG_DISK = 0x0008000,
LOG_RTC = 0x0010000,
// unused 0x0020000,
LOG_ACPI = 0x0040000,
LOG_APIC = 0x0080000,
LOG_NET = 0x0100000,
LOG_VIRTIO = 0x0200000,
LOG_9P = 0x0400000,
LOG_SB16 = 0x0800000,
LOG_FETCH = 0x1000000;
/**
* @const
* @type {Array<Array<string|number>>}
*/
var LOG_NAMES = [
export const LOG_NAMES = [
[1, ""],
[LOG_CPU, "CPU"],
[LOG_DISK, "DISK"],
@ -62,105 +59,81 @@ var LOG_NAMES = [
[LOG_FETCH, "FETC"],
];
var
export const
// flags register bitflags
FLAG_CARRY = 1,
FLAG_PARITY = 4,
FLAG_ADJUST = 16,
FLAG_ZERO = 64,
FLAG_SIGN = 128,
FLAG_TRAP = 256,
FLAG_INTERRUPT = 512,
FLAG_DIRECTION = 1024,
FLAG_OVERFLOW = 2048,
FLAG_IOPL = 1 << 12 | 1 << 13,
FLAG_NT = 1 << 14,
FLAG_RF = 1 << 16,
FLAG_VM = 1 << 17,
FLAG_AC = 1 << 18,
FLAG_VIF = 1 << 19,
FLAG_VIP = 1 << 20,
FLAG_ID = 1 << 21,
// flags register bitflags
/** @const */ FLAG_CARRY = 1,
/** @const */ FLAG_PARITY = 4,
/** @const */ FLAG_ADJUST = 16,
/** @const */ FLAG_ZERO = 64,
/** @const */ FLAG_SIGN = 128,
/** @const */ FLAG_TRAP = 256,
/** @const */ FLAG_INTERRUPT = 512,
/** @const */ FLAG_DIRECTION = 1024,
/** @const */ FLAG_OVERFLOW = 2048,
/** @const */ FLAG_IOPL = 1 << 12 | 1 << 13,
/** @const */ FLAG_NT = 1 << 14,
/** @const */ FLAG_RF = 1 << 16,
/** @const */ FLAG_VM = 1 << 17,
/** @const */ FLAG_AC = 1 << 18,
/** @const */ FLAG_VIF = 1 << 19,
/** @const */ FLAG_VIP = 1 << 20,
/** @const */ FLAG_ID = 1 << 21,
// default values of reserved flags bits
FLAGS_DEFAULT = 1 << 1,
/**
* default values of reserved flags bits
* @const
*/
FLAGS_DEFAULT = 1 << 1,
REG_EAX = 0,
REG_ECX = 1,
REG_EDX = 2,
REG_EBX = 3,
REG_ESP = 4,
REG_EBP = 5,
REG_ESI = 6,
REG_EDI = 7,
REG_ES = 0,
REG_CS = 1,
REG_SS = 2,
REG_DS = 3,
REG_FS = 4,
REG_GS = 5,
/** @const */ REG_EAX = 0,
/** @const */ REG_ECX = 1,
/** @const */ REG_EDX = 2,
/** @const */ REG_EBX = 3,
/** @const */ REG_ESP = 4,
/** @const */ REG_EBP = 5,
/** @const */ REG_ESI = 6,
/** @const */ REG_EDI = 7,
REG_LDTR = 7; // local descriptor table register
/** @const */ REG_ES = 0,
/** @const */ REG_CS = 1,
/** @const */ REG_SS = 2,
/** @const */ REG_DS = 3,
/** @const */ REG_FS = 4,
/** @const */ REG_GS = 5,
/** @const */ REG_LDTR = 7; // local descriptor table register
var
/**
* The minimum number of bytes that can be memory-mapped
* by one device.
*
* @const
*/
export const
// The minimum number of bytes that can be memory-mapped by one device.
MMAP_BLOCK_BITS = 17,
/** @const */
MMAP_BLOCK_SIZE = 1 << MMAP_BLOCK_BITS,
/** @const */
MMAP_MAX = 0x100000000;
/** @const */
var CR0_PG = 1 << 31;
/** @const */
var CR4_PAE = 1 << 5;
export const CR0_PG = 1 << 31;
export const CR4_PAE = 1 << 5;
// https://github.com/qemu/seabios/blob/14221cd86eadba82255fdc55ed174d401c7a0a04/src/fw/paravirt.c#L205-L219
/** @const */ var FW_CFG_SIGNATURE = 0x00;
/** @const */ var FW_CFG_ID = 0x01;
/** @const */ var FW_CFG_RAM_SIZE = 0x03;
/** @const */ var FW_CFG_NB_CPUS = 0x05;
/** @const */ var FW_CFG_MAX_CPUS = 0x0F;
/** @const */ var FW_CFG_NUMA = 0x0D;
/** @const */ var FW_CFG_FILE_DIR = 0x19;
export const FW_CFG_SIGNATURE = 0x00;
export const FW_CFG_ID = 0x01;
export const FW_CFG_RAM_SIZE = 0x03;
export const FW_CFG_NB_CPUS = 0x05;
export const FW_CFG_MAX_CPUS = 0x0F;
export const FW_CFG_NUMA = 0x0D;
export const FW_CFG_FILE_DIR = 0x19;
/** @const */ var FW_CFG_CUSTOM_START = 0x8000;
export const FW_CFG_CUSTOM_START = 0x8000;
// This value is specific to v86, choosen to hopefully not collide with other indexes
/** @const */ var FW_CFG_FILE_START = 0xC000;
/** @const */ var FW_CFG_SIGNATURE_QEMU = 0x554D4551;
export const FW_CFG_FILE_START = 0xC000;
export const FW_CFG_SIGNATURE_QEMU = 0x554D4551;
// See same constant in jit.rs
/** @const */
var WASM_TABLE_SIZE = 900;
export const WASM_TABLE_SIZE = 900;
/** @const */
var WASM_TABLE_OFFSET = 1024;
export const WASM_TABLE_OFFSET = 1024;
/** @const */
var MIXER_CHANNEL_LEFT = 0;
/** @const */
var MIXER_CHANNEL_RIGHT = 1;
/** @const */
var MIXER_CHANNEL_BOTH = 2;
/** @const */
var MIXER_SRC_MASTER = 0;
/** @const */
var MIXER_SRC_PCSPEAKER = 1;
/** @const */
var MIXER_SRC_DAC = 2;
export const MIXER_CHANNEL_LEFT = 0;
export const MIXER_CHANNEL_RIGHT = 1;
export const MIXER_CHANNEL_BOTH = 2;
export const MIXER_SRC_MASTER = 0;
export const MIXER_SRC_PCSPEAKER = 1;
export const MIXER_SRC_DAC = 2;

1129
src/cpu.js

File diff suppressed because it is too large Load diff

View file

@ -1,662 +0,0 @@
"use strict";
CPU.prototype.debug_init = function()
{
var cpu = this;
var debug = {};
this.debug = debug;
debug.init = function()
{
if(!DEBUG) return;
if(cpu.io)
{
// write seabios debug output to console
var seabios_debug = "";
cpu.io.register_write(0x402, this, handle); // seabios
cpu.io.register_write(0x500, this, handle); // vgabios
}
function handle(out_byte)
{
if(out_byte === 10)
{
dbg_log(seabios_debug, LOG_BIOS);
seabios_debug = "";
}
else
{
seabios_debug += String.fromCharCode(out_byte);
}
}
};
debug.get_regs_short = get_regs_short;
debug.dump_regs = dump_regs_short;
debug.get_state = get_state;
debug.dump_state = dump_state;
debug.dump_stack = dump_stack;
debug.dump_page_structures = dump_page_structures;
debug.dump_gdt_ldt = dump_gdt_ldt;
debug.dump_idt = dump_idt;
debug.get_memory_dump = get_memory_dump;
debug.memory_hex_dump = memory_hex_dump;
debug.used_memory_dump = used_memory_dump;
function dump_stack(start, end)
{
if(!DEBUG) return;
var esp = cpu.reg32[REG_ESP];
dbg_log("========= STACK ==========");
if(end >= start || end === undefined)
{
start = 5;
end = -5;
}
for(var i = start; i > end; i--)
{
var line = " ";
if(!i) line = "=> ";
line += h(i, 2) + " | ";
dbg_log(line + h(esp + 4 * i, 8) + " | " + h(cpu.read32s(esp + 4 * i) >>> 0));
}
}
function get_state(where)
{
if(!DEBUG) return;
var mode = cpu.protected_mode[0] ? "prot" : "real";
var vm = (cpu.flags[0] & FLAG_VM) ? 1 : 0;
var flags = cpu.get_eflags();
var iopl = cpu.getiopl();
var cpl = cpu.cpl[0];
var cs_eip = h(cpu.sreg[REG_CS], 4) + ":" + h(cpu.get_real_eip() >>> 0, 8);
var ss_esp = h(cpu.sreg[REG_SS], 4) + ":" + h(cpu.reg32[REG_ES] >>> 0, 8);
var op_size = cpu.is_32[0] ? "32" : "16";
var if_ = (cpu.flags[0] & FLAG_INTERRUPT) ? 1 : 0;
var flag_names = {
[FLAG_CARRY]: "c",
[FLAG_PARITY]: "p",
[FLAG_ADJUST]: "a",
[FLAG_ZERO]: "z",
[FLAG_SIGN]: "s",
[FLAG_TRAP]: "t",
[FLAG_INTERRUPT]: "i",
[FLAG_DIRECTION]: "d",
[FLAG_OVERFLOW]: "o",
};
var flag_string = "";
for(var i = 0; i < 16; i++)
{
if(flag_names[1 << i])
{
if(flags & 1 << i)
{
flag_string += flag_names[1 << i];
}
else
{
flag_string += " ";
}
}
}
return ("mode=" + mode + "/" + op_size + " paging=" + (+((cpu.cr[0] & CR0_PG) !== 0)) +
" pae=" + (+((cpu.cr[4] & CR4_PAE) !== 0)) +
" iopl=" + iopl + " cpl=" + cpl + " if=" + if_ + " cs:eip=" + cs_eip +
" cs_off=" + h(cpu.get_seg_cs() >>> 0, 8) +
" flgs=" + h(cpu.get_eflags() >>> 0, 6) + " (" + flag_string + ")" +
" ss:esp=" + ss_esp +
" ssize=" + (+cpu.stack_size_32[0]) +
(where ? " in " + where : ""));
}
function dump_state(where)
{
if(!DEBUG) return;
dbg_log(get_state(where), LOG_CPU);
}
function get_regs_short()
{
if(!DEBUG) return;
var
r32 = { "eax": REG_EAX, "ecx": REG_ECX, "edx": REG_EDX, "ebx": REG_EBX,
"esp": REG_ESP, "ebp": REG_EBP, "esi": REG_ESI, "edi": REG_EDI },
r32_names = ["eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi"],
s = { "cs": REG_CS, "ds": REG_DS, "es": REG_ES, "fs": REG_FS, "gs": REG_GS, "ss": REG_SS },
line1 = "",
line2 = "";
for(var i = 0; i < 4; i++)
{
line1 += r32_names[i] + "=" + h(cpu.reg32[r32[r32_names[i]]] >>> 0, 8) + " ";
line2 += r32_names[i+4] + "=" + h(cpu.reg32[r32[r32_names[i+4]]] >>> 0, 8) + " ";
}
//line1 += " eip=" + h(cpu.get_real_eip() >>> 0, 8);
//line2 += " flg=" + h(cpu.get_eflags(), 8);
line1 += " ds=" + h(cpu.sreg[REG_DS], 4) + " es=" + h(cpu.sreg[REG_ES], 4) + " fs=" + h(cpu.sreg[REG_FS], 4);
line2 += " gs=" + h(cpu.sreg[REG_GS], 4) + " cs=" + h(cpu.sreg[REG_CS], 4) + " ss=" + h(cpu.sreg[REG_SS], 4);
return [line1, line2];
}
function dump_regs_short()
{
if(!DEBUG) return;
var lines = get_regs_short();
dbg_log(lines[0], LOG_CPU);
dbg_log(lines[1], LOG_CPU);
}
function dump_gdt_ldt()
{
if(!DEBUG) return;
dbg_log("gdt: (len = " + h(cpu.gdtr_size[0]) + ")");
dump_table(cpu.translate_address_system_read(cpu.gdtr_offset[0]), cpu.gdtr_size[0]);
dbg_log("\nldt: (len = " + h(cpu.segment_limits[REG_LDTR]) + ")");
dump_table(cpu.translate_address_system_read(cpu.segment_offsets[REG_LDTR]), cpu.segment_limits[REG_LDTR]);
function dump_table(addr, size)
{
for(var i = 0; i < size; i += 8, addr += 8)
{
var base = cpu.read16(addr + 2) |
cpu.read8(addr + 4) << 16 |
cpu.read8(addr + 7) << 24,
limit = cpu.read16(addr) | (cpu.read8(addr + 6) & 0xF) << 16,
access = cpu.read8(addr + 5),
flags = cpu.read8(addr + 6) >> 4,
flags_str = "",
dpl = access >> 5 & 3;
if(!(access & 128))
{
// present bit not set
//continue;
flags_str += "NP ";
}
else
{
flags_str += " P ";
}
if(access & 16)
{
if(flags & 4)
{
flags_str += "32b ";
}
else
{
flags_str += "16b ";
}
if(access & 8)
{
// executable
flags_str += "X ";
if(access & 4)
{
flags_str += "C ";
}
}
else
{
// data
flags_str += "R ";
}
flags_str += "RW ";
}
else
{
// system
flags_str += "sys: " + h(access & 15);
}
if(flags & 8)
{
limit = limit << 12 | 0xFFF;
}
dbg_log(h(i & ~7, 4) + " " + h(base >>> 0, 8) + " (" + h(limit >>> 0, 8) + " bytes) " +
flags_str + "; dpl = " + dpl + ", a = " + access.toString(2) +
", f = " + flags.toString(2));
}
}
}
function dump_idt()
{
if(!DEBUG) return;
for(var i = 0; i < cpu.idtr_size[0]; i += 8)
{
var addr = cpu.translate_address_system_read(cpu.idtr_offset[0] + i),
base = cpu.read16(addr) | cpu.read16(addr + 6) << 16,
selector = cpu.read16(addr + 2),
type = cpu.read8(addr + 5),
line,
dpl = type >> 5 & 3;
if((type & 31) === 5)
{
line = "task gate ";
}
else if((type & 31) === 14)
{
line = "intr gate ";
}
else if((type & 31) === 15)
{
line = "trap gate ";
}
else
{
line = "invalid ";
}
if(type & 128)
{
line += " P";
}
else
{
// present bit not set
//continue;
line += "NP";
}
dbg_log(h(i >> 3, 4) + " " + h(base >>> 0, 8) + ", " +
h(selector, 4) + "; " + line + "; dpl = " + dpl + ", t = " + type.toString(2));
}
}
function load_page_entry(dword_entry, pae, is_directory)
{
if(!DEBUG) return;
if(!(dword_entry & 1))
{
// present bit not set
return false;
}
var size = (dword_entry & 128) === 128,
address;
if(size && !is_directory)
{
address = dword_entry & (pae ? 0xFFE00000 : 0xFFC00000);
}
else
{
address = dword_entry & 0xFFFFF000;
}
return {
size: size,
global: (dword_entry & 256) === 256,
accessed: (dword_entry & 0x20) === 0x20,
dirty: (dword_entry & 0x40) === 0x40,
cache_disable : (dword_entry & 16) === 16,
user : (dword_entry & 4) === 4,
read_write : (dword_entry & 2) === 2,
address : address >>> 0
};
}
function dump_page_structures() {
var pae = !!(cpu.cr[4] & CR4_PAE);
if(pae)
{
dbg_log("PAE enabled");
for(var i = 0; i < 4; i++) {
var addr = cpu.cr[3] + 8 * i;
var dword = cpu.read32s(addr);
if(dword & 1)
{
dump_page_directory(dword & 0xFFFFF000, true, i << 30);
}
}
}
else
{
dbg_log("PAE disabled");
dump_page_directory(cpu.cr[3], false, 0);
}
}
/* NOTE: PAE entries are 64-bits, we ignore the high half here. */
function dump_page_directory(pd_addr, pae, start)
{
if(!DEBUG) return;
var n = pae ? 512 : 1024;
var entry_size = pae ? 8 : 4;
var pd_shift = pae ? 21 : 22;
for(var i = 0; i < n; i++)
{
var addr = pd_addr + i * entry_size,
dword = cpu.read32s(addr),
entry = load_page_entry(dword, pae, true);
if(!entry)
{
continue;
}
var flags = "";
flags += entry.size ? "S " : " ";
flags += entry.accessed ? "A " : " ";
flags += entry.cache_disable ? "Cd " : " ";
flags += entry.user ? "U " : " ";
flags += entry.read_write ? "Rw " : " ";
if(entry.size)
{
dbg_log("=== " + h(start + (i << pd_shift) >>> 0, 8) + " -> " +
h(entry.address >>> 0, 8) + " | " + flags);
continue;
}
else
{
dbg_log("=== " + h(start + (i << pd_shift) >>> 0, 8) + " | " + flags);
}
for(var j = 0; j < n; j++)
{
var sub_addr = entry.address + j * entry_size;
dword = cpu.read32s(sub_addr);
var subentry = load_page_entry(dword, pae, false);
if(subentry)
{
flags = "";
flags += subentry.cache_disable ? "Cd " : " ";
flags += subentry.user ? "U " : " ";
flags += subentry.read_write ? "Rw " : " ";
flags += subentry.global ? "G " : " ";
flags += subentry.accessed ? "A " : " ";
flags += subentry.dirty ? "Di " : " ";
dbg_log("# " + h(start + (i << pd_shift | j << 12) >>> 0, 8) + " -> " +
h(subentry.address, 8) + " | " + flags + " (at " + h(sub_addr, 8) + ")");
}
}
}
}
function get_memory_dump(start, count)
{
if(!DEBUG) return;
if(start === undefined)
{
start = 0;
count = cpu.memory_size[0];
}
else if(count === undefined)
{
count = start;
start = 0;
}
return cpu.mem8.slice(start, start + count).buffer;
}
function memory_hex_dump(addr, length)
{
if(!DEBUG) return;
length = length || 4 * 0x10;
var line, byt;
for(var i = 0; i < length >> 4; i++)
{
line = h(addr + (i << 4), 5) + " ";
for(var j = 0; j < 0x10; j++)
{
byt = cpu.read8(addr + (i << 4) + j);
line += h(byt, 2) + " ";
}
line += " ";
for(j = 0; j < 0x10; j++)
{
byt = cpu.read8(addr + (i << 4) + j);
line += (byt < 33 || byt > 126) ? "." : String.fromCharCode(byt);
}
dbg_log(line);
}
}
function used_memory_dump()
{
if(!DEBUG) return;
var width = 0x80,
height = 0x10,
block_size = cpu.memory_size[0] / width / height | 0,
row;
for(var i = 0; i < height; i++)
{
row = h(i * width * block_size, 8) + " | ";
for(var j = 0; j < width; j++)
{
var used = cpu.mem32s[(i * width + j) * block_size] > 0;
row += used ? "X" : " ";
}
dbg_log(row);
}
}
debug.debug_interrupt = function(interrupt_nr)
{
//if(interrupt_nr === 0x20)
//{
// //var vxd_device = cpu.safe_read16(cpu.instruction_pointer + 2);
// //var vxd_sub = cpu.safe_read16(cpu.instruction_pointer + 0);
// //var service = "";
// //if(vxd_device === 1)
// //{
// // service = vxd_table1[vxd_sub];
// //}
// //dbg_log("vxd: " + h(vxd_device, 4) + " " + h(vxd_sub, 4) + " " + service);
//}
//if(interrupt_nr >= 0x21 && interrupt_nr < 0x30)
//{
// dbg_log("dos: " + h(interrupt_nr, 2) + " ah=" + h(this.reg8[reg_ah], 2) + " ax=" + h(this.reg16[reg_ax], 4));
//}
//if(interrupt_nr === 0x13 && (this.reg8[reg_ah] | 1) === 0x43)
//{
// this.debug.memory_hex_dump(this.get_seg(reg_ds) + this.reg16[reg_si], 0x18);
//}
//if(interrupt_nr == 0x10)
//{
// dbg_log("int10 ax=" + h(this.reg16[reg_ax], 4) + " '" + String.fromCharCode(this.reg8[reg_al]) + "'");
// this.debug.dump_regs_short();
// if(this.reg8[reg_ah] == 0xe) vga.tt_write(this.reg8[reg_al]);
//}
//if(interrupt_nr === 0x13)
//{
// this.debug.dump_regs_short();
//}
//if(interrupt_nr === 6)
//{
// this.instruction_pointer += 2;
// dbg_log("BUG()", LOG_CPU);
// dbg_log("line=" + this.read_imm16() + " " +
// "file=" + this.read_string(this.translate_address_read(this.read_imm32s())), LOG_CPU);
// this.instruction_pointer -= 8;
// this.debug.dump_regs_short();
//}
//if(interrupt_nr === 0x80)
//{
// dbg_log("linux syscall");
// this.debug.dump_regs_short();
//}
//if(interrupt_nr === 0x40)
//{
// dbg_log("kolibri syscall");
// this.debug.dump_regs_short();
//}
};
let cs;
let capstone_decoder;
debug.dump_code = function(is_32, buffer, start)
{
if(!capstone_decoder)
{
if(cs === undefined)
{
if(typeof require === "function")
{
cs = require("./capstone-x86.min.js");
}
else
{
cs = window.cs;
}
if(cs === undefined)
{
dbg_log("Warning: Missing capstone library, disassembly not available");
return;
}
}
capstone_decoder = [
new cs.Capstone(cs.ARCH_X86, cs.MODE_16),
new cs.Capstone(cs.ARCH_X86, cs.MODE_32),
];
}
try
{
const instructions = capstone_decoder[is_32].disasm(buffer, start);
instructions.forEach(function (instr) {
dbg_log(h(instr.address >>> 0) + ": " +
v86util.pads(instr.bytes.map(x => h(x, 2).slice(-2)).join(" "), 20) + " " +
instr.mnemonic + " " + instr.op_str);
});
dbg_log("");
}
catch(e)
{
dbg_log("Could not disassemble: " + Array.from(buffer).map(x => h(x, 2)).join(" "));
}
};
function dump_file(ab, name)
{
var blob = new Blob([ab]);
var a = document.createElement("a");
a["download"] = name;
a.href = window.URL.createObjectURL(blob);
a.dataset["downloadurl"] = ["application/octet-stream", a["download"], a.href].join(":");
a.click();
window.URL.revokeObjectURL(a.src);
}
let wabt;
debug.dump_wasm = function(buffer)
{
if(wabt === undefined)
{
if(typeof require === "function")
{
wabt = require("./libwabt.js");
}
else
{
wabt = new window.WabtModule;
}
if(wabt === undefined)
{
dbg_log("Warning: Missing libwabt, wasm dump not available");
return;
}
}
// Need to make a small copy otherwise libwabt goes nuts trying to copy
// the whole underlying buffer
buffer = buffer.slice();
try
{
var module = wabt.readWasm(buffer, { readDebugNames: false });
module.generateNames();
module.applyNames();
const result = module.toText({ foldExprs: true, inlineExport: true });
dbg_log(result);
}
catch(e)
{
dump_file(buffer, "failed.wasm");
console.log(e.toString());
}
finally
{
if(module)
{
module.destroy();
}
}
};
};

View file

@ -1,10 +1,15 @@
"use strict";
import { LOG_DMA } from "./const.js";
import { h } from "./lib.js";
import { dbg_log } from "./log.js";
// For Types Only
import { CPU } from "./cpu.js";
/**
* @constructor
* @param {CPU} cpu
*/
function DMA(cpu)
export function DMA(cpu)
{
/** @const @type {CPU} */
this.cpu = cpu;

View file

@ -1,4 +1,4 @@
"use strict";
import { dbg_log, LOG_LEVEL } from "./log.js";
// A minimal elf parser for loading 32 bit, x86, little endian, executable elf files
@ -95,7 +95,7 @@ function create_struct(struct)
}
/** @param {ArrayBuffer} buffer */
function read_elf(buffer)
export function read_elf(buffer)
{
const view = new DataView(buffer);

View file

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

File diff suppressed because it is too large Load diff

2447
src/ide.js

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,12 @@
"use strict";
import { LOG_IO, MMAP_BLOCK_BITS, MMAP_BLOCK_SIZE, MMAP_MAX } from "./const.js";
import { h } from "./lib.js";
import { dbg_assert, dbg_log } from "./log.js";
// For Types Only
import { CPU } from "./cpu.js";
// Enables logging all IO port reads and writes. Very verbose
const LOG_ALL_IO = false;
/**
* The ISA IO bus
@ -7,7 +15,7 @@
* @constructor
* @param {CPU} cpu
*/
function IO(cpu)
export function IO(cpu)
{
/** @const */
this.ports = [];
@ -87,9 +95,9 @@ IO.prototype.empty_port_write = function(x)
/**
* @param {number} port_addr
* @param {Object} device
* @param {function():number=} r8
* @param {function():number=} r16
* @param {function():number=} r32
* @param {function(number):number=} r8
* @param {function(number):number=} r16
* @param {function(number):number=} r32
*/
IO.prototype.register_read = function(port_addr, device, r8, r16, r32)
{
@ -356,7 +364,7 @@ IO.prototype.port_read8 = function(port_addr)
}
var value = entry.read8.call(entry.device, port_addr);
dbg_assert(typeof value === "number");
dbg_assert(value < 0x100 && value >= 0, "8 bit port returned large value: " + h(port_addr));
if(value < 0 || value >= 0x100) dbg_assert(false, "8 bit port returned large value: " + h(port_addr));
return value;
};
@ -373,7 +381,7 @@ IO.prototype.port_read16 = function(port_addr)
}
var value = entry.read16.call(entry.device, port_addr);
dbg_assert(typeof value === "number");
dbg_assert(value < 0x10000 && value >= 0, "16 bit port returned large value: " + h(port_addr));
if(value < 0 || value >= 0x10000) dbg_assert(false, "16 bit port returned large value: " + h(port_addr));
return value;
};

View file

@ -1,364 +0,0 @@
"use strict";
// http://download.intel.com/design/chipsets/datashts/29056601.pdf
/** @const */
var IOAPIC_ADDRESS = 0xFEC00000;
/** @const */
var IOREGSEL = 0;
/** @const */
var IOWIN = 0x10;
/** @const */
var IOAPIC_IRQ_COUNT = 24;
/** @const */
var IOAPIC_ID = 0; // must match value in seabios
/** @const */
var IOAPIC_CONFIG_TRIGGER_MODE_LEVEL = 1 << 15;
/** @const */
var IOAPIC_CONFIG_MASKED = 1 << 16;
/** @const */
var IOAPIC_CONFIG_DELIVS = 1 << 12;
/** @const */
var IOAPIC_CONFIG_REMOTE_IRR = 1 << 14;
/** @const */
var IOAPIC_CONFIG_READONLY_MASK = IOAPIC_CONFIG_REMOTE_IRR | IOAPIC_CONFIG_DELIVS | 0xFFFE0000;
/** @const */
var IOAPIC_DELIVERY_FIXED = 0;
/** @const */
var IOAPIC_DELIVERY_LOWEST_PRIORITY = 1;
/** @const */
var IOAPIC_DELIVERY_NMI = 4;
/** @const */
var IOAPIC_DELIVERY_INIT = 5;
/**
* @constructor
* @param {CPU} cpu
*/
function IOAPIC(cpu)
{
/** @type {CPU} */
this.cpu = cpu;
this.ioredtbl_config = new Int32Array(IOAPIC_IRQ_COUNT);
this.ioredtbl_destination = new Int32Array(IOAPIC_IRQ_COUNT);
for(var i = 0; i < this.ioredtbl_config.length; i++)
{
// disable interrupts
this.ioredtbl_config[i] = IOAPIC_CONFIG_MASKED;
}
// IOAPIC register selection
this.ioregsel = 0;
this.ioapic_id = IOAPIC_ID;
this.irr = 0;
this.irq_value = 0;
dbg_assert(MMAP_BLOCK_SIZE >= 0x20);
cpu.io.mmap_register(IOAPIC_ADDRESS, MMAP_BLOCK_SIZE,
(addr) =>
{
addr = addr - IOAPIC_ADDRESS | 0;
if(addr >= IOWIN && addr < IOWIN + 4)
{
const byte = addr - IOWIN;
dbg_log("ioapic read8 byte " + byte + " " + h(this.ioregsel), LOG_APIC);
return this.read(this.ioregsel) >> (8 * byte) & 0xFF;
}
else
{
dbg_log("Unexpected IOAPIC register read: " + h(addr >>> 0), LOG_APIC);
dbg_assert(false);
return 0;
}
},
(addr, value) =>
{
dbg_assert(false, "unsupported write8 from ioapic: " + h(addr >>> 0));
},
(addr) =>
{
addr = addr - IOAPIC_ADDRESS | 0;
if(addr === IOREGSEL)
{
return this.ioregsel;
}
else if(addr === IOWIN)
{
return this.read(this.ioregsel);
}
else
{
dbg_log("Unexpected IOAPIC register read: " + h(addr >>> 0), LOG_APIC);
dbg_assert(false);
return 0;
}
},
(addr, value) =>
{
addr = addr - IOAPIC_ADDRESS | 0;
if(addr === IOREGSEL)
{
this.ioregsel = value;
}
else if(addr === IOWIN)
{
this.write(this.ioregsel, value);
}
else
{
dbg_log("Unexpected IOAPIC register write: " + h(addr >>> 0) + " <- " + h(value >>> 0, 8), LOG_APIC);
dbg_assert(false);
}
});
}
IOAPIC.prototype.remote_eoi = function(vector)
{
for(var i = 0; i < IOAPIC_IRQ_COUNT; i++)
{
var config = this.ioredtbl_config[i];
if((config & 0xFF) === vector && (config & IOAPIC_CONFIG_REMOTE_IRR))
{
dbg_log("Clear remote IRR for irq=" + h(i), LOG_APIC);
this.ioredtbl_config[i] &= ~IOAPIC_CONFIG_REMOTE_IRR;
this.check_irq(i);
}
}
};
IOAPIC.prototype.check_irq = function(irq)
{
var mask = 1 << irq;
if((this.irr & mask) === 0)
{
return;
}
var config = this.ioredtbl_config[irq];
if((config & IOAPIC_CONFIG_MASKED) === 0)
{
var delivery_mode = config >> 8 & 7;
var destination_mode = config >> 11 & 1;
var vector = config & 0xFF;
var destination = this.ioredtbl_destination[irq] >>> 24;
var is_level = (config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL) === IOAPIC_CONFIG_TRIGGER_MODE_LEVEL;
if((config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL) === 0)
{
this.irr &= ~mask;
}
else
{
this.ioredtbl_config[irq] |= IOAPIC_CONFIG_REMOTE_IRR;
if(config & IOAPIC_CONFIG_REMOTE_IRR)
{
dbg_log("No route: level interrupt and remote IRR still set", LOG_APIC);
return;
}
}
if(delivery_mode === IOAPIC_DELIVERY_FIXED || delivery_mode === IOAPIC_DELIVERY_LOWEST_PRIORITY)
{
this.cpu.devices.apic.route(vector, delivery_mode, is_level, destination, destination_mode);
}
else
{
dbg_assert(false, "TODO");
}
this.ioredtbl_config[irq] &= ~IOAPIC_CONFIG_DELIVS;
}
};
IOAPIC.prototype.set_irq = function(i)
{
if(i >= IOAPIC_IRQ_COUNT)
{
dbg_assert(false, "Bad irq: " + i, LOG_APIC);
return;
}
var mask = 1 << i;
if((this.irq_value & mask) === 0)
{
APIC_LOG_VERBOSE && dbg_log("apic set irq " + i, LOG_APIC);
this.irq_value |= mask;
var config = this.ioredtbl_config[i];
if((config & (IOAPIC_CONFIG_TRIGGER_MODE_LEVEL|IOAPIC_CONFIG_MASKED)) ===
IOAPIC_CONFIG_MASKED)
{
// edge triggered and masked
return;
}
this.irr |= mask;
this.check_irq(i);
}
};
IOAPIC.prototype.clear_irq = function(i)
{
if(i >= IOAPIC_IRQ_COUNT)
{
dbg_assert(false, "Bad irq: " + i, LOG_APIC);
return;
}
var mask = 1 << i;
if((this.irq_value & mask) === mask)
{
this.irq_value &= ~mask;
var config = this.ioredtbl_config[i];
if(config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL)
{
this.irr &= ~mask;
}
}
};
IOAPIC.prototype.read = function(reg)
{
if(reg === 0)
{
dbg_log("IOAPIC Read id", LOG_APIC);
return this.ioapic_id << 24;
}
else if(reg === 1)
{
dbg_log("IOAPIC Read version", LOG_APIC);
return 0x11 | IOAPIC_IRQ_COUNT - 1 << 16;
}
else if(reg === 2)
{
dbg_log("IOAPIC Read arbitration id", LOG_APIC);
return this.ioapic_id << 24;
}
else if(reg >= 0x10 && reg < 0x10 + 2 * IOAPIC_IRQ_COUNT)
{
var irq = reg - 0x10 >> 1;
var index = reg & 1;
if(index)
{
var value = this.ioredtbl_destination[irq];
dbg_log("IOAPIC Read destination irq=" + h(irq) + " -> " + h(value, 8), LOG_APIC);
}
else
{
var value = this.ioredtbl_config[irq];
dbg_log("IOAPIC Read config irq=" + h(irq) + " -> " + h(value, 8), LOG_APIC);
}
return value;
}
else
{
dbg_log("IOAPIC register read outside of range " + h(reg), LOG_APIC);
dbg_assert(false);
return 0;
}
};
IOAPIC.prototype.write = function(reg, value)
{
//dbg_log("IOAPIC write " + h(reg) + " <- " + h(value, 8), LOG_APIC);
if(reg === 0)
{
this.ioapic_id = value >>> 24 & 0x0F;
}
else if(reg === 1 || reg === 2)
{
dbg_log("Invalid write: " + reg, LOG_APIC);
}
else if(reg >= 0x10 && reg < 0x10 + 2 * IOAPIC_IRQ_COUNT)
{
var irq = reg - 0x10 >> 1;
var index = reg & 1;
if(index)
{
this.ioredtbl_destination[irq] = value & 0xFF000000;
dbg_log("Write destination " + h(value >>> 0, 8) + " irq=" + h(irq) + " dest=" + h(value >>> 24, 2), LOG_APIC);
}
else
{
var old_value = this.ioredtbl_config[irq];
this.ioredtbl_config[irq] = value & ~IOAPIC_CONFIG_READONLY_MASK | old_value & IOAPIC_CONFIG_READONLY_MASK;
var vector = value & 0xFF;
var delivery_mode = value >> 8 & 7;
var destination_mode = value >> 11 & 1;
var is_level = value >> 15 & 1;
var disabled = value >> 16 & 1;
dbg_log("Write config " + h(value >>> 0, 8) +
" irq=" + h(irq) +
" vector=" + h(vector, 2) +
" deliverymode=" + DELIVERY_MODES[delivery_mode] +
" destmode=" + DESTINATION_MODES[destination_mode] +
" is_level=" + is_level +
" disabled=" + disabled, LOG_APIC);
this.check_irq(irq);
}
}
else
{
dbg_log("IOAPIC register write outside of range " + h(reg) + ": " + h(value >>> 0, 8), LOG_APIC);
dbg_assert(false);
}
};
IOAPIC.prototype.get_state = function()
{
var state = [];
state[0] = this.ioredtbl_config;
state[1] = this.ioredtbl_destination;
state[2] = this.ioregsel;
state[3] = this.ioapic_id;
state[4] = this.irr;
state[5] = this.irq_value;
return state;
};
IOAPIC.prototype.set_state = function(state)
{
this.ioredtbl_config = state[0];
this.ioredtbl_destination = state[1];
this.ioregsel = state[2];
this.ioapic_id = state[3];
this.irr = state[4];
this.irq_value = state[5];
};

255
src/iso9660.js Normal file
View file

@ -0,0 +1,255 @@
// Source: https://wiki.osdev.org/ISO_9660
// Limitations:
// - can only generate iso files
// - only supports a single directory, no file system hierarchy
// - root directory entry is limited to 2 KiB (~42 files)
// - filenames are normalised to 8.3 length and [A-Z0-9_.]
import { dbg_assert } from "./log.js";
const BLOCK_SIZE = 2 * 1024; // 0x800
const FILE_FLAGS_HIDDEN = 1 << 0;
const FILE_FLAGS_DIRECTORY = 1 << 1;
const FILE_FLAGS_ASSOCIATED_FILE = 1 << 2;
const FILE_FLAGS_HAS_EXTENDED_ATTRIBUTE_RECORD = 1 << 3;
const FILE_FLAGS_HAS_PERMISSIONS = 1 << 4;
const FILE_FLAGS_NOT_FINAL = 1 << 7;
/**
* @param {Array.<{ name: string, contents: Uint8Array}>} files
*/
export function generate(files)
{
const te = new TextEncoder();
const date = new Date;
const write8 = (b, v) => { b.buffer[b.offset++] = v; };
const write_le16 = (b, v) => { b.buffer[b.offset++] = v; b.buffer[b.offset++] = v >> 8; };
const write_le32 = (b, v) => { b.buffer[b.offset++] = v; b.buffer[b.offset++] = v >> 8; b.buffer[b.offset++] = v >> 16; b.buffer[b.offset++] = v >> 24; };
const write_be16 = (b, v) => { b.buffer[b.offset++] = v >> 8; b.buffer[b.offset++] = v; };
const write_be32 = (b, v) => { b.buffer[b.offset++] = v >> 24; b.buffer[b.offset++] = v >> 16; b.buffer[b.offset++] = v >> 8; b.buffer[b.offset++] = v; };
const write_lebe16 = (b, v) => { write_le16(b, v); write_be16(b, v); };
const write_lebe32 = (b, v) => { write_le32(b, v); write_be32(b, v); };
const fill = (b, len, v) => { b.buffer.fill(v, b.offset, b.offset += len); };
const write_ascii = (b, v) => { b.offset += te.encodeInto(v, b.buffer.subarray(b.offset)).written; };
const write_padded_ascii = (b, len, v) => { b.offset += te.encodeInto(v.padEnd(len), b.buffer.subarray(b.offset)).written; };
const write_dummy_date_ascii = b => { fill(b, 16, 0x20); write8(b, 0); };
const write_date_compact = b => {
write8(b, date.getUTCFullYear() - 1900);
write8(b, 1 + date.getUTCMonth());
write8(b, date.getUTCDate());
write8(b, date.getUTCHours());
write8(b, date.getUTCMinutes());
write8(b, date.getUTCSeconds());
write8(b, 0);
};
const skip = (b, len) => { b.offset += len; };
const write_record = (b, name, flags, is_special, lba, len) => {
if(!is_special) name = sanitise_filename(name) + ";1";
// write name first and get its length
const START = buffer.offset;
const NAME_OFFSET = 33;
const name_len = te.encodeInto(name, b.buffer.subarray(b.offset + NAME_OFFSET)).written;
const pad = (name_len & 1) ? 0 : 1;
const len_field = 33 + name_len + pad;
dbg_assert(len_field < 256);
write8(buffer, len_field); // Length of directory record
write8(buffer, 0); // Extended Attribute Record length
write_lebe32(buffer, lba); // Location of extent (LBA)
write_lebe32(buffer, len); // Data length (size of extent)
write_date_compact(buffer);
write8(buffer, flags);
write8(buffer, 0); // File unit size for files recorded in interleaved mode, zero otherwise
write8(buffer, 0); // Interleave gap size for files recorded in interleaved mode, zero otherwise
write_lebe16(buffer, 1); // Volume sequence number - the volume that this extent is recorded on
write8(buffer, name_len); // length of file name
dbg_assert(buffer.offset === START + NAME_OFFSET);
skip(buffer, name_len + pad); // File name: was already written
dbg_assert(buffer.offset === START + len_field);
};
const write_special_directory_record = (b, name, lba, len) => write_record(b, name, FILE_FLAGS_DIRECTORY, true, lba, len);
const write_file_record = (b, name, lba, len) => write_record(b, name, 0, false, lba, len);
function round_byte_size_to_block_size(n)
{
return 1 + Math.floor((n - 1) / BLOCK_SIZE);
}
dbg_assert(round_byte_size_to_block_size(0) === 0);
dbg_assert(round_byte_size_to_block_size(1) === 1);
dbg_assert(round_byte_size_to_block_size(BLOCK_SIZE - 1) === 1);
dbg_assert(round_byte_size_to_block_size(BLOCK_SIZE) === 1);
dbg_assert(round_byte_size_to_block_size(BLOCK_SIZE + 1) === 2);
dbg_assert(round_byte_size_to_block_size(2 * BLOCK_SIZE) === 2);
dbg_assert(round_byte_size_to_block_size(2 * BLOCK_SIZE + 1) === 3);
dbg_assert(round_byte_size_to_block_size(10 * BLOCK_SIZE + 1) === 11);
function to_msdos_filename(name)
{
const dot = name.lastIndexOf(".");
if(dot === -1) return name.substr(0, 8);
return name.substr(0, Math.min(8, dot)) + "." + name.substr(dot + 1, 3);
}
dbg_assert(to_msdos_filename("abcdefghijkl.qwerty") === "abcdefgh.qwe");
dbg_assert(to_msdos_filename("abcdefghijkl") === "abcdefgh");
function sanitise_filename(name)
{
return to_msdos_filename(name.toUpperCase().replace(/[^A-Z0-9_.]/g, ""));
}
// layout:
// (lba = one block of BLOCK_SIZE bytes)
// LBA | contents
// ------+--------
// 0..15 | System Area (could be used for mbr, but not used by us)
// 16 | Primary Volume Descriptor
// 17 | Volume Descriptor Set Terminator
// 18 | empty
// 19 | Little Endian Path Table
// 20 | empty
// 21 | Big Endian Path Table
// 22 | empty
// 23 | Root directory
// 24..n | File contents
const SYSTEM_AREA_SIZE = 16 * BLOCK_SIZE;
const PRIMARY_VOLUME_LBA = 16;
const VOLUME_SET_TERMINATOR_LBA = 17;
const LE_PATH_TABLE_LBA = 19;
const BE_PATH_TABLE_LBA = 21;
const ROOT_DIRECTORY_LBA = 23;
const LE_PATH_TABLE_SIZE = BLOCK_SIZE;
const BE_PATH_TABLE_SIZE = BLOCK_SIZE;
const ROOT_DIRECTORY_SIZE = BLOCK_SIZE;
let next_file_lba = 24;
files = files.map(({ name, contents }) => {
const lba = next_file_lba;
next_file_lba += round_byte_size_to_block_size(contents.length);
name = to_msdos_filename(name);
return { name, contents, lba };
});
const N_LBAS = next_file_lba;
const total_size = N_LBAS * BLOCK_SIZE;
const buffer = {
buffer: new Uint8Array(total_size),
offset: SYSTEM_AREA_SIZE,
};
// LBA 16: Primary Volume Descriptor
dbg_assert(buffer.offset === PRIMARY_VOLUME_LBA * BLOCK_SIZE);
write8(buffer, 0x01); // Volume Descriptor type: Primary Volume Descriptor
write_ascii(buffer, "CD001"); // Always CD001
write8(buffer, 0x01); // Version
write8(buffer, 0x00); // unused
write_padded_ascii(buffer, 32, "V86"); // System Identifier
write_padded_ascii(buffer, 32, "CDROM"); // Identification of this volume
skip(buffer, 8); // unused
write_lebe32(buffer, N_LBAS);
skip(buffer, 32); // unused
dbg_assert(buffer.offset === 0x8000 + 120);
write_lebe16(buffer, 1); // Volume Set Size
write_lebe16(buffer, 1); // Volume Sequence Number
dbg_assert(buffer.offset === 0x8080);
write_lebe16(buffer, BLOCK_SIZE);
write_lebe32(buffer, 10); // Path Table Size
write_le32(buffer, LE_PATH_TABLE_LBA); // Location of Type-L Path Table
write_le32(buffer, 0); // Location of the Optional Type-L Path Table
write_be32(buffer, BE_PATH_TABLE_LBA); // Location of Type-M Path Table
write_be32(buffer, 0); // Location of the Optional Type-M Path Table
dbg_assert(buffer.offset === 0x8000 + 156);
// Directory entry for the root directory
write_special_directory_record(buffer, "\x00", ROOT_DIRECTORY_LBA, 0x800);
dbg_assert(buffer.offset === 0x8000 + 190);
fill(buffer, 128, 0x20); // Volume Set Identifier
fill(buffer, 128, 0x20); // Publisher Identifier
fill(buffer, 128, 0x20); // Data Preparer Identifier
fill(buffer, 128, 0x20); // Application Identifier
fill(buffer, 37, 0x20); // Copyright File Identifier
fill(buffer, 37, 0x20); // Abstract File Identifier
fill(buffer, 37, 0x20); // Bibliographic File Identifier
dbg_assert(buffer.offset === 0x8000 + 813);
write_dummy_date_ascii(buffer); // Volume Creation Date and Time
write_dummy_date_ascii(buffer); // Volume Modification Date and Time
write_dummy_date_ascii(buffer); // Volume Expiration Date and Time
write_dummy_date_ascii(buffer); // Volume Effective Date and Time
write8(buffer, 0x01); // File Structure Version
dbg_assert(buffer.offset === 0x8000 + 882);
write8(buffer, 0x00); // Unused
skip(buffer, 512); // Application Used
skip(buffer, 653); // Reserved
// LBA 17: Volume Descriptor Set Terminator
dbg_assert(buffer.offset === VOLUME_SET_TERMINATOR_LBA * BLOCK_SIZE);
write8(buffer, 0xFF); // 0xFF: Volume Descriptor Set Terminator
write_ascii(buffer, "CD001"); // Always CD001
write8(buffer, 0x01); // Version
// LBA 19: Little Endian Path Table
buffer.offset = LE_PATH_TABLE_LBA * BLOCK_SIZE;
write8(buffer, 0x01); // Length of Directory Identifier
write8(buffer, 0x00); // Extended Attribute Record Length
write_le32(buffer, ROOT_DIRECTORY_LBA); // Location of Extent (LBA)
write_le16(buffer, 1); // Directory number of parent directory
write_ascii(buffer, "\x00"); // file name
dbg_assert(buffer.offset < LE_PATH_TABLE_LBA * BLOCK_SIZE + LE_PATH_TABLE_SIZE);
// LBA 21: Big Endian Path Table
buffer.offset = BE_PATH_TABLE_LBA * BLOCK_SIZE;
write8(buffer, 0x01); // Length of Directory Identifier
write8(buffer, 0x00); // Extended Attribute Record Length
write_be32(buffer, ROOT_DIRECTORY_LBA); // Location of Extent (LBA)
write_be16(buffer, 1); // Directory number of parent directory
write_ascii(buffer, "\x00"); // file name
dbg_assert(buffer.offset < BE_PATH_TABLE_LBA * BLOCK_SIZE + BE_PATH_TABLE_SIZE);
// LBA 23: root directory
buffer.offset = ROOT_DIRECTORY_LBA * BLOCK_SIZE;
write_special_directory_record(buffer, "\x00", ROOT_DIRECTORY_LBA, 0x800); // "."
write_special_directory_record(buffer, "\x01", ROOT_DIRECTORY_LBA, 0x800); // ".."
for(const { name, contents, lba } of files)
{
write_file_record(buffer, name, lba, contents.length);
}
// TODO: this assertion can fail if too many files are used as input
// ROOT_DIRECTORY_SIZE should be choosen dynamically
dbg_assert(buffer.offset < ROOT_DIRECTORY_LBA * BLOCK_SIZE + ROOT_DIRECTORY_SIZE);
// file contents
for(let { contents, lba } of files)
{
buffer.buffer.set(contents, lba * BLOCK_SIZE);
}
return buffer.buffer;
}
/**
* @param {Uint8Array} buffer
*/
export function is_probably_iso9660_file(buffer)
{
return (
buffer.length >= 17 * BLOCK_SIZE &&
buffer[BLOCK_SIZE + 0] === 1 && // Primary Volume Descriptor
buffer[BLOCK_SIZE + 1] === 67 && // "C"
buffer[BLOCK_SIZE + 2] === 68 && // "D"
buffer[BLOCK_SIZE + 3] === 48 && // "0"
buffer[BLOCK_SIZE + 4] === 48 && // "0"
buffer[BLOCK_SIZE + 5] === 49 // "1"
);
}

View file

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

View file

@ -1,52 +1,20 @@
"use strict";
var goog = goog || {};
goog.exportSymbol = function(name, sym) {
if(typeof module !== "undefined" && typeof module.exports !== "undefined")
{
module.exports[name] = sym;
}
else if(typeof window !== "undefined")
{
window[name] = sym;
}
else if(typeof importScripts === "function")
{
// web worker
self[name] = sym;
}
};
goog.exportProperty = function() {};
var v86util = v86util || {};
import { dbg_assert } from "./log.js";
// pad string with spaces on the right
v86util.pads = function(str, len)
export function pads(str, len)
{
str = (str || str === 0) ? str + "" : "";
return str.padEnd(len, " ");
};
}
// pad string with zeros on the left
v86util.pad0 = function(str, len)
export function pad0(str, len)
{
str = (str || str === 0) ? str + "" : "";
return str.padStart(len, "0");
};
}
// generates array given size with zeros
v86util.zeros = function(size)
{
return Array(size).fill(0);
};
// generates [0, 1, 2, ..., size-1]
v86util.range = function(size)
{
return Array.from(Array(size).keys());
};
v86util.view = function(constructor, memory, offset, length)
export var view = function(constructor, memory, offset, length)
{
dbg_assert(offset >= 0);
return new Proxy({},
@ -78,7 +46,7 @@ v86util.view = function(constructor, memory, offset, length)
* @param {number=} len
* @return {string}
*/
function h(n, len)
export function h(n, len)
{
if(!n)
{
@ -89,14 +57,14 @@ function h(n, len)
var str = n.toString(16);
}
return "0x" + v86util.pad0(str.toUpperCase(), len || 1);
return "0x" + pad0(str.toUpperCase(), len || 1);
}
function hex_dump(buffer)
export function hex_dump(buffer)
{
function hex(n, len)
{
return v86util.pad0(n.toString(16).toUpperCase(), len);
return pad0(n.toString(16).toUpperCase(), len);
}
const result = [];
@ -144,11 +112,13 @@ function hex_dump(buffer)
return "\n" + result.join("\n") + "\n";
}
/* global require */
export var get_rand_int;
if(typeof crypto !== "undefined" && crypto.getRandomValues)
{
const rand_data = new Int32Array(1);
v86util.get_rand_int = function()
get_rand_int = function()
{
crypto.getRandomValues(rand_data);
return rand_data[0];
@ -159,34 +129,41 @@ else if(typeof require !== "undefined")
/** @type {{ randomBytes: Function }} */
const crypto = require("crypto");
v86util.get_rand_int = function()
get_rand_int = function()
{
return crypto.randomBytes(4)["readInt32LE"](0);
};
}
else if(typeof process !== "undefined")
{
import("node:" + "crypto").then(crypto => {
get_rand_int = function()
{
return crypto["randomBytes"](4)["readInt32LE"](0);
};
});
}
else
{
dbg_assert(false, "Unsupported platform: No cryptographic random values");
}
(function()
export var int_log2;
if(typeof Math.clz32 === "function" && Math.clz32(0) === 32 && Math.clz32(0x12345) === 15 && Math.clz32(-1) === 0)
{
if(typeof Math.clz32 === "function" && Math.clz32(0) === 32 && Math.clz32(0x12345) === 15 && Math.clz32(-1) === 0)
/**
* calculate the integer logarithm base 2
* @param {number} x
* @return {number}
*/
int_log2 = function(x)
{
/**
* calculate the integer logarithm base 2
* @param {number} x
* @return {number}
*/
v86util.int_log2 = function(x)
{
dbg_assert(x > 0);
dbg_assert(x > 0);
return 31 - Math.clz32(x);
};
return;
}
return 31 - Math.clz32(x);
};
} else {
var int_log2_table = new Int8Array(256);
@ -203,7 +180,7 @@ else
* @param {number} x
* @return {number}
*/
v86util.int_log2 = function(x)
int_log2 = function(x)
{
x >>>= 0;
dbg_assert(x > 0);
@ -236,28 +213,28 @@ else
}
}
};
})();
}
v86util.round_up_to_next_power_of_2 = function(x)
export const round_up_to_next_power_of_2 = function(x)
{
dbg_assert(x >= 0);
return x <= 1 ? 1 : 1 << 1 + v86util.int_log2(x - 1);
return x <= 1 ? 1 : 1 << 1 + int_log2(x - 1);
};
if(DEBUG)
if(typeof DEBUG !== "undefined" && DEBUG)
{
dbg_assert(v86util.int_log2(1) === 0);
dbg_assert(v86util.int_log2(2) === 1);
dbg_assert(v86util.int_log2(7) === 2);
dbg_assert(v86util.int_log2(8) === 3);
dbg_assert(v86util.int_log2(123456789) === 26);
dbg_assert(int_log2(1) === 0);
dbg_assert(int_log2(2) === 1);
dbg_assert(int_log2(7) === 2);
dbg_assert(int_log2(8) === 3);
dbg_assert(int_log2(123456789) === 26);
dbg_assert(v86util.round_up_to_next_power_of_2(0) === 1);
dbg_assert(v86util.round_up_to_next_power_of_2(1) === 1);
dbg_assert(v86util.round_up_to_next_power_of_2(2) === 2);
dbg_assert(v86util.round_up_to_next_power_of_2(7) === 8);
dbg_assert(v86util.round_up_to_next_power_of_2(8) === 8);
dbg_assert(v86util.round_up_to_next_power_of_2(123456789) === 134217728);
dbg_assert(round_up_to_next_power_of_2(0) === 1);
dbg_assert(round_up_to_next_power_of_2(1) === 1);
dbg_assert(round_up_to_next_power_of_2(2) === 2);
dbg_assert(round_up_to_next_power_of_2(7) === 8);
dbg_assert(round_up_to_next_power_of_2(8) === 8);
dbg_assert(round_up_to_next_power_of_2(123456789) === 134217728);
}
/**
@ -266,7 +243,7 @@ if(DEBUG)
* Queue wrapper around Uint8Array
* Used by devices such as the PS2 controller
*/
function ByteQueue(size)
export function ByteQueue(size)
{
var data = new Uint8Array(size),
start,
@ -337,7 +314,7 @@ function ByteQueue(size)
* Queue wrapper around Float32Array
* Used by devices such as the sound blaster sound card
*/
function FloatQueue(size)
export function FloatQueue(size)
{
this.size = size;
this.data = new Float32Array(size);
@ -465,7 +442,7 @@ CircularQueue.prototype.set = function(new_data)
this.index = 0;
};
function dump_file(ab, name)
export function dump_file(ab, name)
{
if(!Array.isArray(ab))
{
@ -476,7 +453,7 @@ function dump_file(ab, name)
download(blob, name);
}
function download(file_or_blob, name)
export function download(file_or_blob, name)
{
var a = document.createElement("a");
a["download"] = name;
@ -502,7 +479,7 @@ function download(file_or_blob, name)
* A simple 1d bitmap
* @constructor
*/
v86util.Bitmap = function(length_or_buffer)
export var Bitmap = function(length_or_buffer)
{
if(typeof length_or_buffer === "number")
{
@ -514,11 +491,11 @@ v86util.Bitmap = function(length_or_buffer)
}
else
{
dbg_assert(false, "v86util.Bitmap: Invalid argument");
dbg_assert(false, "Bitmap: Invalid argument");
}
};
v86util.Bitmap.prototype.set = function(index, value)
Bitmap.prototype.set = function(index, value)
{
const bit_index = index & 7;
const byte_index = index >> 3;
@ -528,7 +505,7 @@ v86util.Bitmap.prototype.set = function(index, value)
value ? this.view[byte_index] | bit_mask : this.view[byte_index] & ~bit_mask;
};
v86util.Bitmap.prototype.get = function(index)
Bitmap.prototype.get = function(index)
{
const bit_index = index & 7;
const byte_index = index >> 3;
@ -536,187 +513,249 @@ v86util.Bitmap.prototype.get = function(index)
return this.view[byte_index] >> bit_index & 1;
};
v86util.Bitmap.prototype.get_buffer = function()
Bitmap.prototype.get_buffer = function()
{
return this.view.buffer;
};
export var load_file;
export var get_file_size;
if(typeof XMLHttpRequest === "undefined")
if(typeof XMLHttpRequest === "undefined" ||
typeof process !== "undefined" && process.versions && process.versions.node)
{
v86util.load_file = load_file_nodejs;
let fs;
/**
* @param {string} filename
* @param {Object} options
* @param {number=} n_tries
*/
load_file = async function(filename, options, n_tries)
{
if(!fs)
{
// string concat to work around closure compiler 'Invalid module path "node:fs/promises" for resolution mode'
fs = await import("node:" + "fs/promises");
}
if(options.range)
{
dbg_assert(!options.as_json);
const fd = await fs["open"](filename, "r");
const length = options.range.length;
const buffer = Buffer.allocUnsafe(length);
try
{
/** @type {{ bytesRead: Number }} */
const result = await fd["read"]({
buffer,
position: options.range.start
});
dbg_assert(result.bytesRead === length);
}
finally
{
await fd["close"]();
}
options.done && options.done(new Uint8Array(buffer));
}
else
{
const o = {
encoding: options.as_json ? "utf-8" : null,
};
const data = await fs["readFile"](filename, o);
const result = options.as_json ? JSON.parse(data) : new Uint8Array(data).buffer;
options.done(result);
}
};
get_file_size = async function(path)
{
if(!fs)
{
// string concat to work around closure compiler 'Invalid module path "node:fs/promises" for resolution mode'
fs = await import("node:" + "fs/promises");
}
const stat = await fs["stat"](path);
return stat.size;
};
}
else
{
v86util.load_file = load_file;
}
/**
* @param {string} filename
* @param {Object} options
* @param {number=} n_tries
*/
function load_file(filename, options, n_tries)
{
var http = new XMLHttpRequest();
http.open(options.method || "get", filename, true);
if(options.as_json)
/**
* @param {string} filename
* @param {Object} options
* @param {number=} n_tries
*/
load_file = async function(filename, options, n_tries)
{
http.responseType = "json";
}
else
{
http.responseType = "arraybuffer";
}
var http = new XMLHttpRequest();
if(options.headers)
{
var header_names = Object.keys(options.headers);
http.open(options.method || "get", filename, true);
for(var i = 0; i < header_names.length; i++)
if(options.as_json)
{
var name = header_names[i];
http.setRequestHeader(name, options.headers[name]);
http.responseType = "json";
}
}
if(options.range)
{
const start = options.range.start;
const end = start + options.range.length - 1;
http.setRequestHeader("Range", "bytes=" + start + "-" + end);
http.setRequestHeader("X-Accept-Encoding", "identity");
// Abort if server responds with complete file in response to range
// request, to prevent downloading large files from broken http servers
http.onreadystatechange = function()
else
{
if(http.status === 200)
http.responseType = "arraybuffer";
}
if(options.headers)
{
var header_names = Object.keys(options.headers);
for(var i = 0; i < header_names.length; i++)
{
console.error("Server sent full file in response to ranged request, aborting", { filename });
http.abort();
var name = header_names[i];
http.setRequestHeader(name, options.headers[name]);
}
};
}
}
http.onload = function(e)
{
if(http.readyState === 4)
if(options.range)
{
if(http.status !== 200 && http.status !== 206)
const start = options.range.start;
const end = start + options.range.length - 1;
http.setRequestHeader("Range", "bytes=" + start + "-" + end);
http.setRequestHeader("X-Accept-Encoding", "identity");
// Abort if server responds with complete file in response to range
// request, to prevent downloading large files from broken http servers
http.onreadystatechange = function()
{
console.error("Loading the image " + filename + " failed (status %d)", http.status);
if(http.status >= 500 && http.status < 600)
if(http.status === 200)
{
retry();
console.error("Server sent full file in response to ranged request, aborting", { filename });
http.abort();
}
}
else if(http.response)
};
}
http.onload = function(e)
{
if(http.readyState === 4)
{
if(options.range)
if(http.status !== 200 && http.status !== 206)
{
const enc = http.getResponseHeader("Content-Encoding");
if(enc && enc !== "identity")
console.error("Loading the image " + filename + " failed (status %d)", http.status);
if(http.status >= 500 && http.status < 600)
{
console.error("Server sent Content-Encoding in response to ranged request", {filename, enc});
retry();
}
}
options.done && options.done(http.response, http);
else if(http.response)
{
if(options.range)
{
const enc = http.getResponseHeader("Content-Encoding");
if(enc && enc !== "identity")
{
console.error("Server sent Content-Encoding in response to ranged request", {filename, enc});
}
}
options.done && options.done(http.response, http);
}
}
};
http.onerror = function(e)
{
console.error("Loading the image " + filename + " failed", e);
retry();
};
if(options.progress)
{
http.onprogress = function(e)
{
options.progress(e);
};
}
http.send(null);
function retry()
{
const number_of_tries = n_tries || 0;
const timeout = [1, 1, 2, 3, 5, 8, 13, 21][number_of_tries] || 34;
setTimeout(() => {
load_file(filename, options, number_of_tries + 1);
}, 1000 * timeout);
}
};
http.onerror = function(e)
get_file_size = async function(url)
{
console.error("Loading the image " + filename + " failed", e);
retry();
};
if(options.progress)
{
http.onprogress = function(e)
{
options.progress(e);
};
}
http.send(null);
function retry()
{
const number_of_tries = n_tries || 0;
const timeout = [1, 1, 2, 3, 5, 8, 13, 21][number_of_tries] || 34;
setTimeout(() => {
load_file(filename, options, number_of_tries + 1);
}, 1000 * timeout);
}
}
function load_file_nodejs(filename, options)
{
const fs = require("fs");
if(options.range)
{
dbg_assert(!options.as_json);
fs["open"](filename, "r", (err, fd) =>
{
if(err) throw err;
const length = options.range.length;
var buffer = Buffer.allocUnsafe(length);
fs["read"](fd, buffer, 0, length, options.range.start, (err, bytes_read) =>
{
if(err) throw err;
dbg_assert(bytes_read === length);
options.done && options.done(new Uint8Array(buffer));
fs["close"](fd, (err) => {
if(err) throw err;
});
});
});
}
else
{
var o = {
encoding: options.as_json ? "utf-8" : null,
};
fs["readFile"](filename, o, function(err, data)
{
if(err)
return new Promise((resolve, reject) => {
load_file(url, {
done: (buffer, http) =>
{
console.log("Could not read file:", filename, err);
}
else
{
var result = data;
var header = http.getResponseHeader("Content-Range") || "";
var match = header.match(/\/(\d+)\s*$/);
if(options.as_json)
if(match)
{
result = JSON.parse(result);
resolve(+match[1]);
}
else
{
result = new Uint8Array(result).buffer;
const error = new Error("`Range: bytes=...` header not supported (Got `" + header + "`)");
reject(error);
}
options.done(result);
},
headers: {
Range: "bytes=0-0",
"X-Accept-Encoding": "identity"
}
});
}
});
};
}
// Reads len characters at offset from Memory object mem as a JS string
v86util.read_sized_string_from_mem = function read_sized_string_from_mem(mem, offset, len)
export function read_sized_string_from_mem(mem, offset, len)
{
offset >>>= 0;
len >>>= 0;
return String.fromCharCode(...new Uint8Array(mem.buffer, offset, len));
}
/**
* Unicode mappings of supported 8-bit code pages.
* Each mapping is a string of 256 Unicode symbols used as a lookup table for 8-bit character codes.
*
* Supported mappings and their encoding labels:
* - "cp437": CP437 (MS-DOS Latin US), default
* - "cp858": CP858 (Western Europe), the lower 128 bytes are identical to CP437
* - "ascii": ASCII (7-Bit), same as CP437 with lower 32 and upper 128 bytes mapped to "."
*
* @type {Object<string, string>}
*/
const CHARMAPS =
{
cp437: " ☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ",
cp858: "ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø×ƒáíóúñѪº¿®¬½¼¡«»░▒▓│┤ÁÂÀ©╣║╗╝¢¥┐└┴┬├─┼ãÃ╚╔╩╦╠═╬¤ðÐÊËÈ€ÍÎÏ┘┌█▄¦Ì▀ÓßÔÒõÕµþÞÚÛÙýݯ´­±‗¾¶§÷¸°¨·¹³²■ "
};
CHARMAPS.cp858 = CHARMAPS.cp437.slice(0, 128) + CHARMAPS.cp858;
CHARMAPS.ascii = CHARMAPS.cp437.split("").map((c, i) => i > 31 && i < 128 ? c : ".").join("");
/**
* Return charmap for given encoding, default to CP437 if encoding is falsey or not defined in CHARMAPS.
*
* @param {string} encoding
* @return {!string}
*/
export function get_charmap(encoding)
{
return encoding && CHARMAPS[encoding] ? CHARMAPS[encoding] : CHARMAPS.cp437;
}

View file

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

View file

@ -1,10 +1,12 @@
"use strict";
import { CPU } from "./cpu.js";
import { save_state, restore_state } from "./state.js";
export { V86 } from "./browser/starter.js";
/**
* @constructor
* @param {Object=} wasm
*/
function v86(bus, wasm)
export function v86(bus, wasm)
{
/** @type {boolean} */
this.running = false;
@ -98,6 +100,7 @@ if(typeof process !== "undefined")
{
v86.prototype.yield = function(t, tick)
{
/* global global */
if(t < 1)
{
global.setImmediate(tick => this.yield_callback(tick), tick);
@ -111,6 +114,17 @@ if(typeof process !== "undefined")
v86.prototype.register_yield = function() {};
v86.prototype.unregister_yield = function() {};
}
else if(globalThis["scheduler"] && typeof globalThis["scheduler"]["postTask"] === "function" && location.href.includes("use-scheduling-api"))
{
v86.prototype.yield = function(t, tick)
{
t = Math.max(0, t);
globalThis["scheduler"]["postTask"](() => this.yield_callback(tick), { delay: t });
};
v86.prototype.register_yield = function() {};
v86.prototype.unregister_yield = function() {};
}
else if(typeof Worker !== "undefined")
{
// XXX: This has a slightly lower throughput compared to window.postMessage
@ -152,8 +166,7 @@ else if(typeof Worker !== "undefined")
// // TODO: Make this deactivatable, for other applications
// // using postMessage
//
// /** @const */
// let MAGIC_POST_MESSAGE = 0xAA55;
// const MAGIC_POST_MESSAGE = 0xAA55;
//
// v86.prototype.yield = function(t)
// {
@ -196,16 +209,16 @@ else
v86.prototype.save_state = function()
{
// TODO: Should be implemented here, not on cpu
return this.cpu.save_state();
return save_state(this.cpu);
};
v86.prototype.restore_state = function(state)
{
// TODO: Should be implemented here, not on cpu
return this.cpu.restore_state(state);
return restore_state(this.cpu, state);
};
/* global require */
if(typeof performance === "object" && performance.now)
{
v86.microtick = performance.now.bind(performance);

View file

@ -1,98 +0,0 @@
"use strict";
CPU.prototype.mmap_read8 = function(addr)
{
const value = this.memory_map_read8[addr >>> MMAP_BLOCK_BITS](addr);
dbg_assert(value >= 0 && value <= 0xFF);
return value;
};
CPU.prototype.mmap_write8 = function(addr, value)
{
dbg_assert(value >= 0 && value <= 0xFF);
this.memory_map_write8[addr >>> MMAP_BLOCK_BITS](addr, value);
};
CPU.prototype.mmap_read16 = function(addr)
{
var fn = this.memory_map_read8[addr >>> MMAP_BLOCK_BITS];
const value = fn(addr) | fn(addr + 1 | 0) << 8;
dbg_assert(value >= 0 && value <= 0xFFFF);
return value;
};
CPU.prototype.mmap_write16 = function(addr, value)
{
var fn = this.memory_map_write8[addr >>> MMAP_BLOCK_BITS];
dbg_assert(value >= 0 && value <= 0xFFFF);
fn(addr, value & 0xFF);
fn(addr + 1 | 0, value >> 8);
};
CPU.prototype.mmap_read32 = function(addr)
{
var aligned_addr = addr >>> MMAP_BLOCK_BITS;
return this.memory_map_read32[aligned_addr](addr);
};
CPU.prototype.mmap_write32 = function(addr, value)
{
var aligned_addr = addr >>> MMAP_BLOCK_BITS;
this.memory_map_write32[aligned_addr](addr, value);
};
CPU.prototype.mmap_write64 = function(addr, value0, value1)
{
var aligned_addr = addr >>> MMAP_BLOCK_BITS;
// This should hold since writes across pages are split up
dbg_assert(aligned_addr === (addr + 7) >>> MMAP_BLOCK_BITS);
var write_func32 = this.memory_map_write32[aligned_addr];
write_func32(addr, value0);
write_func32(addr + 4, value1);
};
CPU.prototype.mmap_write128 = function(addr, value0, value1, value2, value3)
{
var aligned_addr = addr >>> MMAP_BLOCK_BITS;
// This should hold since writes across pages are split up
dbg_assert(aligned_addr === (addr + 12) >>> MMAP_BLOCK_BITS);
var write_func32 = this.memory_map_write32[aligned_addr];
write_func32(addr, value0);
write_func32(addr + 4, value1);
write_func32(addr + 8, value2);
write_func32(addr + 12, value3);
};
/**
* @param {Array.<number>|Uint8Array} blob
* @param {number} offset
*/
CPU.prototype.write_blob = function(blob, offset)
{
dbg_assert(blob && blob.length >= 0);
if(blob.length)
{
dbg_assert(!this.in_mapped_range(offset));
dbg_assert(!this.in_mapped_range(offset + blob.length - 1));
this.jit_dirty_cache(offset, offset + blob.length);
this.mem8.set(blob, offset);
}
};
CPU.prototype.read_blob = function(offset, length)
{
if(length)
{
dbg_assert(!this.in_mapped_range(offset));
dbg_assert(!this.in_mapped_range(offset + length - 1));
}
return this.mem8.subarray(offset, offset + length);
};

View file

@ -1,59 +1,66 @@
"use strict";
import { LOG_NET } from "./const.js";
import { h, hex_dump } from "./lib.js";
import { dbg_assert, dbg_log } from "./log.js";
// For Types Only
import { CPU } from "./cpu.js";
import { PCI } from "./pci.js";
import { BusConnector } from "./bus.js";
// http://www.ethernut.de/pdf/8019asds.pdf
const NE2K_LOG_VERBOSE = false;
const NE2K_LOG_PACKETS = false;
/** @const */ var E8390_CMD = 0x00; /* The command register (for all pages) */
const E8390_CMD = 0x00; /* The command register (for all pages) */
/* Page 0 register offsets. */
/** @const */ var EN0_CLDALO = 0x01; /* Low byte of current local dma addr RD */
/** @const */ var EN0_STARTPG = 0x01; /* Starting page of ring bfr WR */
/** @const */ var EN0_CLDAHI = 0x02; /* High byte of current local dma addr RD */
/** @const */ var EN0_STOPPG = 0x02; /* Ending page +1 of ring bfr WR */
/** @const */ var EN0_BOUNDARY = 0x03; /* Boundary page of ring bfr RD WR */
/** @const */ var EN0_TSR = 0x04; /* Transmit status reg RD */
/** @const */ var EN0_TPSR = 0x04; /* Transmit starting page WR */
/** @const */ var EN0_NCR = 0x05; /* Number of collision reg RD */
/** @const */ var EN0_TCNTLO = 0x05; /* Low byte of tx byte count WR */
/** @const */ var EN0_FIFO = 0x06; /* FIFO RD */
/** @const */ var EN0_TCNTHI = 0x06; /* High byte of tx byte count WR */
/** @const */ var EN0_ISR = 0x07; /* Interrupt status reg RD WR */
/** @const */ var EN0_CRDALO = 0x08; /* low byte of current remote dma address RD */
/** @const */ var EN0_RSARLO = 0x08; /* Remote start address reg 0 */
/** @const */ var EN0_CRDAHI = 0x09; /* high byte, current remote dma address RD */
/** @const */ var EN0_RSARHI = 0x09; /* Remote start address reg 1 */
/** @const */ var EN0_RCNTLO = 0x0a; /* Remote byte count reg WR */
/** @const */ var EN0_RCNTHI = 0x0b; /* Remote byte count reg WR */
/** @const */ var EN0_RSR = 0x0c; /* rx status reg RD */
/** @const */ var EN0_RXCR = 0x0c; /* RX configuration reg WR */
/** @const */ var EN0_TXCR = 0x0d; /* TX configuration reg WR */
/** @const */ var EN0_COUNTER0 = 0x0d; /* Rcv alignment error counter RD */
/** @const */ var EN0_DCFG = 0x0e; /* Data configuration reg WR */
/** @const */ var EN0_COUNTER1 = 0x0e; /* Rcv CRC error counter RD */
/** @const */ var EN0_IMR = 0x0f; /* Interrupt mask reg WR */
/** @const */ var EN0_COUNTER2 = 0x0f; /* Rcv missed frame error counter RD */
const EN0_CLDALO = 0x01; /* Low byte of current local dma addr RD */
const EN0_STARTPG = 0x01; /* Starting page of ring bfr WR */
const EN0_CLDAHI = 0x02; /* High byte of current local dma addr RD */
const EN0_STOPPG = 0x02; /* Ending page +1 of ring bfr WR */
const EN0_BOUNDARY = 0x03; /* Boundary page of ring bfr RD WR */
const EN0_TSR = 0x04; /* Transmit status reg RD */
const EN0_TPSR = 0x04; /* Transmit starting page WR */
const EN0_NCR = 0x05; /* Number of collision reg RD */
const EN0_TCNTLO = 0x05; /* Low byte of tx byte count WR */
const EN0_FIFO = 0x06; /* FIFO RD */
const EN0_TCNTHI = 0x06; /* High byte of tx byte count WR */
const EN0_ISR = 0x07; /* Interrupt status reg RD WR */
const EN0_CRDALO = 0x08; /* low byte of current remote dma address RD */
const EN0_RSARLO = 0x08; /* Remote start address reg 0 */
const EN0_CRDAHI = 0x09; /* high byte, current remote dma address RD */
const EN0_RSARHI = 0x09; /* Remote start address reg 1 */
const EN0_RCNTLO = 0x0a; /* Remote byte count reg WR */
const EN0_RCNTHI = 0x0b; /* Remote byte count reg WR */
const EN0_RSR = 0x0c; /* rx status reg RD */
const EN0_RXCR = 0x0c; /* RX configuration reg WR */
const EN0_TXCR = 0x0d; /* TX configuration reg WR */
const EN0_COUNTER0 = 0x0d; /* Rcv alignment error counter RD */
const EN0_DCFG = 0x0e; /* Data configuration reg WR */
const EN0_COUNTER1 = 0x0e; /* Rcv CRC error counter RD */
const EN0_IMR = 0x0f; /* Interrupt mask reg WR */
const EN0_COUNTER2 = 0x0f; /* Rcv missed frame error counter RD */
/** @const */ var NE_DATAPORT = 0x10; /* NatSemi-defined port window offset. */
/** @const */ var NE_RESET = 0x1f; /* Issue a read to reset, a write to clear. */
const NE_DATAPORT = 0x10; /* NatSemi-defined port window offset. */
const NE_RESET = 0x1f; /* Issue a read to reset, a write to clear. */
/* Bits in EN0_ISR - Interrupt status register */
/** @const */ var ENISR_RX = 0x01; /* Receiver, no error */
/** @const */ var ENISR_TX = 0x02; /* Transmitter, no error */
/** @const */ var ENISR_RX_ERR = 0x04; /* Receiver, with error */
/** @const */ var ENISR_TX_ERR = 0x08; /* Transmitter, with error */
/** @const */ var ENISR_OVER = 0x10; /* Receiver overwrote the ring */
/** @const */ var ENISR_COUNTERS = 0x20; /* Counters need emptying */
/** @const */ var ENISR_RDC = 0x40; /* remote dma complete */
/** @const */ var ENISR_RESET = 0x80; /* Reset completed */
/** @const */ var ENISR_ALL = 0x3f; /* Interrupts we will enable */
const ENISR_RX = 0x01; /* Receiver, no error */
const ENISR_TX = 0x02; /* Transmitter, no error */
const ENISR_RX_ERR = 0x04; /* Receiver, with error */
const ENISR_TX_ERR = 0x08; /* Transmitter, with error */
const ENISR_OVER = 0x10; /* Receiver overwrote the ring */
const ENISR_COUNTERS = 0x20; /* Counters need emptying */
const ENISR_RDC = 0x40; /* remote dma complete */
const ENISR_RESET = 0x80; /* Reset completed */
const ENISR_ALL = 0x3f; /* Interrupts we will enable */
/** @const */ var ENRSR_RXOK = 0x01; /* Received a good packet */
const ENRSR_RXOK = 0x01; /* Received a good packet */
/** @const */ var START_PAGE = 0x40;
/** @const */ var START_RX_PAGE = 0x40 + 12;
/** @const */ var STOP_PAGE = 0x80;
const START_PAGE = 0x40;
const START_RX_PAGE = 0x40 + 12;
const STOP_PAGE = 0x80;
// Search and replace MAC addresses in ethernet, arp and dhcp packets.
@ -226,7 +233,7 @@ function translate_mac_address(packet, search_mac, replacement_mac)
}
}
function format_mac(mac)
export function format_mac(mac)
{
return [
mac[0].toString(16).padStart(2, "0"),
@ -288,7 +295,7 @@ function dump_packet(packet, prefix)
* @param {Boolean} mac_address_translation
* @param {number} [id=0] id
*/
function Ne2k(cpu, bus, preserve_mac_from_state_image, mac_address_translation, id)
export function Ne2k(cpu, bus, preserve_mac_from_state_image, mac_address_translation, id)
{
/** @const @type {CPU} */
this.cpu = cpu;
@ -311,8 +318,7 @@ function Ne2k(cpu, bus, preserve_mac_from_state_image, mac_address_translation,
this.name = "ne2k";
/** @const */
var use_pci = true;
const use_pci = true;
if(use_pci)
{
@ -390,7 +396,12 @@ function Ne2k(cpu, bus, preserve_mac_from_state_image, mac_address_translation,
{
dbg_log("Read cmd", LOG_NET);
return this.cr;
});
}, function()
{
dbg_log("Read16 cmd", LOG_NET);
return this.cr;
}
);
io.register_write(this.port | E8390_CMD, this, function(data_byte)
{

View file

@ -1,16 +1,20 @@
"use strict";
import { LOG_PCI } from "./const.js";
import { h } from "./lib.js";
import { dbg_assert, dbg_log } from "./log.js";
// For Types Only
import { CPU } from "./cpu.js";
// http://wiki.osdev.org/PCI
var
/** @const */ PCI_CONFIG_ADDRESS = 0xCF8,
/** @const */ PCI_CONFIG_DATA = 0xCFC;
export const PCI_CONFIG_ADDRESS = 0xCF8;
export const PCI_CONFIG_DATA = 0xCFC;
/**
* @constructor
* @param {CPU} cpu
*/
function PCI(cpu)
export function PCI(cpu)
{
this.pci_addr = new Uint8Array(4);
this.pci_value = new Uint8Array(4);
@ -390,7 +394,7 @@ PCI.prototype.pci_write32 = function(address, written)
var bar_nr = addr - 0x10 >> 2;
var bar = device.pci_bars[bar_nr];
dbg_log("BAR" + bar_nr + " exists=" + (bar ? "y" : "n") + " changed to " +
dbg_log("BAR" + bar_nr + " exists=" + (bar ? "y" : "n") + " changed from " + h(space[addr >> 2]) + " to " +
h(written >>> 0) + " dev=" + h(bdf >> 3, 2) + " (" + device.name + ") ", LOG_PCI);
if(bar)
@ -491,7 +495,10 @@ PCI.prototype.register_device = function(device)
dbg_log("PCI register bdf=" + h(device_id) + " (" + device.name + ")", LOG_PCI);
dbg_assert(!this.devices[device_id]);
if(this.devices[device_id])
{
dbg_log("warning: overwriting device " + this.devices[device_id].name + " with " + device.name, LOG_PCI);
}
dbg_assert(device.pci_space.length >= 64);
dbg_assert(device_id < this.devices.length);
@ -514,6 +521,7 @@ PCI.prototype.register_device = function(device)
var bar_base = bar_space[i];
var type = bar_base & 1;
dbg_log("device "+ device.name +" register bar of size "+bar.size +" at " + h(bar_base), LOG_PCI);
bar.original_bar = bar_base;
bar.entries = [];
@ -553,17 +561,6 @@ PCI.prototype.set_io_bars = function(bar, from, to)
ports[from + i] = this.io.create_empty_entry();
}
if(old_entry.read8 === this.io.empty_port_read8 &&
old_entry.read16 === this.io.empty_port_read16 &&
old_entry.read32 === this.io.empty_port_read32 &&
old_entry.write8 === this.io.empty_port_write &&
old_entry.write16 === this.io.empty_port_write &&
old_entry.write32 === this.io.empty_port_write)
{
// happens when a device doesn't register its full range (currently ne2k and virtio)
dbg_log("Warning: Bad IO bar: Source not mapped, port=" + h(from + i, 4), LOG_PCI);
}
var entry = bar.entries[i];
var empty_entry = ports[to + i];
dbg_assert(entry && empty_entry);
@ -572,18 +569,6 @@ PCI.prototype.set_io_bars = function(bar, from, to)
{
ports[to + i] = entry;
}
if(empty_entry.read8 === this.io.empty_port_read8 ||
empty_entry.read16 === this.io.empty_port_read16 ||
empty_entry.read32 === this.io.empty_port_read32 ||
empty_entry.write8 === this.io.empty_port_write ||
empty_entry.write16 === this.io.empty_port_write ||
empty_entry.write32 === this.io.empty_port_write)
{
// These can fail if the os maps an io port in multiple bars (indicating a bug)
// XXX: Fails during restore_state
dbg_log("Warning: Bad IO bar: Target already mapped, port=" + h(to + i, 4), LOG_PCI);
}
}
};

View file

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

View file

@ -1,6 +1,12 @@
"use strict";
import { LOG_PS2 } from "./const.js";
import { h } from "./lib.js";
import { dbg_log } from "./log.js";
// For Types Only
import { CPU } from "./cpu.js";
import { BusConnector } from "./bus.js";
import { ByteQueue } from "./lib.js";
/** @const */
const PS2_LOG_VERBOSE = false;
/**
@ -8,7 +14,7 @@ const PS2_LOG_VERBOSE = false;
* @param {CPU} cpu
* @param {BusConnector} bus
*/
function PS2(cpu, bus)
export function PS2(cpu, bus)
{
/** @const @type {CPU} */
this.cpu = cpu;
@ -16,6 +22,40 @@ function PS2(cpu, bus)
/** @const @type {BusConnector} */
this.bus = bus;
this.reset();
this.bus.register("keyboard-code", function(code)
{
this.kbd_send_code(code);
}, this);
this.bus.register("mouse-click", function(data)
{
this.mouse_send_click(data[0], data[1], data[2]);
}, this);
this.bus.register("mouse-delta", function(data)
{
this.mouse_send_delta(data[0], data[1]);
}, this);
this.bus.register("mouse-wheel", function(data)
{
this.wheel_movement -= data[0];
this.wheel_movement -= data[1] * 2; // X Wheel Movement
this.wheel_movement = Math.min(7, Math.max(-8, this.wheel_movement));
this.send_mouse_packet(0, 0);
}, this);
cpu.io.register_read(0x60, this, this.port60_read);
cpu.io.register_read(0x64, this, this.port64_read);
cpu.io.register_write(0x60, this, this.port60_write);
cpu.io.register_write(0x64, this, this.port64_write);
}
PS2.prototype.reset = function()
{
/** @type {boolean} */
this.enable_mouse_stream = false;
@ -101,42 +141,13 @@ function PS2(cpu, bus)
/** @type {boolean} */
this.next_byte_is_aux = false;
this.bus.register("keyboard-code", function(code)
{
this.kbd_send_code(code);
}, this);
this.bus.register("mouse-click", function(data)
{
this.mouse_send_click(data[0], data[1], data[2]);
}, this);
this.bus.register("mouse-delta", function(data)
{
this.mouse_send_delta(data[0], data[1]);
}, this);
this.bus.register("mouse-wheel", function(data)
{
this.wheel_movement -= data[0];
this.wheel_movement -= data[1] * 2; // X Wheel Movement
this.wheel_movement = Math.min(7, Math.max(-8, this.wheel_movement));
this.send_mouse_packet(0, 0);
}, this);
this.command_register = 1 | 4;
// TODO: What should be the initial value?
this.controller_output_port = 0;
this.read_output_register = false;
this.read_command_register = false;
this.read_controller_output_port = false;
cpu.io.register_read(0x60, this, this.port60_read);
cpu.io.register_read(0x64, this, this.port64_read);
cpu.io.register_write(0x60, this, this.port60_write);
cpu.io.register_write(0x64, this, this.port64_write);
}
};
PS2.prototype.get_state = function()
{
@ -286,7 +297,8 @@ PS2.prototype.mouse_send_delta = function(delta_x, delta_y)
// note: delta_x or delta_y can be floating point numbers
var factor = this.resolution * this.sample_rate / 80;
//const factor = this.resolution * this.sample_rate / 80;
const factor = 1;
this.mouse_delta_x += delta_x * factor;
this.mouse_delta_y += delta_y * factor;
@ -298,8 +310,7 @@ PS2.prototype.mouse_send_delta = function(delta_x, delta_y)
if(change_x || change_y)
{
var now = Date.now();
//var now = Date.now();
//if(now - this.last_mouse_packet < 1000 / this.sample_rate)
//{
// // TODO: set timeout

View file

@ -1,57 +1,66 @@
"use strict";
import { v86 } from "./main.js";
import { LOG_RTC } from "./const.js";
import { h } from "./lib.js";
import { dbg_assert, dbg_log } from "./log.js";
/** @const */ var CMOS_RTC_SECONDS = 0x00;
/** @const */ var CMOS_RTC_SECONDS_ALARM = 0x01;
/** @const */ var CMOS_RTC_MINUTES = 0x02;
/** @const */ var CMOS_RTC_MINUTES_ALARM = 0x03;
/** @const */ var CMOS_RTC_HOURS = 0x04;
/** @const */ var CMOS_RTC_HOURS_ALARM = 0x05;
/** @const */ var CMOS_RTC_DAY_WEEK = 0x06;
/** @const */ var CMOS_RTC_DAY_MONTH = 0x07;
/** @const */ var CMOS_RTC_MONTH = 0x08;
/** @const */ var CMOS_RTC_YEAR = 0x09;
/** @const */ var CMOS_STATUS_A = 0x0a;
/** @const */ var CMOS_STATUS_B = 0x0b;
/** @const */ var CMOS_STATUS_C = 0x0c;
/** @const */ var CMOS_STATUS_D = 0x0d;
/** @const */ var CMOS_RESET_CODE = 0x0f;
// For Types Only
import { CPU } from "./cpu.js";
import { DMA } from "./dma.js";
/** @const */ var CMOS_FLOPPY_DRIVE_TYPE = 0x10;
/** @const */ var CMOS_DISK_DATA = 0x12;
/** @const */ var CMOS_EQUIPMENT_INFO = 0x14;
/** @const */ var CMOS_MEM_BASE_LOW = 0x15;
/** @const */ var CMOS_MEM_BASE_HIGH = 0x16;
/** @const */ var CMOS_MEM_OLD_EXT_LOW = 0x17;
/** @const */ var CMOS_MEM_OLD_EXT_HIGH = 0x18;
/** @const */ var CMOS_DISK_DRIVE1_TYPE = 0x19;
/** @const */ var CMOS_DISK_DRIVE2_TYPE = 0x1a;
/** @const */ var CMOS_DISK_DRIVE1_CYL = 0x1b;
/** @const */ var CMOS_DISK_DRIVE2_CYL = 0x24;
/** @const */ var CMOS_MEM_EXTMEM_LOW = 0x30;
/** @const */ var CMOS_MEM_EXTMEM_HIGH = 0x31;
/** @const */ var CMOS_CENTURY = 0x32;
/** @const */ var CMOS_MEM_EXTMEM2_LOW = 0x34;
/** @const */ var CMOS_MEM_EXTMEM2_HIGH = 0x35;
/** @const */ var CMOS_CENTURY2 = 0x37;
/** @const */ var CMOS_BIOS_BOOTFLAG1 = 0x38;
/** @const */ var CMOS_BIOS_DISKTRANSFLAG = 0x39;
/** @const */ var CMOS_BIOS_BOOTFLAG2 = 0x3d;
/** @const */ var CMOS_MEM_HIGHMEM_LOW = 0x5b;
/** @const */ var CMOS_MEM_HIGHMEM_MID = 0x5c;
/** @const */ var CMOS_MEM_HIGHMEM_HIGH = 0x5d;
/** @const */ var CMOS_BIOS_SMP_COUNT = 0x5f;
export const CMOS_RTC_SECONDS = 0x00;
export const CMOS_RTC_SECONDS_ALARM = 0x01;
export const CMOS_RTC_MINUTES = 0x02;
export const CMOS_RTC_MINUTES_ALARM = 0x03;
export const CMOS_RTC_HOURS = 0x04;
export const CMOS_RTC_HOURS_ALARM = 0x05;
export const CMOS_RTC_DAY_WEEK = 0x06;
export const CMOS_RTC_DAY_MONTH = 0x07;
export const CMOS_RTC_MONTH = 0x08;
export const CMOS_RTC_YEAR = 0x09;
export const CMOS_STATUS_A = 0x0a;
export const CMOS_STATUS_B = 0x0b;
export const CMOS_STATUS_C = 0x0c;
export const CMOS_STATUS_D = 0x0d;
export const CMOS_DIAG_STATUS = 0x0e;
export const CMOS_RESET_CODE = 0x0f;
export const CMOS_FLOPPY_DRIVE_TYPE = 0x10;
export const CMOS_DISK_DATA = 0x12;
export const CMOS_EQUIPMENT_INFO = 0x14;
export const CMOS_MEM_BASE_LOW = 0x15;
export const CMOS_MEM_BASE_HIGH = 0x16;
export const CMOS_MEM_OLD_EXT_LOW = 0x17;
export const CMOS_MEM_OLD_EXT_HIGH = 0x18;
export const CMOS_DISK_DRIVE1_TYPE = 0x19;
export const CMOS_DISK_DRIVE2_TYPE = 0x1a;
export const CMOS_DISK_DRIVE1_CYL = 0x1b;
export const CMOS_DISK_DRIVE2_CYL = 0x24;
export const CMOS_MEM_EXTMEM_LOW = 0x30;
export const CMOS_MEM_EXTMEM_HIGH = 0x31;
export const CMOS_CENTURY = 0x32;
export const CMOS_MEM_EXTMEM2_LOW = 0x34;
export const CMOS_MEM_EXTMEM2_HIGH = 0x35;
export const CMOS_CENTURY2 = 0x37;
export const CMOS_BIOS_BOOTFLAG1 = 0x38;
export const CMOS_BIOS_DISKTRANSFLAG = 0x39;
export const CMOS_BIOS_BOOTFLAG2 = 0x3d;
export const CMOS_MEM_HIGHMEM_LOW = 0x5b;
export const CMOS_MEM_HIGHMEM_MID = 0x5c;
export const CMOS_MEM_HIGHMEM_HIGH = 0x5d;
export const CMOS_BIOS_SMP_COUNT = 0x5f;
// see CPU.prototype.fill_cmos
const BOOT_ORDER_CD_FIRST = 0x123;
const BOOT_ORDER_HD_FIRST = 0x312;
const BOOT_ORDER_FD_FIRST = 0x321;
export const BOOT_ORDER_CD_FIRST = 0x123;
export const BOOT_ORDER_HD_FIRST = 0x312;
export const BOOT_ORDER_FD_FIRST = 0x321;
/**
* RTC (real time clock) and CMOS
* @constructor
* @param {CPU} cpu
*/
function RTC(cpu)
export function RTC(cpu)
{
/** @const @type {CPU} */
this.cpu = cpu;
@ -78,8 +87,13 @@ function RTC(cpu)
this.cmos_b = 2;
this.cmos_c = 0;
this.cmos_diag_status = 0;
this.nmi_disabled = 0;
this.update_interrupt = false;
this.update_interrupt_time = 0;
cpu.io.register_write(0x70, this, function(out_byte)
{
this.cmos_index = out_byte & 0x7F;
@ -106,6 +120,9 @@ RTC.prototype.get_state = function()
state[9] = this.cmos_b;
state[10] = this.cmos_c;
state[11] = this.nmi_disabled;
state[12] = this.update_interrupt;
state[13] = this.update_interrupt_time;
state[14] = this.cmos_diag_status;
return state;
};
@ -124,6 +141,9 @@ RTC.prototype.set_state = function(state)
this.cmos_b = state[9];
this.cmos_c = state[10];
this.nmi_disabled = state[11];
this.update_interrupt = state[12] || false;
this.update_interrupt_time = state[13] || 0;
this.cmos_diag_status = state[14] || 0;
};
RTC.prototype.timer = function(time, legacy_mode)
@ -147,6 +167,13 @@ RTC.prototype.timer = function(time, legacy_mode)
this.next_interrupt_alarm = 0;
}
else if(this.update_interrupt && this.update_interrupt_time < time)
{
this.cpu.device_raise_irq(8);
this.cmos_c |= 1 << 4 | 1 << 7;
this.update_interrupt_time = time + 1000; // 1 second
}
let t = 100;
@ -158,6 +185,10 @@ RTC.prototype.timer = function(time, legacy_mode)
{
t = Math.min(t, Math.max(0, this.next_interrupt_alarm - time));
}
if(this.update_interrupt)
{
t = Math.min(t, Math.max(0, this.update_interrupt_time - time));
}
return t;
};
@ -285,7 +316,11 @@ RTC.prototype.cmos_port_read = function()
return c;
case CMOS_STATUS_D:
return 0;
return 1 << 7; // CMOS battery charged
case CMOS_DIAG_STATUS:
dbg_log("cmos diagnostic status read", LOG_RTC);
return this.cmos_diag_status;
case CMOS_CENTURY:
case CMOS_CENTURY2:
@ -310,6 +345,11 @@ RTC.prototype.cmos_port_write = function(data_byte)
break;
case 0xB:
this.cmos_b = data_byte;
if(this.cmos_b & 0x80)
{
// remove update interrupt flag
this.cmos_b &= 0xEF;
}
if(this.cmos_b & 0x40)
{
this.next_interrupt = Date.now();
@ -336,11 +376,19 @@ RTC.prototype.cmos_port_write = function(data_byte)
this.next_interrupt_alarm = +alarm_date;
}
if(this.cmos_b & 0x10) dbg_log("Unimplemented: updated interrupt", LOG_RTC);
if(this.cmos_b & 0x10)
{
dbg_log("update interrupt", LOG_RTC);
this.update_interrupt_time = Date.now();
}
dbg_log("cmos b=" + h(this.cmos_b, 2), LOG_RTC);
break;
case CMOS_DIAG_STATUS:
this.cmos_diag_status = data_byte;
break;
case CMOS_RTC_SECONDS_ALARM:
case CMOS_RTC_MINUTES_ALARM:
case CMOS_RTC_HOURS_ALARM:
@ -351,6 +399,7 @@ RTC.prototype.cmos_port_write = function(data_byte)
dbg_log("cmos write index " + h(this.cmos_index) + ": " + h(data_byte), LOG_RTC);
}
this.update_interrupt = (this.cmos_b & 0x10) === 0x10 && (this.cmos_a & 0xF) > 0;
this.periodic_interrupt = (this.cmos_b & 0x40) === 0x40 && (this.cmos_a & 0xF) > 0;
};

658
src/rust/cpu/apic.rs Normal file
View file

@ -0,0 +1,658 @@
// See Intel's System Programming Guide
use crate::cpu::{cpu::js, global_pointers::acpi_enabled, ioapic};
use std::sync::{Mutex, MutexGuard};
const APIC_LOG_VERBOSE: bool = false;
// should probably be kept in sync with TSC_RATE in cpu.rs
const APIC_TIMER_FREQ: f64 = 1.0 * 1000.0 * 1000.0;
const APIC_TIMER_MODE_MASK: u32 = 3 << 17;
const APIC_TIMER_MODE_ONE_SHOT: u32 = 0;
const APIC_TIMER_MODE_PERIODIC: u32 = 1 << 17;
const _APIC_TIMER_MODE_TSC: u32 = 2 << 17;
const DELIVERY_MODES: [&str; 8] = [
"Fixed (0)",
"Lowest Prio (1)",
"SMI (2)",
"Reserved (3)",
"NMI (4)",
"INIT (5)",
"Reserved (6)",
"ExtINT (7)",
];
const DESTINATION_MODES: [&str; 2] = ["physical", "logical"];
const IOAPIC_CONFIG_MASKED: u32 = 0x10000;
const IOAPIC_DELIVERY_INIT: u8 = 5;
const IOAPIC_DELIVERY_NMI: u8 = 4;
const IOAPIC_DELIVERY_FIXED: u8 = 0;
// keep in sync with cpu.js
#[allow(dead_code)]
const APIC_STRUCT_SIZE: usize = 4 * 46;
// Note: JavaScript (cpu.get_state_apic) depens on this layout
const _: () = assert!(std::mem::offset_of!(Apic, timer_last_tick) == 6 * 4);
const _: () = assert!(std::mem::offset_of!(Apic, lvt_timer) == 8 * 4);
const _: () = assert!(std::mem::offset_of!(Apic, lvt_perf_counter) == 9 * 4);
const _: () = assert!(std::mem::offset_of!(Apic, icr0) == 14 * 4);
const _: () = assert!(std::mem::offset_of!(Apic, icr1) == 15 * 4);
const _: () = assert!(std::mem::offset_of!(Apic, irr) == 16 * 4);
const _: () = assert!(std::mem::offset_of!(Apic, isr) == 24 * 4);
const _: () = assert!(std::mem::offset_of!(Apic, tmr) == 32 * 4);
const _: () = assert!(std::mem::offset_of!(Apic, spurious_vector) == 40 * 4);
const _: () = assert!(std::mem::offset_of!(Apic, lvt_thermal_sensor) == 45 * 4);
const _: () = assert!(std::mem::size_of::<Apic>() == APIC_STRUCT_SIZE);
#[repr(C)]
pub struct Apic {
apic_id: u32,
timer_divider: u32,
timer_divider_shift: u32,
timer_initial_count: u32,
timer_current_count: u32,
timer_last_tick: f64,
lvt_timer: u32,
lvt_perf_counter: u32,
lvt_int0: u32,
lvt_int1: u32,
lvt_error: u32,
tpr: u32,
icr0: u32,
icr1: u32,
irr: [u32; 8],
isr: [u32; 8],
tmr: [u32; 8],
spurious_vector: u32,
destination_format: u32,
local_destination: u32,
error: u32,
read_error: u32,
lvt_thermal_sensor: u32,
}
static APIC: Mutex<Apic> = Mutex::new(Apic {
apic_id: 0,
timer_divider: 0,
timer_divider_shift: 1,
timer_initial_count: 0,
timer_current_count: 0,
timer_last_tick: 0.0,
lvt_timer: IOAPIC_CONFIG_MASKED,
lvt_thermal_sensor: IOAPIC_CONFIG_MASKED,
lvt_perf_counter: IOAPIC_CONFIG_MASKED,
lvt_int0: IOAPIC_CONFIG_MASKED,
lvt_int1: IOAPIC_CONFIG_MASKED,
lvt_error: IOAPIC_CONFIG_MASKED,
tpr: 0,
icr0: 0,
icr1: 0,
irr: [0; 8],
isr: [0; 8],
tmr: [0; 8],
spurious_vector: 0xFE,
destination_format: !0,
local_destination: 0,
error: 0,
read_error: 0,
});
pub fn get_apic() -> MutexGuard<'static, Apic> { APIC.try_lock().unwrap() }
#[no_mangle]
pub fn get_apic_addr() -> u32 { &raw mut *get_apic() as u32 }
pub fn read32(addr: u32) -> u32 {
if unsafe { !*acpi_enabled } {
return 0;
}
read32_internal(&mut get_apic(), addr)
}
fn read32_internal(apic: &mut Apic, addr: u32) -> u32 {
match addr {
0x20 => {
dbg_log!("APIC read id");
apic.apic_id
},
0x30 => {
// version
dbg_log!("APIC read version");
0x50014
},
0x80 => {
if APIC_LOG_VERBOSE {
dbg_log!("APIC read tpr");
}
apic.tpr
},
0xB0 => {
// write-only (written by DSL)
if APIC_LOG_VERBOSE {
dbg_log!("APIC read eoi register");
}
0
},
0xD0 => {
dbg_log!("Read local destination");
apic.local_destination
},
0xE0 => {
dbg_log!("Read destination format");
apic.destination_format
},
0xF0 => apic.spurious_vector,
0x100 | 0x110 | 0x120 | 0x130 | 0x140 | 0x150 | 0x160 | 0x170 => {
let index = ((addr - 0x100) >> 4) as usize;
dbg_log!("Read isr {}: {:08x}", index, apic.isr[index] as u32);
apic.isr[index]
},
0x180 | 0x190 | 0x1A0 | 0x1B0 | 0x1C0 | 0x1D0 | 0x1E0 | 0x1F0 => {
let index = ((addr - 0x180) >> 4) as usize;
dbg_log!("Read tmr {}: {:08x}", index, apic.tmr[index] as u32);
apic.tmr[index]
},
0x200 | 0x210 | 0x220 | 0x230 | 0x240 | 0x250 | 0x260 | 0x270 => {
let index = ((addr - 0x200) >> 4) as usize;
dbg_log!("Read irr {}: {:08x}", index, apic.irr[index] as u32);
apic.irr[index]
},
0x280 => {
dbg_log!("Read error: {:08x}", apic.read_error);
apic.read_error
},
0x300 => {
if APIC_LOG_VERBOSE {
dbg_log!("APIC read icr0");
}
apic.icr0
},
0x310 => {
dbg_log!("APIC read icr1");
apic.icr1
},
0x320 => {
if APIC_LOG_VERBOSE {
dbg_log!("read timer lvt");
}
apic.lvt_timer
},
0x330 => {
dbg_log!("read lvt thermal sensor");
apic.lvt_thermal_sensor
},
0x340 => {
dbg_log!("read lvt perf counter");
apic.lvt_perf_counter
},
0x350 => {
dbg_log!("read lvt int0");
apic.lvt_int0
},
0x360 => {
dbg_log!("read lvt int1");
apic.lvt_int1
},
0x370 => {
dbg_log!("read lvt error");
apic.lvt_error
},
0x3E0 => {
// divider
dbg_log!("read timer divider");
apic.timer_divider
},
0x380 => {
dbg_log!("read timer initial count");
apic.timer_initial_count
},
0x390 => {
let now = unsafe { js::microtick() };
if apic.timer_last_tick > now {
// should only happen after restore_state
dbg_log!("warning: APIC last_tick is in the future, resetting");
apic.timer_last_tick = now;
}
let diff = now - apic.timer_last_tick;
let diff_in_ticks = diff * APIC_TIMER_FREQ / (1 << apic.timer_divider_shift) as f64;
dbg_assert!(diff_in_ticks >= 0.0);
let diff_in_ticks = diff_in_ticks as u64;
let result = if diff_in_ticks < apic.timer_initial_count as u64 {
apic.timer_initial_count - diff_in_ticks as u32
}
else {
let mode = apic.lvt_timer & APIC_TIMER_MODE_MASK;
if mode == APIC_TIMER_MODE_PERIODIC {
apic.timer_initial_count
- (diff_in_ticks % (apic.timer_initial_count as u64 + 1)) as u32
}
else if mode == APIC_TIMER_MODE_ONE_SHOT {
0
}
else {
dbg_assert!(false, "apic unimplemented timer mode: {:x}", mode);
0
}
};
if APIC_LOG_VERBOSE {
dbg_log!("read timer current count: {}", result);
}
result
},
_ => {
dbg_log!("APIC read {:x}", addr);
dbg_assert!(false);
0
},
}
}
pub fn write32(addr: u32, value: u32) {
if unsafe { !*acpi_enabled } {
return;
}
write32_internal(&mut get_apic(), addr, value)
}
fn write32_internal(apic: &mut Apic, addr: u32, value: u32) {
match addr {
0x20 => {
dbg_log!("APIC write id: {:08x}", value >> 8);
apic.apic_id = value;
},
0x30 => {
// version
dbg_log!("APIC write version: {:08x}, ignored", value);
},
0x80 => {
if APIC_LOG_VERBOSE {
dbg_log!("Set tpr: {:02x}", value & 0xFF);
}
apic.tpr = value & 0xFF;
},
0xB0 => {
if let Some(highest_isr) = highest_isr(apic) {
if APIC_LOG_VERBOSE {
dbg_log!("eoi: {:08x} for vector {:x}", value, highest_isr);
}
register_clear_bit(&mut apic.isr, highest_isr);
if register_get_bit(&apic.tmr, highest_isr) {
// Send eoi to all IO APICs
ioapic::remote_eoi(apic, highest_isr);
}
}
else {
dbg_log!("Bad eoi: No isr set");
}
},
0xD0 => {
dbg_log!("Set local destination: {:08x}", value);
apic.local_destination = value & 0xFF000000;
},
0xE0 => {
dbg_log!("Set destination format: {:08x}", value);
apic.destination_format = value | 0xFFFFFF;
},
0xF0 => {
dbg_log!("Set spurious vector: {:08x}", value);
apic.spurious_vector = value;
},
0x280 => {
// updated readable error register with real error
dbg_log!("Write error: {:08x}", value);
apic.read_error = apic.error;
apic.error = 0;
},
0x300 => {
let vector = (value & 0xFF) as u8;
let delivery_mode = ((value >> 8) & 7) as u8;
let destination_mode = ((value >> 11) & 1) as u8;
let is_level = value & ioapic::IOAPIC_CONFIG_TRIGGER_MODE_LEVEL
== ioapic::IOAPIC_CONFIG_TRIGGER_MODE_LEVEL;
let destination_shorthand = (value >> 18) & 3;
let destination = (apic.icr1 >> 24) as u8;
dbg_log!(
"APIC write icr0: {:08x} vector={:02x} destination_mode={} delivery_mode={} destination_shorthand={}",
value,
vector,
DESTINATION_MODES[destination_mode as usize],
DELIVERY_MODES[delivery_mode as usize],
["no", "self", "all with self", "all without self"][destination_shorthand as usize]
);
let mut value = value;
value &= !(1 << 12);
apic.icr0 = value;
if destination_shorthand == 0 {
// no shorthand
route(
apic,
vector,
delivery_mode,
is_level,
destination,
destination_mode,
);
}
else if destination_shorthand == 1 {
// self
deliver(apic, vector, IOAPIC_DELIVERY_FIXED, is_level);
}
else if destination_shorthand == 2 {
// all including self
deliver(apic, vector, delivery_mode, is_level);
}
else if destination_shorthand == 3 {
// all but self
}
else {
dbg_assert!(false);
}
},
0x310 => {
dbg_log!("APIC write icr1: {:08x}", value);
apic.icr1 = value;
},
0x320 => {
if APIC_LOG_VERBOSE {
dbg_log!("timer lvt: {:08x}", value);
}
// TODO: check if unmasking and if this should trigger an interrupt immediately
apic.lvt_timer = value;
},
0x330 => {
dbg_log!("lvt thermal sensor: {:08x}", value);
apic.lvt_thermal_sensor = value;
},
0x340 => {
dbg_log!("lvt perf counter: {:08x}", value);
apic.lvt_perf_counter = value;
},
0x350 => {
dbg_log!("lvt int0: {:08x}", value);
apic.lvt_int0 = value;
},
0x360 => {
dbg_log!("lvt int1: {:08x}", value);
apic.lvt_int1 = value;
},
0x370 => {
dbg_log!("lvt error: {:08x}", value);
apic.lvt_error = value;
},
0x3E0 => {
apic.timer_divider = value;
let divide_shift = (value & 0b11) | ((value & 0b1000) >> 1);
apic.timer_divider_shift = if divide_shift == 0b111 { 0 } else { divide_shift + 1 };
dbg_log!(
"APIC timer divider: {:08x} shift={} tick={:.6}ms",
apic.timer_divider,
apic.timer_divider_shift,
(1 << apic.timer_divider_shift) as f64 / APIC_TIMER_FREQ
);
},
0x380 => {
if APIC_LOG_VERBOSE {
dbg_log!(
"APIC timer initial: {} next_interrupt={:.2}ms",
value,
value as f64 * (1 << apic.timer_divider_shift) as f64 / APIC_TIMER_FREQ,
);
}
apic.timer_initial_count = value;
apic.timer_current_count = value;
apic.timer_last_tick = unsafe { js::microtick() };
},
0x390 => {
dbg_log!("write timer current: {:08x}", value);
dbg_assert!(false, "read-only register");
},
_ => {
dbg_log!("APIC write32 {:x} <- {:08x}", addr, value);
dbg_assert!(false);
},
}
}
#[no_mangle]
pub fn apic_timer(now: f64) -> f64 { timer(&mut get_apic(), now) }
fn timer(apic: &mut Apic, now: f64) -> f64 {
if apic.timer_initial_count == 0 || apic.timer_current_count == 0 {
return 100.0;
}
if apic.timer_last_tick > now {
// should only happen after restore_state
dbg_log!("warning: APIC last_tick is in the future, resetting");
apic.timer_last_tick = now;
}
let diff = now - apic.timer_last_tick;
let diff_in_ticks = diff * APIC_TIMER_FREQ / (1 << apic.timer_divider_shift) as f64;
dbg_assert!(diff_in_ticks >= 0.0);
let diff_in_ticks = diff_in_ticks as u64;
let time_per_interrupt =
apic.timer_initial_count as f64 * (1 << apic.timer_divider_shift) as f64 / APIC_TIMER_FREQ;
if diff_in_ticks >= apic.timer_initial_count as u64 {
let mode = apic.lvt_timer & APIC_TIMER_MODE_MASK;
if mode == APIC_TIMER_MODE_PERIODIC {
if APIC_LOG_VERBOSE {
dbg_log!("APIC timer periodic interrupt");
}
if diff_in_ticks >= 2 * apic.timer_initial_count as u64 {
dbg_log!(
"warning: APIC skipping {} interrupts initial={} ticks={} last_tick={:.1}ms now={:.1}ms d={:.1}ms",
diff_in_ticks / apic.timer_initial_count as u64 - 1,
apic.timer_initial_count,
diff_in_ticks,
apic.timer_last_tick,
now,
diff,
);
apic.timer_last_tick = now;
}
else {
apic.timer_last_tick += time_per_interrupt;
dbg_assert!(apic.timer_last_tick <= now);
}
}
else if mode == APIC_TIMER_MODE_ONE_SHOT {
if APIC_LOG_VERBOSE {
dbg_log!("APIC timer one shot end");
}
apic.timer_current_count = 0;
}
else {
dbg_assert!(false, "apic unimplemented timer mode: {:x}", mode);
}
if apic.lvt_timer & IOAPIC_CONFIG_MASKED == 0 {
deliver(
apic,
(apic.lvt_timer & 0xFF) as u8,
IOAPIC_DELIVERY_FIXED,
false,
);
}
}
apic.timer_last_tick + time_per_interrupt - now
}
pub fn route(
apic: &mut Apic,
vector: u8,
mode: u8,
is_level: bool,
_destination: u8,
_destination_mode: u8,
) {
// TODO
deliver(apic, vector, mode, is_level);
}
fn deliver(apic: &mut Apic, vector: u8, mode: u8, is_level: bool) {
if APIC_LOG_VERBOSE {
dbg_log!("Deliver {:02x} mode={} level={}", vector, mode, is_level);
}
if mode == IOAPIC_DELIVERY_INIT {
// TODO
return;
}
if mode == IOAPIC_DELIVERY_NMI {
// TODO
return;
}
if vector < 0x10 || vector == 0xFF {
dbg_assert!(false, "TODO: Invalid vector: {:x}", vector);
}
if register_get_bit(&apic.irr, vector) {
dbg_log!("Not delivered: irr already set, vector={:02x}", vector);
return;
}
register_set_bit(&mut apic.irr, vector);
if is_level {
register_set_bit(&mut apic.tmr, vector);
}
else {
register_clear_bit(&mut apic.tmr, vector);
}
}
fn highest_irr(apic: &mut Apic) -> Option<u8> {
let highest = register_get_highest_bit(&apic.irr);
if let Some(x) = highest {
dbg_assert!(x >= 0x10);
dbg_assert!(x != 0xFF);
}
highest
}
fn highest_isr(apic: &mut Apic) -> Option<u8> {
let highest = register_get_highest_bit(&apic.isr);
if let Some(x) = highest {
dbg_assert!(x >= 0x10);
dbg_assert!(x != 0xFF);
}
highest
}
pub fn acknowledge_irq() -> Option<u8> { acknowledge_irq_internal(&mut get_apic()) }
fn acknowledge_irq_internal(apic: &mut Apic) -> Option<u8> {
let highest_irr = match highest_irr(apic) {
None => return None,
Some(x) => x,
};
if let Some(highest_isr) = highest_isr(apic) {
if highest_isr >= highest_irr {
if APIC_LOG_VERBOSE {
dbg_log!("Higher isr, isr={:x} irr={:x}", highest_isr, highest_irr);
}
return None;
}
}
if highest_irr & 0xF0 <= apic.tpr as u8 & 0xF0 {
if APIC_LOG_VERBOSE {
dbg_log!(
"Higher tpr, tpr={:x} irr={:x}",
apic.tpr & 0xF0,
highest_irr
);
}
return None;
}
register_clear_bit(&mut apic.irr, highest_irr);
register_set_bit(&mut apic.isr, highest_irr);
if APIC_LOG_VERBOSE {
dbg_log!("Calling vector {:x}", highest_irr);
}
dbg_assert!(acknowledge_irq_internal(apic).is_none());
Some(highest_irr)
}
// functions operating on 256-bit registers (for irr, isr, tmr)
fn register_get_bit(v: &[u32; 8], bit: u8) -> bool { v[(bit >> 5) as usize] & 1 << (bit & 31) != 0 }
fn register_set_bit(v: &mut [u32; 8], bit: u8) { v[(bit >> 5) as usize] |= 1 << (bit & 31); }
fn register_clear_bit(v: &mut [u32; 8], bit: u8) { v[(bit >> 5) as usize] &= !(1 << (bit & 31)); }
fn register_get_highest_bit(v: &[u32; 8]) -> Option<u8> {
dbg_assert!(v.as_ptr().addr() & std::mem::align_of::<u64>() - 1 == 0);
let v: &[u64; 4] = unsafe { std::mem::transmute(v) };
for i in (0..4).rev() {
let word = v[i];
if word != 0 {
return Some(word.ilog2() as u8 | (i as u8) << 6);
}
}
None
}

View file

@ -1,23 +1,5 @@
#![allow(non_upper_case_globals)]
extern "C" {
fn cpu_exception_hook(interrupt: i32) -> bool;
fn call_indirect1(f: i32, x: u16);
pub fn microtick() -> f64;
pub fn run_hardware_timers(acpi_enabled: bool, t: f64) -> f64;
pub fn cpu_event_halt();
pub fn apic_acknowledge_irq() -> i32;
pub fn stop_idling();
pub fn io_port_read8(port: i32) -> i32;
pub fn io_port_read16(port: i32) -> i32;
pub fn io_port_read32(port: i32) -> i32;
pub fn io_port_write8(port: i32, value: i32);
pub fn io_port_write16(port: i32, value: i32);
pub fn io_port_write32(port: i32, value: i32);
}
use crate::config;
use crate::cpu::fpu::fpu_set_tag_word;
use crate::cpu::global_pointers::*;
@ -27,7 +9,8 @@ use crate::cpu::misc_instr::{
push16, push32,
};
use crate::cpu::modrm::{resolve_modrm16, resolve_modrm32};
use crate::cpu::pic;
use crate::cpu::{apic, ioapic, pic};
use crate::dbg::dbg_trace;
use crate::gen;
use crate::jit;
use crate::jit::is_near_end_of_page;
@ -39,11 +22,36 @@ use crate::profiler;
use crate::profiler::stat;
use crate::softfloat;
use crate::state_flags::CachedStateFlags;
use crate::util::dbg_trace;
use std::collections::HashSet;
use std::ptr;
mod wasm {
extern "C" {
pub fn call_indirect1(f: i32, x: u16);
}
}
pub mod js {
extern "C" {
pub fn cpu_exception_hook(interrupt: i32) -> bool;
pub fn microtick() -> f64;
pub fn run_hardware_timers(acpi_enabled: bool, t: f64) -> f64;
pub fn cpu_event_halt();
pub fn stop_idling();
pub fn io_port_read8(port: i32) -> i32;
pub fn io_port_read16(port: i32) -> i32;
pub fn io_port_read32(port: i32) -> i32;
pub fn io_port_write8(port: i32, value: i32);
pub fn io_port_write16(port: i32, value: i32);
pub fn io_port_write32(port: i32, value: i32);
pub fn get_rand_int() -> i32;
}
}
/// The offset for our generated functions in the wasm table. Every index less than this is
/// reserved for rustc's indirect functions
pub const WASM_TABLE_OFFSET: u32 = 1024;
@ -208,7 +216,10 @@ pub const MSR_TEST_CTRL: i32 = 0x33;
pub const MSR_SMI_COUNT: i32 = 0x34;
pub const IA32_FEAT_CTL: i32 = 0x3A;
pub const IA32_SPEC_CTRL: i32 = 0x48;
pub const IA32_BIOS_UPDT_TRIG: i32 = 0x79;
pub const IA32_BIOS_SIGN_ID: i32 = 0x8B;
pub const IA32_PMC0: i32 = 0xC1;
pub const IA32_PMC1: i32 = 0xC2;
pub const MSR_PLATFORM_INFO: i32 = 0xCE;
pub const MSR_TSX_FORCE_ABORT: i32 = 0x10F;
pub const IA32_TSX_CTRL: i32 = 0x122;
@ -218,6 +229,8 @@ pub const IA32_SYSENTER_CS: i32 = 0x174;
pub const IA32_SYSENTER_ESP: i32 = 0x175;
pub const IA32_SYSENTER_EIP: i32 = 0x176;
pub const IA32_MCG_CAP: i32 = 0x179;
pub const IA32_PERFEVTSEL0: i32 = 0x186;
pub const IA32_PERFEVTSEL1: i32 = 0x187;
pub const IA32_MISC_ENABLE: i32 = 0x1A0;
pub const IA32_PAT: i32 = 0x277;
pub const IA32_RTIT_CTL: i32 = 0x570;
@ -230,7 +243,10 @@ pub const IA32_APIC_BASE_BSP: i32 = 1 << 8;
pub const IA32_APIC_BASE_EXTD: i32 = 1 << 10;
pub const IA32_APIC_BASE_EN: i32 = 1 << 11;
pub const APIC_ADDRESS: i32 = 0xFEE00000u32 as i32;
pub const IOAPIC_MEM_ADDRESS: u32 = 0xFEC00000;
pub const IOAPIC_MEM_SIZE: u32 = 32;
pub const APIC_MEM_ADDRESS: u32 = 0xFEE00000;
pub const APIC_MEM_SIZE: u32 = 0x1000;
pub const MXCSR_MASK: i32 = 0xffff;
pub const MXCSR_FZ: i32 = 1 << 15;
@ -270,6 +286,8 @@ pub const CHECK_TLB_INVARIANTS: bool = false;
pub const DEBUG: bool = cfg!(debug_assertions);
pub const LOOP_COUNTER: i32 = 100_003;
// should probably be kept in sync with APIC_TIMER_FREQ in apic.js
pub const TSC_RATE: f64 = 1_000_000.0;
pub static mut cpuid_level: u32 = 0x16;
@ -711,13 +729,13 @@ pub unsafe fn iret(is_16: bool) {
let executable = access & 8 == 8;
let conforming = access & 4 == 4;
if dpl < *cpl && !(executable && conforming) {
dbg_log!(
"set segment to null sreg={} dpl={} executable={} conforming={}",
reg,
dpl,
executable,
conforming
);
//dbg_log!(
// "set segment to null sreg={} dpl={} executable={} conforming={}",
// reg,
// dpl,
// executable,
// conforming
//);
*segment_is_null.offset(reg as isize) = true;
*sreg.offset(reg as isize) = 0;
}
@ -893,13 +911,19 @@ pub unsafe fn call_interrupt_vector(
let ss_segment_descriptor =
match return_on_pagefault!(lookup_segment_selector(ss_segment_selector)) {
Ok((desc, _)) => desc,
Err(_) => {
Err(
SelectorNullOrInvalid::IsNull | SelectorNullOrInvalid::OutsideOfTableLimit,
) => {
panic!("Unimplemented: #TS handler");
},
};
dbg_assert!(!ss_segment_descriptor.is_dc(), "TODO: Handle direction bit");
dbg_assert!(new_esp as u32 <= ss_segment_descriptor.effective_limit());
if ss_segment_descriptor.is_dc() {
dbg_assert!(new_esp as u32 > ss_segment_descriptor.effective_limit());
}
else {
dbg_assert!(new_esp as u32 - 1 <= ss_segment_descriptor.effective_limit());
}
dbg_assert!(!ss_segment_descriptor.is_system() && ss_segment_descriptor.is_writable());
if ss_segment_selector.rpl() != cs_segment_descriptor.dpl() {
@ -1158,12 +1182,18 @@ pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool
return;
},
Err(SelectorNullOrInvalid::OutsideOfTableLimit) => {
dbg_log!("#gp invalid cs: {:x}", selector);
trigger_gp(selector & !3);
dbg_log!("#gp invalid cs: {:x}", cs_selector);
trigger_gp(cs_selector & !3);
return;
},
};
if cs_info.is_system() {
dbg_log!("#gp non-code cs: {:x}", cs_selector);
trigger_gp(cs_selector & !3);
return;
}
if !cs_info.is_executable() {
dbg_log!("#gp non-executable cs: {:x}", cs_selector);
trigger_gp(cs_selector & !3);
@ -1202,8 +1232,12 @@ pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool
},
};
dbg_assert!(!ss_info.is_dc(), "TODO: Handle direction bit");
dbg_assert!(new_esp as u32 <= ss_info.effective_limit());
if ss_info.is_dc() {
dbg_assert!(new_esp as u32 > ss_info.effective_limit());
}
else {
dbg_assert!(new_esp as u32 - 1 <= ss_info.effective_limit());
}
dbg_assert!(!ss_info.is_system() && ss_info.is_writable());
if ss_selector.rpl() != cs_info.dpl()
@ -1225,16 +1259,18 @@ pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool
if is_16 { 4 + 2 * parameter_count } else { 8 + 4 * parameter_count };
}
if ss_info.is_32() {
return_on_pagefault!(writable_or_pagefault(
return_on_pagefault!(writable_or_pagefault_cpl(
cs_info.dpl(),
ss_info.base() + new_esp - stack_space,
stack_space
)); // , cs_info.dpl
));
}
else {
return_on_pagefault!(writable_or_pagefault(
return_on_pagefault!(writable_or_pagefault_cpl(
cs_info.dpl(),
ss_info.base() + (new_esp - stack_space & 0xFFFF),
stack_space
)); // , cs_info.dpl
));
}
let old_esp = read_reg32(ESP);
@ -1248,6 +1284,7 @@ pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool
update_cs_size(cs_info.is_32());
dbg_assert!(new_ss & 3 == cs_info.dpl() as i32);
// XXX: Should be checked before side effects
if !switch_seg(SS, new_ss) {
dbg_assert!(false);
@ -1269,7 +1306,6 @@ pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool
if is_call {
if is_16 {
for i in (0..parameter_count).rev() {
//for(let i = parameter_count - 1; i >= 0; i--)
let parameter = safe_read16(old_stack_pointer + 2 * i).unwrap();
push16(parameter).unwrap();
}
@ -1280,7 +1316,6 @@ pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool
}
else {
for i in (0..parameter_count).rev() {
//for(let i = parameter_count - 1; i >= 0; i--)
let parameter = safe_read32s(old_stack_pointer + 4 * i).unwrap();
push32(parameter).unwrap();
}
@ -1299,7 +1334,6 @@ pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool
cs_info.dpl(),
cs_info.is_dc()
);
// ok
if is_call {
if is_16 {
@ -1315,6 +1349,8 @@ pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool
push32(get_real_eip()).unwrap();
}
}
dbg_assert!(*cpl == cs_info.dpl());
}
// Note: eip from call is ignored
@ -1344,10 +1380,11 @@ pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool
update_state_flags();
}
else if info.system_type() == 1 || info.system_type() == 9 {
dbg_assert!(false, "TODO: far call task gate");
}
else {
dbg_assert!(false);
//let types = { 9: "Available 386 TSS", 0xb: "Busy 386 TSS", 4: "286 Call Gate", 0xc: "386 Call Gate" };
//throw debug.unimpl("load system segment descriptor, type = " + (info.access & 15) + " (" + types[info.access & 15] + ")");
dbg_assert!(false, "TODO: #gp invalid system type");
}
}
else {
@ -1806,10 +1843,14 @@ pub unsafe fn readable_or_pagefault(addr: i32, size: i32) -> OrPageFault<()> {
}
pub unsafe fn writable_or_pagefault(addr: i32, size: i32) -> OrPageFault<()> {
writable_or_pagefault_cpl(*cpl, addr, size)
}
pub unsafe fn writable_or_pagefault_cpl(other_cpl: u8, addr: i32, size: i32) -> OrPageFault<()> {
dbg_assert!(size < 0x1000);
dbg_assert!(size > 0);
let user = *cpl == 3;
let user = other_cpl == 3;
translate_address(addr, true, user, false, true)?;
let end = addr + size - 1 & !0xFFF;
@ -2196,7 +2237,7 @@ pub unsafe fn trigger_fault_end_jit() {
#[allow(static_mut_refs)]
let (code, error_code) = jit_fault.take().unwrap();
if DEBUG {
if cpu_exception_hook(code) {
if js::cpu_exception_hook(code) {
return;
}
}
@ -2920,7 +2961,7 @@ pub unsafe fn cycle_internal() {
{
in_jit = true;
}
call_indirect1(
wasm::call_indirect1(
wasm_table_index as i32 + WASM_TABLE_OFFSET as i32,
initial_state,
);
@ -3105,11 +3146,11 @@ pub unsafe fn segment_prefix_op(seg: i32) {
pub unsafe fn main_loop() -> f64 {
profiler::stat_increment(stat::MAIN_LOOP);
let start = microtick();
let start = js::microtick();
if *in_hlt {
if *flags & FLAG_INTERRUPT != 0 {
let t = run_hardware_timers(*acpi_enabled, start);
let t = js::run_hardware_timers(*acpi_enabled, start);
handle_irqs();
if *in_hlt {
profiler::stat_increment(stat::MAIN_LOOP_IDLE);
@ -3125,8 +3166,8 @@ pub unsafe fn main_loop() -> f64 {
loop {
do_many_cycles_native();
let now = microtick();
let t = run_hardware_timers(*acpi_enabled, now);
let now = js::microtick();
let t = js::run_hardware_timers(*acpi_enabled, now);
handle_irqs();
if *in_hlt {
return t;
@ -3155,7 +3196,7 @@ pub unsafe fn trigger_de() {
dbg_log!("#de");
*instruction_pointer = *previous_ip;
if DEBUG {
if cpu_exception_hook(CPU_EXCEPTION_DE) {
if js::cpu_exception_hook(CPU_EXCEPTION_DE) {
return;
}
}
@ -3168,7 +3209,7 @@ pub unsafe fn trigger_ud() {
dbg_trace();
*instruction_pointer = *previous_ip;
if DEBUG {
if cpu_exception_hook(CPU_EXCEPTION_UD) {
if js::cpu_exception_hook(CPU_EXCEPTION_UD) {
return;
}
}
@ -3181,7 +3222,7 @@ pub unsafe fn trigger_nm() {
dbg_trace();
*instruction_pointer = *previous_ip;
if DEBUG {
if cpu_exception_hook(CPU_EXCEPTION_NM) {
if js::cpu_exception_hook(CPU_EXCEPTION_NM) {
return;
}
}
@ -3193,7 +3234,7 @@ pub unsafe fn trigger_gp(code: i32) {
dbg_log!("#gp");
*instruction_pointer = *previous_ip;
if DEBUG {
if cpu_exception_hook(CPU_EXCEPTION_GP) {
if js::cpu_exception_hook(CPU_EXCEPTION_GP) {
return;
}
}
@ -3285,7 +3326,7 @@ pub unsafe fn safe_read32s(addr: i32) -> OrPageFault<i32> {
}
pub unsafe fn safe_read_f32(addr: i32) -> OrPageFault<f32> {
Ok(std::mem::transmute(safe_read32s(addr)?))
Ok(f32::from_bits(i32::cast_unsigned(safe_read32s(addr)?)))
}
pub unsafe fn safe_read64s(addr: i32) -> OrPageFault<u64> {
@ -3539,10 +3580,11 @@ pub unsafe fn safe_write_slow_jit(
if Page::page_of(*instruction_pointer as u32) == Page::page_of(addr as u32) {
// XXX: Check based on virtual address
dbg_log!(
"SMC: bits={} eip={:x} writeaddr={:x}",
"SMC: bits={} eip={:x} writeaddr={:x} value={:x}",
bitsize,
(*instruction_pointer & !0xFFF | eip_offset_in_page) as u32,
addr as u32
addr as u32,
value_low,
);
}
let crosses_page = (addr & 0xFFF) + bitsize / 8 > 0x1000;
@ -4062,7 +4104,7 @@ pub unsafe fn set_tsc(low: u32, high: u32) {
#[no_mangle]
pub unsafe fn read_tsc() -> u64 {
let value = (microtick() * TSC_RATE) as u64 - tsc_offset;
let value = (js::microtick() * TSC_RATE) as u64 - tsc_offset;
if !TSC_ENABLE_IMPRECISE_BROWSER_WORKAROUND {
return value;
@ -4247,7 +4289,7 @@ pub unsafe fn trigger_np(code: i32) {
dbg_log!("#np");
*instruction_pointer = *previous_ip;
if DEBUG {
if cpu_exception_hook(CPU_EXCEPTION_NP) {
if js::cpu_exception_hook(CPU_EXCEPTION_NP) {
return;
}
}
@ -4259,7 +4301,7 @@ pub unsafe fn trigger_ss(code: i32) {
dbg_log!("#ss");
*instruction_pointer = *previous_ip;
if DEBUG {
if cpu_exception_hook(CPU_EXCEPTION_SS) {
if js::cpu_exception_hook(CPU_EXCEPTION_SS) {
return;
}
}
@ -4270,17 +4312,14 @@ pub unsafe fn trigger_ss(code: i32) {
pub unsafe fn store_current_tsc() { *current_tsc = read_tsc(); }
#[no_mangle]
pub unsafe fn handle_irqs() { handle_irqs_internal(&mut pic::get_pic()) }
pub unsafe fn handle_irqs_internal(pic: &mut pic::Pic) {
pub unsafe fn handle_irqs() {
if *flags & FLAG_INTERRUPT != 0 {
if let Some(irq) = pic::pic_acknowledge_irq(pic) {
if let Some(irq) = pic::pic_acknowledge_irq() {
pic_call_irq(irq)
}
else if *acpi_enabled {
let irq = apic_acknowledge_irq();
if irq >= 0 {
pic_call_irq(irq as u8)
if let Some(irq) = apic::acknowledge_irq() {
pic_call_irq(irq)
}
}
}
@ -4289,12 +4328,68 @@ pub unsafe fn handle_irqs_internal(pic: &mut pic::Pic) {
unsafe fn pic_call_irq(interrupt_nr: u8) {
*previous_ip = *instruction_pointer; // XXX: What if called after instruction (port IO)
if *in_hlt {
stop_idling();
js::stop_idling();
*in_hlt = false;
}
call_interrupt_vector(interrupt_nr as i32, false, None);
}
#[no_mangle]
unsafe fn device_raise_irq(i: u8) {
pic::set_irq(i);
if *acpi_enabled {
ioapic::set_irq(i);
}
handle_irqs()
}
#[no_mangle]
unsafe fn device_lower_irq(i: u8) {
pic::clear_irq(i);
if *acpi_enabled {
ioapic::clear_irq(i);
}
handle_irqs()
}
pub fn io_port_read8(port: i32) -> i32 {
unsafe {
match port {
0x20 => pic::port20_read() as i32,
0x21 => pic::port21_read() as i32,
0xA0 => pic::portA0_read() as i32,
0xA1 => pic::portA1_read() as i32,
0x4D0 => pic::port4D0_read() as i32,
0x4D1 => pic::port4D1_read() as i32,
_ => js::io_port_read8(port),
}
}
}
pub fn io_port_read16(port: i32) -> i32 { unsafe { js::io_port_read16(port) } }
pub fn io_port_read32(port: i32) -> i32 { unsafe { js::io_port_read32(port) } }
pub fn io_port_write8(port: i32, value: i32) {
unsafe {
match port {
0x20 | 0x21 | 0xA0 | 0xA1 | 0x4D0 | 0x4D1 => {
match port {
0x20 => pic::port20_write(value as u8),
0x21 => pic::port21_write(value as u8),
0xA0 => pic::portA0_write(value as u8),
0xA1 => pic::portA1_write(value as u8),
0x4D0 => pic::port4D0_write(value as u8),
0x4D1 => pic::port4D1_write(value as u8),
_ => dbg_assert!(false),
};
handle_irqs()
},
_ => js::io_port_write8(port, value),
}
}
}
pub fn io_port_write16(port: i32, value: i32) { unsafe { js::io_port_write16(port, value) } }
pub fn io_port_write32(port: i32, value: i32) { unsafe { js::io_port_write32(port, value) } }
#[no_mangle]
#[cfg(debug_assertions)]
pub unsafe fn check_page_switch(block_addr: u32, next_block_addr: u32) {

View file

@ -77,10 +77,11 @@ pub unsafe fn fpu_get_sti(mut i: i32) -> F80 {
};
}
// only used for debugging
#[no_mangle]
pub unsafe fn fpu_get_sti_f64(mut i: i32) -> f64 {
i = i + *fpu_stack_ptr as i32 & 7;
std::mem::transmute((*fpu_st.offset(i as isize)).to_f64())
f64::from_bits((*fpu_st.offset(i as isize)).to_f64())
}
#[no_mangle]
@ -496,7 +497,7 @@ pub unsafe fn fpu_fprem(ieee: bool) {
let exp0 = st0.log2();
let exp1 = st1.log2();
let d = (exp0 - exp1).abs();
if !intel_compatibility || d < F80::of_f64(std::mem::transmute(64.0)) {
if !intel_compatibility || d < F80::of_f64(f64::to_bits(64.0)) {
let fprem_quotient =
(if ieee { (st0 / st1).round() } else { (st0 / st1).trunc() }).to_i32();
fpu_write_st(*fpu_stack_ptr as i32, st0 % st1);
@ -513,7 +514,7 @@ pub unsafe fn fpu_fprem(ieee: bool) {
*fpu_status_word &= !FPU_C2;
}
else {
let n = F80::of_f64(std::mem::transmute(32.0));
let n = F80::of_f64(f64::to_bits(32.0));
let fprem_quotient =
(if ieee { (st0 / st1).round() } else { (st0 / st1).trunc() } / (d - n).two_pow());
fpu_write_st(

View file

@ -1,6 +1,7 @@
#![allow(non_snake_case)]
use crate::cpu::arith::*;
use crate::cpu::cpu::js;
use crate::cpu::cpu::*;
use crate::cpu::fpu::*;
use crate::cpu::global_pointers::*;
@ -575,7 +576,6 @@ pub unsafe fn instr_66() {
}
pub unsafe fn instr_67() {
// Address-size override prefix
dbg_assert!(is_asize_32() == *is_32);
*prefixes |= prefix::PREFIX_MASK_ADDRSIZE;
run_prefix_instruction();
*prefixes = 0;
@ -995,7 +995,6 @@ pub unsafe fn instr32_99() { write_reg32(EDX, read_reg32(EAX) >> 31); }
pub unsafe fn instr16_9A(new_ip: i32, new_cs: i32) {
// callf
far_jump(new_ip, new_cs, true, false);
dbg_assert!(*is_32 || get_real_eip() < 0x10000);
}
#[no_mangle]
pub unsafe fn instr32_9A(new_ip: i32, new_cs: i32) {
@ -1005,7 +1004,6 @@ pub unsafe fn instr32_9A(new_ip: i32, new_cs: i32) {
}
}
far_jump(new_ip, new_cs, true, true);
dbg_assert!(*is_32 || get_real_eip() < 0x10000);
}
#[no_mangle]
pub unsafe fn instr_9B() {
@ -2057,13 +2055,11 @@ pub unsafe fn instr32_E9(imm32s: i32) {
pub unsafe fn instr16_EA(new_ip: i32, cs: i32) {
// jmpf
far_jump(new_ip, cs, false, false);
dbg_assert!(*is_32 || get_real_eip() < 0x10000);
}
#[no_mangle]
pub unsafe fn instr32_EA(new_ip: i32, cs: i32) {
// jmpf
far_jump(new_ip, cs, false, true);
dbg_assert!(*is_32 || get_real_eip() < 0x10000);
}
pub unsafe fn instr16_EB(imm8: i32) {
@ -2167,12 +2163,12 @@ pub unsafe fn instr_F4() {
// due it will immediately call call_interrupt_vector and continue
// execution without an unnecessary cycle through do_run
if *flags & FLAG_INTERRUPT != 0 {
run_hardware_timers(*acpi_enabled, microtick());
js::run_hardware_timers(*acpi_enabled, js::microtick());
handle_irqs();
}
else {
// execution can never resume (until NMIs are supported)
cpu_event_halt();
js::cpu_event_halt();
}
}
#[no_mangle]
@ -2391,7 +2387,6 @@ pub unsafe fn instr16_FF_3_mem(addr: i32) {
let new_ip = return_on_pagefault!(safe_read16(addr));
let new_cs = return_on_pagefault!(safe_read16(addr + 2));
far_jump(new_ip, new_cs, true, false);
dbg_assert!(*is_32 || get_real_eip() < 0x10000);
}
pub unsafe fn instr16_FF_4_helper(data: i32) {
// jmp near
@ -2414,7 +2409,6 @@ pub unsafe fn instr16_FF_5_mem(addr: i32) {
let new_ip = return_on_pagefault!(safe_read16(addr));
let new_cs = return_on_pagefault!(safe_read16(addr + 2));
far_jump(new_ip, new_cs, false, false);
dbg_assert!(*is_32 || get_real_eip() < 0x10000);
}
pub unsafe fn instr16_FF_6_mem(addr: i32) {
return_on_pagefault!(push16(return_on_pagefault!(safe_read16(addr))));
@ -2455,7 +2449,6 @@ pub unsafe fn instr32_FF_3_mem(addr: i32) {
}
}
far_jump(new_ip, new_cs, true, true);
dbg_assert!(*is_32 || new_ip < 0x10000);
}
pub unsafe fn instr32_FF_4_helper(data: i32) {
@ -2484,7 +2477,6 @@ pub unsafe fn instr32_FF_5_mem(addr: i32) {
}
}
far_jump(new_ip, new_cs, false, true);
dbg_assert!(*is_32 || new_ip < 0x10000);
}
pub unsafe fn instr32_FF_6_mem(addr: i32) {
return_on_pagefault!(push32(return_on_pagefault!(safe_read32s(addr))));

View file

@ -1,9 +1,5 @@
#![allow(non_snake_case)]
extern "C" {
fn get_rand_int() -> i32;
}
unsafe fn undefined_instruction() {
dbg_assert!(false, "Undefined instructions");
trigger_ud()
@ -275,11 +271,11 @@ pub unsafe fn instr16_0F01_4_reg(r: i32) {
pub unsafe fn instr32_0F01_4_reg(r: i32) { write_reg32(r, *cr); }
#[no_mangle]
pub unsafe fn instr16_0F01_4_mem(addr: i32) {
return_on_pagefault!(safe_write16(addr, *cr));
return_on_pagefault!(safe_write16(addr, *cr & 0xFFFF));
}
#[no_mangle]
pub unsafe fn instr32_0F01_4_mem(addr: i32) {
return_on_pagefault!(safe_write16(addr, *cr));
return_on_pagefault!(safe_write16(addr, *cr & 0xFFFF));
}
#[no_mangle]
@ -1207,13 +1203,14 @@ pub unsafe fn instr_0F30() {
);
let address = low & !(IA32_APIC_BASE_BSP | IA32_APIC_BASE_EXTD | IA32_APIC_BASE_EN);
dbg_assert!(
address == APIC_ADDRESS,
address == APIC_MEM_ADDRESS as i32,
"Changing APIC address not supported"
);
dbg_assert!(low & IA32_APIC_BASE_EXTD == 0, "x2apic not supported");
*apic_enabled = low & IA32_APIC_BASE_EN == IA32_APIC_BASE_EN
},
IA32_TIME_STAMP_COUNTER => set_tsc(low as u32, high as u32),
IA32_BIOS_UPDT_TRIG => {}, // windows xp
IA32_BIOS_SIGN_ID => {},
MISC_FEATURE_ENABLES => {
// Linux 4, see: https://patchwork.kernel.org/patch/9528279/
@ -1226,6 +1223,8 @@ pub unsafe fn instr_0F30() {
// Only used in 64 bit mode (by SWAPGS), but set by kvm-unit-test
dbg_log!("GS Base written");
},
IA32_PERFEVTSEL0 | IA32_PERFEVTSEL1 => {}, // linux/9legacy
IA32_PMC0 | IA32_PMC1 => {}, // linux
IA32_PAT => {},
IA32_SPEC_CTRL => {}, // linux 5.19
IA32_TSX_CTRL => {}, // linux 5.19
@ -1283,7 +1282,7 @@ pub unsafe fn instr_0F32() {
IA32_PLATFORM_ID => {},
IA32_APIC_BASE => {
if *acpi_enabled {
low = APIC_ADDRESS;
low = APIC_MEM_ADDRESS as i32;
if *apic_enabled {
low |= IA32_APIC_BASE_EN
}
@ -1296,13 +1295,11 @@ pub unsafe fn instr_0F32() {
// Enable Misc. Processor Features
low = 1 << 0; // fast string
},
IA32_RTIT_CTL => {
// linux4
},
IA32_RTIT_CTL => {}, // linux4
MSR_SMI_COUNT => {},
IA32_MCG_CAP => {
// netbsd
},
IA32_MCG_CAP => {}, // netbsd
IA32_PERFEVTSEL0 | IA32_PERFEVTSEL1 => {}, // linux/9legacy
IA32_PMC0 | IA32_PMC1 => {}, // linux
IA32_PAT => {},
MSR_PKG_C2_RESIDENCY => {},
IA32_SPEC_CTRL => {}, // linux 5.19
@ -2189,14 +2186,14 @@ pub unsafe fn instr_F30F5F_mem(addr: i32, r: i32) {
#[no_mangle]
pub unsafe fn instr_0F60(source: i32, r: i32) {
// punpcklbw mm, mm/m32
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 4] = std::mem::transmute(source);
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 4] = i32::to_le_bytes(source);
let mut result = [0; 8];
for i in 0..4 {
result[2 * i + 0] = destination[i];
result[2 * i + 1] = source[i];
}
write_mmx_reg64(r, std::mem::transmute(result));
write_mmx_reg64(r, u64::from_le_bytes(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0F60_reg(r1: i32, r2: i32) { instr_0F60(read_mmx32s(r1), r2); }
@ -2207,7 +2204,7 @@ pub unsafe fn instr_0F60_mem(addr: i32, r: i32) {
pub unsafe fn instr_660F60(source: reg128, r: i32) {
// punpcklbw xmm, xmm/m128
// XXX: Aligned access or #gp
let destination: [u8; 8] = std::mem::transmute(read_xmm64s(r));
let destination: [u8; 8] = u64::to_le_bytes(read_xmm64s(r));
let mut result = reg128 { i8: [0; 16] };
for i in 0..8 {
result.u8[2 * i + 0] = destination[i];
@ -2292,7 +2289,7 @@ pub unsafe fn instr_0F63(source: u64, r: i32) {
result[i + 0] = saturate_sw_to_sb(destination[i] as i32);
result[i + 4] = saturate_sw_to_sb(source[i] as i32);
}
write_mmx_reg64(r, std::mem::transmute(result));
write_mmx_reg64(r, u64::from_le_bytes(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0F63_reg(r1: i32, r2: i32) { instr_0F63(read_mmx64s(r1), r2); }
@ -2324,7 +2321,7 @@ pub unsafe fn instr_0F64(source: u64, r: i32) {
for i in 0..8 {
result[i] = if destination[i] > source[i] { 255 } else { 0 };
}
write_mmx_reg64(r, std::mem::transmute(result));
write_mmx_reg64(r, u64::from_le_bytes(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0F64_reg(r1: i32, r2: i32) { instr_0F64(read_mmx64s(r1), r2); }
@ -2420,7 +2417,7 @@ pub unsafe fn instr_0F67(source: u64, r: i32) {
result[i + 0] = saturate_sw_to_ub(destination[i]);
result[i + 4] = saturate_sw_to_ub(source[i]);
}
write_mmx_reg64(r, std::mem::transmute(result));
write_mmx_reg64(r, u64::from_le_bytes(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0F67_reg(r1: i32, r2: i32) { instr_0F67(read_mmx64s(r1), r2); }
@ -2446,14 +2443,14 @@ pub unsafe fn instr_660F67_mem(addr: i32, r: i32) {
#[no_mangle]
pub unsafe fn instr_0F68(source: u64, r: i32) {
// punpckhbw mm, mm/m64
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(source);
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let mut result: [u8; 8] = [0; 8];
for i in 0..4 {
result[2 * i + 0] = destination[i + 4];
result[2 * i + 1] = source[i + 4];
}
write_mmx_reg64(r, std::mem::transmute(result));
write_mmx_reg64(r, u64::from_le_bytes(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0F68_reg(r1: i32, r2: i32) { instr_0F68(read_mmx64s(r1), r2); }
@ -2868,13 +2865,13 @@ pub unsafe fn instr_660F73_7_reg(r: i32, imm8: i32) {
#[no_mangle]
pub unsafe fn instr_0F74(source: u64, r: i32) {
// pcmpeqb mm, mm/m64
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(source);
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let mut result: [u8; 8] = [0; 8];
for i in 0..8 {
result[i] = if destination[i] == source[i] { 255 } else { 0 };
}
write_mmx_reg64(r, std::mem::transmute(result));
write_mmx_reg64(r, u64::from_le_bytes(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0F74_reg(r1: i32, r2: i32) { instr_0F74(read_mmx64s(r1), r2); }
@ -3241,8 +3238,7 @@ pub unsafe fn instr_0FA2() {
},
1 => {
// pentium
eax = 3 | 6 << 4 | 15 << 8;
eax = 3 | 7 << 4 | 6 << 8; // pentium3
ebx = 1 << 16 | 8 << 8; // cpu count, clflush size
ecx = 1 << 0 | 1 << 23 | 1 << 30; // sse3, popcnt, rdrand
let vme = 0 << 1;
@ -3820,7 +3816,7 @@ pub unsafe fn instr_F20FC2_mem(addr: i32, r: i32, imm: i32) {
pub unsafe fn instr_F30FC2(source: i32, r: i32, imm8: i32) {
// cmpss xmm, xmm/m32
let destination = read_xmm_f32(r);
let source: f32 = std::mem::transmute(source);
let source: f32 = f32::from_bits(i32::cast_unsigned(source));
let result = if sse_comparison(imm8, destination as f64, source as f64) { -1 } else { 0 };
write_xmm32(r, result);
}
@ -3944,7 +3940,7 @@ pub unsafe fn instr32_0FC7_1_mem(addr: i32) { instr16_0FC7_1_mem(addr) }
#[no_mangle]
pub unsafe fn instr16_0FC7_6_reg(r: i32) {
// rdrand
let rand = get_rand_int();
let rand = js::get_rand_int();
write_reg16(r, rand);
*flags &= !FLAGS_ALL;
*flags |= 1;
@ -3953,7 +3949,7 @@ pub unsafe fn instr16_0FC7_6_reg(r: i32) {
#[no_mangle]
pub unsafe fn instr32_0FC7_6_reg(r: i32) {
// rdrand
let rand = get_rand_int();
let rand = js::get_rand_int();
write_reg32(r, rand);
*flags &= !FLAGS_ALL;
*flags |= 1;
@ -4131,7 +4127,7 @@ pub unsafe fn instr_0FD7_mem(_addr: i32, _r: i32) { trigger_ud(); }
#[no_mangle]
pub unsafe fn instr_0FD7(r1: i32) -> i32 {
// pmovmskb r, mm
let x: [u8; 8] = std::mem::transmute(read_mmx64s(r1));
let x: [u8; 8] = u64::to_le_bytes(read_mmx64s(r1));
let mut result = 0;
for i in 0..8 {
result |= x[i] as i32 >> 7 << i
@ -4155,13 +4151,13 @@ pub unsafe fn instr_660FD7_reg(r1: i32, r2: i32) { write_reg32(r2, instr_660FD7(
#[no_mangle]
pub unsafe fn instr_0FD8(source: u64, r: i32) {
// psubusb mm, mm/m64
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(source);
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let mut result = [0; 8];
for i in 0..8 {
result[i] = saturate_sd_to_ub(destination[i] as i32 - source[i] as i32) as u8;
}
write_mmx_reg64(r, std::mem::transmute(result));
write_mmx_reg64(r, u64::from_le_bytes(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0FD8_reg(r1: i32, r2: i32) { instr_0FD8(read_mmx64s(r1), r2); }
@ -4215,13 +4211,13 @@ pub unsafe fn instr_660FD9_mem(addr: i32, r: i32) {
#[no_mangle]
pub unsafe fn instr_0FDA(source: u64, r: i32) {
// pminub mm, mm/m64
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(source);
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let mut result = [0; 8];
for i in 0..8 {
result[i] = u8::min(source[i], destination[i])
}
write_mmx_reg64(r, std::mem::transmute(result));
write_mmx_reg64(r, u64::from_le_bytes(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0FDA_reg(r1: i32, r2: i32) { instr_0FDA(read_mmx64s(r1), r2); }
@ -4267,13 +4263,13 @@ pub unsafe fn instr_660FDB_mem(addr: i32, r: i32) {
#[no_mangle]
pub unsafe fn instr_0FDC(source: u64, r: i32) {
// paddusb mm, mm/m64
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(source);
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let mut result = [0; 8];
for i in 0..8 {
result[i] = saturate_ud_to_ub(destination[i] as u32 + source[i] as u32);
}
write_mmx_reg64(r, std::mem::transmute(result));
write_mmx_reg64(r, u64::from_le_bytes(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0FDC_reg(r1: i32, r2: i32) { instr_0FDC(read_mmx64s(r1), r2); }
@ -4329,13 +4325,13 @@ pub unsafe fn instr_660FDD_mem(addr: i32, r: i32) {
#[no_mangle]
pub unsafe fn instr_0FDE(source: u64, r: i32) {
// pmaxub mm, mm/m64
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(source);
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let mut result = [0; 8];
for i in 0..8 {
result[i] = u8::max(source[i], destination[i])
}
write_mmx_reg64(r, std::mem::transmute(result));
write_mmx_reg64(r, u64::from_le_bytes(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0FDE_reg(r1: i32, r2: i32) { instr_0FDE(read_mmx64s(r1), r2); }
@ -4381,13 +4377,13 @@ pub unsafe fn instr_660FDF_mem(addr: i32, r: i32) {
#[no_mangle]
pub unsafe fn instr_0FE0(source: u64, r: i32) {
// pavgb mm, mm/m64
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(source);
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let mut result = [0; 8];
for i in 0..8 {
result[i] = (destination[i] as i32 + source[i] as i32 + 1 >> 1) as u8;
}
write_mmx_reg64(r, std::mem::transmute(result));
write_mmx_reg64(r, u64::from_le_bytes(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0FE0_reg(r1: i32, r2: i32) { instr_0FE0(read_mmx64s(r1), r2); }
@ -4960,8 +4956,8 @@ pub unsafe fn instr_660FF5_mem(addr: i32, r: i32) {
#[no_mangle]
pub unsafe fn instr_0FF6(source: u64, r: i32) {
// psadbw mm, mm/m64
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(source);
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let mut sum = 0;
for i in 0..8 {
sum += (destination[i] as i32 - source[i] as i32).abs() as u64;
@ -4995,8 +4991,8 @@ pub unsafe fn instr_0FF7_mem(_addr: i32, _r: i32) { trigger_ud(); }
#[no_mangle]
pub unsafe fn maskmovq(r1: i32, r2: i32, addr: i32) {
// maskmovq mm, mm
let source: [u8; 8] = std::mem::transmute(read_mmx64s(r2));
let mask: [u8; 8] = std::mem::transmute(read_mmx64s(r1));
let source: [u8; 8] = u64::to_le_bytes(read_mmx64s(r2));
let mask: [u8; 8] = u64::to_le_bytes(read_mmx64s(r1));
match writable_or_pagefault(addr, 8) {
Ok(()) => *page_fault = false,
Err(()) => {
@ -5048,13 +5044,13 @@ pub unsafe fn instr_660FF7_reg(r1: i32, r2: i32) {
#[no_mangle]
pub unsafe fn instr_0FF8(source: u64, r: i32) {
// psubb mm, mm/m64
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(source);
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let mut result = [0; 8];
for i in 0..8 {
result[i] = destination[i] - source[i];
}
write_mmx_reg64(r, std::mem::transmute(result));
write_mmx_reg64(r, u64::from_le_bytes(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0FF8_reg(r1: i32, r2: i32) { instr_0FF8(read_mmx64s(r1), r2); }
@ -5166,13 +5162,13 @@ pub unsafe fn instr_660FFB_mem(addr: i32, r: i32) {
#[no_mangle]
pub unsafe fn instr_0FFC(source: u64, r: i32) {
// paddb mm, mm/m64
let destination: [u8; 8] = std::mem::transmute(read_mmx64s(r));
let source: [u8; 8] = std::mem::transmute(source);
let destination: [u8; 8] = u64::to_le_bytes(read_mmx64s(r));
let source: [u8; 8] = u64::to_le_bytes(source);
let mut result = [0; 8];
for i in 0..8 {
result[i] = destination[i] + source[i];
}
write_mmx_reg64(r, std::mem::transmute(result));
write_mmx_reg64(r, u64::from_le_bytes(result));
transition_fpu_to_mmx();
}
pub unsafe fn instr_0FFC_reg(r1: i32, r2: i32) { instr_0FFC(read_mmx64s(r1), r2); }

316
src/rust/cpu/ioapic.rs Normal file
View file

@ -0,0 +1,316 @@
// http://download.intel.com/design/chipsets/datashts/29056601.pdf
use crate::cpu::{apic, global_pointers::acpi_enabled};
use std::sync::{Mutex, MutexGuard};
const IOAPIC_LOG_VERBOSE: bool = false;
const IOREGSEL: u32 = 0;
const IOWIN: u32 = 0x10;
const IOAPIC_IRQ_COUNT: usize = 24;
const IOAPIC_FIRST_IRQ_REG: u32 = 0x10;
const IOAPIC_LAST_IRQ_REG: u32 = 0x10 + 2 * IOAPIC_IRQ_COUNT as u32;
const IOAPIC_ID: u32 = 0; // must match value in seabios
pub const IOAPIC_CONFIG_TRIGGER_MODE_LEVEL: u32 = 1 << 15;
const IOAPIC_CONFIG_MASKED: u32 = 1 << 16;
const IOAPIC_CONFIG_DELIVS: u32 = 1 << 12;
const IOAPIC_CONFIG_REMOTE_IRR: u32 = 1 << 14;
const IOAPIC_CONFIG_READONLY_MASK: u32 =
IOAPIC_CONFIG_REMOTE_IRR | IOAPIC_CONFIG_DELIVS | 0xFFFE0000;
const IOAPIC_DELIVERY_FIXED: u8 = 0;
const IOAPIC_DELIVERY_LOWEST_PRIORITY: u8 = 1;
const _IOAPIC_DELIVERY_NMI: u8 = 4;
const _IOAPIC_DELIVERY_INIT: u8 = 5;
const DELIVERY_MODES: [&str; 8] = [
"Fixed (0)",
"Lowest Prio (1)",
"SMI (2)",
"Reserved (3)",
"NMI (4)",
"INIT (5)",
"Reserved (6)",
"ExtINT (7)",
];
const DESTINATION_MODES: [&str; 2] = ["physical", "logical"];
// keep in sync with cpu.js
#[allow(dead_code)]
const IOAPIC_STRUCT_SIZE: usize = 4 * 52;
// Note: JavaScript (cpu.get_state_apic) depens on this layout
const _: () = assert!(std::mem::offset_of!(Ioapic, ioredtbl_destination) == 24 * 4);
const _: () = assert!(std::mem::offset_of!(Ioapic, ioregsel) == 48 * 4);
const _: () = assert!(std::mem::offset_of!(Ioapic, irq_value) == 51 * 4);
const _: () = assert!(std::mem::size_of::<Ioapic>() == IOAPIC_STRUCT_SIZE);
#[repr(C)]
struct Ioapic {
ioredtbl_config: [u32; IOAPIC_IRQ_COUNT],
ioredtbl_destination: [u32; IOAPIC_IRQ_COUNT],
ioregsel: u32,
ioapic_id: u32,
irr: u32,
irq_value: u32,
}
static IOAPIC: Mutex<Ioapic> = Mutex::new(Ioapic {
ioredtbl_config: [IOAPIC_CONFIG_MASKED; IOAPIC_IRQ_COUNT],
ioredtbl_destination: [0; IOAPIC_IRQ_COUNT],
ioregsel: 0,
ioapic_id: IOAPIC_ID,
irr: 0,
irq_value: 0,
});
fn get_ioapic() -> MutexGuard<'static, Ioapic> { IOAPIC.try_lock().unwrap() }
#[no_mangle]
pub fn get_ioapic_addr() -> u32 { &raw mut *get_ioapic() as u32 }
pub fn remote_eoi(apic: &mut apic::Apic, vector: u8) {
remote_eoi_internal(&mut get_ioapic(), apic, vector);
}
fn remote_eoi_internal(ioapic: &mut Ioapic, apic: &mut apic::Apic, vector: u8) {
for i in 0..IOAPIC_IRQ_COUNT as u8 {
let config = ioapic.ioredtbl_config[i as usize];
if (config & 0xFF) as u8 == vector && config & IOAPIC_CONFIG_REMOTE_IRR != 0 {
dbg_log!("Clear remote IRR for irq={:x}", i);
ioapic.ioredtbl_config[i as usize] &= !IOAPIC_CONFIG_REMOTE_IRR;
check_irq(ioapic, apic, i);
}
}
}
fn check_irq(ioapic: &mut Ioapic, apic: &mut apic::Apic, irq: u8) {
let mask = 1 << irq;
if ioapic.irr & mask == 0 {
return;
}
let config = ioapic.ioredtbl_config[irq as usize];
if config & IOAPIC_CONFIG_MASKED == 0 {
let delivery_mode = ((config >> 8) & 7) as u8;
let destination_mode = ((config >> 11) & 1) as u8;
let vector = (config & 0xFF) as u8;
let destination = (ioapic.ioredtbl_destination[irq as usize] >> 24) as u8;
let is_level =
config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL == IOAPIC_CONFIG_TRIGGER_MODE_LEVEL;
if config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL == 0 {
ioapic.irr &= !mask;
}
else {
ioapic.ioredtbl_config[irq as usize] |= IOAPIC_CONFIG_REMOTE_IRR;
if config & IOAPIC_CONFIG_REMOTE_IRR != 0 {
dbg_log!("No route: level interrupt and remote IRR still set");
return;
}
}
if delivery_mode == IOAPIC_DELIVERY_FIXED
|| delivery_mode == IOAPIC_DELIVERY_LOWEST_PRIORITY
{
apic::route(
apic,
vector,
delivery_mode,
is_level,
destination,
destination_mode,
);
}
else {
dbg_assert!(false, "TODO");
}
ioapic.ioredtbl_config[irq as usize] &= !IOAPIC_CONFIG_DELIVS;
}
}
pub fn set_irq(i: u8) { set_irq_internal(&mut get_ioapic(), &mut apic::get_apic(), i) }
fn set_irq_internal(ioapic: &mut Ioapic, apic: &mut apic::Apic, i: u8) {
if i as usize >= IOAPIC_IRQ_COUNT {
dbg_assert!(false, "Bad irq: {}", i);
return;
}
let mask = 1 << i;
if ioapic.irq_value & mask == 0 {
if IOAPIC_LOG_VERBOSE {
dbg_log!("apic set irq {}", i);
}
ioapic.irq_value |= mask;
let config = ioapic.ioredtbl_config[i as usize];
if config & (IOAPIC_CONFIG_TRIGGER_MODE_LEVEL | IOAPIC_CONFIG_MASKED)
== IOAPIC_CONFIG_MASKED
{
// edge triggered and masked
return;
}
ioapic.irr |= mask;
check_irq(ioapic, apic, i);
}
}
pub fn clear_irq(i: u8) { clear_irq_internal(&mut get_ioapic(), i) }
fn clear_irq_internal(ioapic: &mut Ioapic, i: u8) {
if i as usize >= IOAPIC_IRQ_COUNT {
dbg_assert!(false, "Bad irq: {}", i);
return;
}
let mask = 1 << i;
if ioapic.irq_value & mask == mask {
ioapic.irq_value &= !mask;
let config = ioapic.ioredtbl_config[i as usize];
if config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL != 0 {
ioapic.irr &= !mask;
}
}
}
pub fn read32(addr: u32) -> u32 {
if unsafe { !*acpi_enabled } {
return 0;
}
read32_internal(&mut get_ioapic(), addr)
}
fn read32_internal(ioapic: &mut Ioapic, addr: u32) -> u32 {
match addr {
IOREGSEL => ioapic.ioregsel,
IOWIN => match ioapic.ioregsel {
0 => {
dbg_log!("IOAPIC Read id");
ioapic.ioapic_id << 24
},
1 => {
dbg_log!("IOAPIC Read version");
0x11 | (IOAPIC_IRQ_COUNT as u32 - 1) << 16
},
2 => {
dbg_log!("IOAPIC Read arbitration id");
ioapic.ioapic_id << 24
},
IOAPIC_FIRST_IRQ_REG..IOAPIC_LAST_IRQ_REG => {
let irq = ((ioapic.ioregsel - IOAPIC_FIRST_IRQ_REG) >> 1) as u8;
let index = ioapic.ioregsel & 1;
if index != 0 {
let value = ioapic.ioredtbl_destination[irq as usize];
dbg_log!("IOAPIC Read destination irq={:x} -> {:08x}", irq, value);
value
}
else {
let value = ioapic.ioredtbl_config[irq as usize];
dbg_log!("IOAPIC Read config irq={:x} -> {:08x}", irq, value);
value
}
},
reg => {
dbg_assert!(false, "IOAPIC register read outside of range {:x}", reg);
0
},
},
_ => {
dbg_assert!(false, "Unaligned or oob IOAPIC memory read: {:x}", addr);
0
},
}
}
pub fn write32(addr: u32, value: u32) {
if unsafe { !*acpi_enabled } {
return;
}
write32_internal(&mut get_ioapic(), &mut apic::get_apic(), addr, value)
}
fn write32_internal(ioapic: &mut Ioapic, apic: &mut apic::Apic, addr: u32, value: u32) {
//dbg_log!("IOAPIC write {:x} <- {:08x}", reg, value);
match addr {
IOREGSEL => ioapic.ioregsel = value,
IOWIN => match ioapic.ioregsel {
0 => ioapic.ioapic_id = (value >> 24) & 0x0F,
1 | 2 => {
dbg_log!("IOAPIC Invalid write: {}", ioapic.ioregsel);
},
IOAPIC_FIRST_IRQ_REG..IOAPIC_LAST_IRQ_REG => {
let irq = ((ioapic.ioregsel - IOAPIC_FIRST_IRQ_REG) >> 1) as u8;
let index = ioapic.ioregsel & 1;
if index != 0 {
dbg_log!(
"Write destination {:08x} irq={:x} dest={:02x}",
value,
irq,
value >> 24
);
ioapic.ioredtbl_destination[irq as usize] = value & 0xFF000000;
}
else {
let old_value = ioapic.ioredtbl_config[irq as usize] as u32;
ioapic.ioredtbl_config[irq as usize] = (value & !IOAPIC_CONFIG_READONLY_MASK)
| (old_value & IOAPIC_CONFIG_READONLY_MASK);
let vector = value & 0xFF;
let delivery_mode = (value >> 8) & 7;
let destination_mode = (value >> 11) & 1;
let is_level = (value >> 15) & 1;
let disabled = (value >> 16) & 1;
dbg_log!(
"Write config {:08x} irq={:x} vector={:02x} deliverymode={} destmode={} is_level={} disabled={}",
value,
irq,
vector,
DELIVERY_MODES[delivery_mode as usize],
DESTINATION_MODES[destination_mode as usize],
is_level,
disabled
);
check_irq(ioapic, apic, irq);
}
},
reg => {
dbg_assert!(
false,
"IOAPIC register write outside of range {:x} <- {:x}",
reg,
value
)
},
},
_ => {
dbg_assert!(
false,
"Unaligned or oob IOAPIC memory write: {:x} <- {:x}",
addr,
value
)
},
}
}

View file

@ -12,8 +12,12 @@ mod ext {
}
}
use crate::cpu::cpu::reg128;
use crate::cpu::apic;
use crate::cpu::cpu::{
handle_irqs, reg128, APIC_MEM_ADDRESS, APIC_MEM_SIZE, IOAPIC_MEM_ADDRESS, IOAPIC_MEM_SIZE,
};
use crate::cpu::global_pointers::memory_size;
use crate::cpu::ioapic;
use crate::cpu::vga;
use crate::jit;
use crate::page::Page;
@ -122,6 +126,12 @@ pub fn read32s(addr: u32) -> i32 {
ptr::read_unaligned(vga_mem8.offset((addr - VGA_LFB_ADDRESS) as isize) as *const i32)
} // XXX
}
else if addr >= APIC_MEM_ADDRESS && addr < APIC_MEM_ADDRESS + APIC_MEM_SIZE {
apic::read32(addr - APIC_MEM_ADDRESS) as i32
}
else if addr >= IOAPIC_MEM_ADDRESS && addr < IOAPIC_MEM_ADDRESS + IOAPIC_MEM_SIZE {
ioapic::read32(addr - IOAPIC_MEM_ADDRESS) as i32
}
else {
unsafe { ext::mmap_read32(addr) }
}
@ -206,7 +216,7 @@ pub unsafe fn write32(addr: u32, value: i32) {
else {
jit::jit_dirty_cache_small(addr, addr + 4);
write32_no_mmap_or_dirty_check(addr, value);
};
}
}
pub unsafe fn write32_no_mmap_or_dirty_check(addr: u32, value: i32) {
@ -238,6 +248,8 @@ pub unsafe fn memcpy_no_mmap_or_dirty_check(src_addr: u32, dst_addr: u32, count:
pub unsafe fn memcpy_into_svga_lfb(src_addr: u32, dst_addr: u32, count: u32) {
dbg_assert!(src_addr < *memory_size);
dbg_assert!(in_svga_lfb(dst_addr));
dbg_assert!(Page::page_of(dst_addr) == Page::page_of(dst_addr + count - 1));
vga::mark_dirty(dst_addr);
ptr::copy_nonoverlapping(
mem8.offset(src_addr as isize),
vga_mem8.offset((dst_addr - VGA_LFB_ADDRESS) as isize),
@ -274,6 +286,14 @@ pub unsafe fn mmap_write32(addr: u32, value: i32) {
value,
)
}
else if addr >= APIC_MEM_ADDRESS && addr < APIC_MEM_ADDRESS + APIC_MEM_SIZE {
apic::write32(addr - APIC_MEM_ADDRESS, value as u32);
handle_irqs();
}
else if addr >= IOAPIC_MEM_ADDRESS && addr < IOAPIC_MEM_ADDRESS + IOAPIC_MEM_SIZE {
ioapic::write32(addr - IOAPIC_MEM_ADDRESS, value as u32);
handle_irqs();
}
else {
ext::mmap_write32(addr, value)
}

View file

@ -1,3 +1,4 @@
pub mod apic;
pub mod arith;
pub mod call_indirect;
pub mod cpu;
@ -5,6 +6,7 @@ pub mod fpu;
pub mod global_pointers;
pub mod instructions;
pub mod instructions_0f;
pub mod ioapic;
pub mod memory;
pub mod misc_instr;
pub mod modrm;

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)
#[repr(C, packed)]
const _: () = assert!(std::mem::offset_of!(Pic0, special_mask_mode) == 12);
#[repr(C)]
struct Pic0 {
irq_mask: u8,
@ -39,7 +39,7 @@ struct Pic0 {
special_mask_mode: bool,
}
pub struct Pic {
struct Pic {
master: Pic0,
slave: Pic0,
}
@ -91,17 +91,16 @@ static PIC: Mutex<Pic> = Mutex::new(Pic {
},
});
pub fn get_pic() -> MutexGuard<'static, Pic> { PIC.try_lock().unwrap() }
fn get_pic() -> MutexGuard<'static, Pic> { PIC.try_lock().unwrap() }
// Checking for callable interrupts:
// (cpu changes interrupt flag) -> cpu.handle_irqs -> pic_acknowledge_irq
// (pic changes isr/irr) -> pic.check_irqs -> cpu.handle_irqs -> ...
// triggering irqs:
// (io device has irq) -> cpu.device_raise_irq -> pic.set_irq -> pic.check_irqs -> cpu.handle_irqs -> (see above)
// called from javascript for saving/restoring state
#[no_mangle]
pub fn get_pic_addr_master() -> u32 { &raw mut get_pic().master as u32 }
#[no_mangle]
pub fn get_pic_addr_slave() -> u32 { &raw mut get_pic().slave as u32 }
impl Pic0 {
unsafe fn get_irq(&mut self) -> Option<u8> {
fn get_irq(&mut self) -> Option<u8> {
let enabled_irr = self.irr & self.irq_mask;
if enabled_irr == 0 {
@ -144,44 +143,36 @@ impl Pic0 {
Some(irq_number)
}
unsafe fn port0_read(self: &Pic0) -> u32 {
(if self.read_isr { self.isr } else { self.irr }) as u32
}
unsafe fn port1_read(self: &Pic0) -> u32 { !self.irq_mask as u32 }
fn port0_read(self: &Pic0) -> u32 { (if self.read_isr { self.isr } else { self.irr }) as u32 }
fn port1_read(self: &Pic0) -> u32 { !self.irq_mask as u32 }
}
impl Pic {
unsafe fn set_irq(self: &mut Pic, i: u8) {
fn set_irq(self: &mut Pic, i: u8) {
let mask = 1 << (i & 7);
let dev = if i < 8 { &mut self.master } else { &mut self.slave };
if dev.irq_value & mask == 0 || dev.elcr & mask != 0 {
dev.irr |= mask;
dev.irq_value |= mask;
if i < 8 {
self.check_irqs_master()
}
else {
if i >= 8 {
self.check_irqs_slave()
}
}
}
unsafe fn clear_irq(self: &mut Pic, i: u8) {
fn clear_irq(self: &mut Pic, i: u8) {
let mask = 1 << (i & 7);
let dev = if i < 8 { &mut self.master } else { &mut self.slave };
dev.irq_value &= !mask;
if dev.elcr & mask != 0 {
dev.irr &= !mask;
if i < 8 {
self.check_irqs_master()
}
else {
if i >= 8 {
self.check_irqs_slave()
}
}
}
unsafe fn port0_write(&mut self, index: u8, v: u8) {
fn port0_write(&mut self, index: u8, v: u8) {
let dev = if index == 0 { &mut self.master } else { &mut self.slave };
if v & 0x10 != 0 {
// xxxx1xxx
@ -245,16 +236,13 @@ impl Pic {
dev.isr &= dev.isr - 1;
}
if index == 0 {
self.check_irqs_master()
}
else {
if index == 1 {
self.check_irqs_slave()
}
}
}
unsafe fn port1_write(&mut self, index: u8, v: u8) {
fn port1_write(&mut self, index: u8, v: u8) {
let dev = if index == 0 { &mut self.master } else { &mut self.slave };
if dev.state == 0 {
if dev.expect_icw4 {
@ -273,10 +261,7 @@ impl Pic {
dbg_log!("interrupt mask: {:x}", dev.irq_mask);
}
if index == 0 {
self.check_irqs_master()
}
else {
if index == 1 {
self.check_irqs_slave()
}
}
@ -294,13 +279,7 @@ impl Pic {
}
}
unsafe fn check_irqs_master(&mut self) {
let is_set = self.master.get_irq().is_some();
if is_set {
cpu::handle_irqs_internal(self);
}
}
unsafe fn check_irqs_slave(&mut self) {
fn check_irqs_slave(&mut self) {
let is_set = self.slave.get_irq().is_some();
if is_set {
self.set_irq(2)
@ -312,7 +291,8 @@ impl Pic {
}
// called by the cpu
pub unsafe fn pic_acknowledge_irq(pic: &mut Pic) -> Option<u8> {
pub fn pic_acknowledge_irq() -> Option<u8> {
let mut pic = get_pic();
let irq = match pic.master.get_irq() {
Some(i) => i,
None => return None,
@ -343,14 +323,14 @@ pub unsafe fn pic_acknowledge_irq(pic: &mut Pic) -> Option<u8> {
dbg_assert!(pic.master.get_irq().is_none());
if irq == 2 {
acknowledge_irq_slave(pic)
acknowledge_irq_slave(&mut pic)
}
else {
Some(pic.master.irq_map | irq)
}
}
unsafe fn acknowledge_irq_slave(pic: &mut Pic) -> Option<u8> {
fn acknowledge_irq_slave(pic: &mut Pic) -> Option<u8> {
let irq = match pic.slave.get_irq() {
Some(i) => i,
None => return None,
@ -384,9 +364,7 @@ unsafe fn acknowledge_irq_slave(pic: &mut Pic) -> Option<u8> {
Some(pic.slave.irq_map | irq)
}
// called by javascript
#[no_mangle]
pub unsafe fn pic_set_irq(i: u8) {
pub fn set_irq(i: u8) {
dbg_assert!(i < 16);
if PIC_LOG_VERBOSE {
@ -396,9 +374,7 @@ pub unsafe fn pic_set_irq(i: u8) {
get_pic().set_irq(i)
}
// called by javascript
#[no_mangle]
pub unsafe fn pic_clear_irq(i: u8) {
pub fn clear_irq(i: u8) {
dbg_assert!(i < 16);
if PIC_LOG_VERBOSE {
@ -408,36 +384,19 @@ pub unsafe fn pic_clear_irq(i: u8) {
get_pic().clear_irq(i)
}
#[no_mangle]
pub unsafe fn port20_read() -> u32 { get_pic().master.port0_read() }
#[no_mangle]
pub unsafe fn port21_read() -> u32 { get_pic().master.port1_read() }
pub fn port20_read() -> u32 { get_pic().master.port0_read() }
pub fn port21_read() -> u32 { get_pic().master.port1_read() }
#[no_mangle]
pub unsafe fn portA0_read() -> u32 { get_pic().slave.port0_read() }
#[no_mangle]
pub unsafe fn portA1_read() -> u32 { get_pic().slave.port1_read() }
pub fn portA0_read() -> u32 { get_pic().slave.port0_read() }
pub fn portA1_read() -> u32 { get_pic().slave.port1_read() }
#[no_mangle]
pub unsafe fn port20_write(v: u8) { get_pic().port0_write(0, v) }
#[no_mangle]
pub unsafe fn port21_write(v: u8) { get_pic().port1_write(0, v) }
pub fn port20_write(v: u8) { get_pic().port0_write(0, v) }
pub fn port21_write(v: u8) { get_pic().port1_write(0, v) }
#[no_mangle]
pub unsafe fn portA0_write(v: u8) { get_pic().port0_write(1, v) }
#[no_mangle]
pub unsafe fn portA1_write(v: u8) { get_pic().port1_write(1, v) }
pub fn portA0_write(v: u8) { get_pic().port0_write(1, v) }
pub fn portA1_write(v: u8) { get_pic().port1_write(1, v) }
#[no_mangle]
pub unsafe fn port4D0_read() -> u32 { get_pic().master.elcr as u32 }
#[no_mangle]
pub unsafe fn port4D1_read() -> u32 { get_pic().slave.elcr as u32 }
#[no_mangle]
pub unsafe fn port4D0_write(v: u8) { get_pic().master.elcr = v }
#[no_mangle]
pub unsafe fn port4D1_write(v: u8) { get_pic().slave.elcr = v }
#[no_mangle]
pub unsafe fn get_pic_addr_master() -> u32 { &raw const get_pic().master as u32 }
#[no_mangle]
pub unsafe fn get_pic_addr_slave() -> u32 { &raw const get_pic().slave as u32 }
pub fn port4D0_read() -> u32 { get_pic().master.elcr as u32 }
pub fn port4D1_read() -> u32 { get_pic().slave.elcr as u32 }
pub fn port4D0_write(v: u8) { get_pic().master.elcr = v }
pub fn port4D1_write(v: u8) { get_pic().slave.elcr = v }

View file

@ -9,7 +9,6 @@
// ins 0 0 1/w
// outs 0 1 0
use crate::cpu;
use crate::cpu::arith::{cmp16, cmp32, cmp8};
use crate::cpu::cpu::{
get_seg, io_port_read16, io_port_read32, io_port_read8, io_port_write16, io_port_write32,
@ -300,7 +299,6 @@ unsafe fn string_instruction(
phys_dst -= (count_until_end_of_page - 1) * size_bytes as u32;
}
if movs_into_svga_lfb {
cpu::vga::mark_dirty(phys_dst);
memory::memcpy_into_svga_lfb(
phys_src,
phys_dst,

View file

@ -1,3 +1,43 @@
#[allow(dead_code)]
pub const DEBUG: bool = cfg!(debug_assertions);
#[cfg(target_arch = "wasm32")]
extern "C" {
pub fn log_from_wasm(ptr: *const u8, len: usize);
pub fn console_log_from_wasm(ptr: *const u8, len: usize);
pub fn dbg_trace_from_wasm();
}
#[cfg(target_arch = "wasm32")]
pub fn log_to_js_console<T: std::string::ToString>(s: T) {
let s = s.to_string();
let len = s.len();
unsafe {
log_from_wasm(s.as_bytes().as_ptr(), len);
}
}
#[cfg(target_arch = "wasm32")]
pub fn console_log_to_js_console<T: std::string::ToString>(s: T) {
let s = s.to_string();
let len = s.len();
unsafe {
console_log_from_wasm(s.as_bytes().as_ptr(), len);
}
}
#[cfg(target_arch = "wasm32")]
pub fn dbg_trace() {
if DEBUG {
unsafe {
dbg_trace_from_wasm();
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn dbg_trace() {}
#[allow(unused_macros)]
macro_rules! dbg_log {
($fmt:expr) => {
@ -30,14 +70,12 @@ macro_rules! dbg_assert {
macro_rules! console_log {
($fmt:expr) => {
{
use crate::util::{ console_log_to_js_console };
console_log_to_js_console($fmt);
crate::dbg::console_log_to_js_console($fmt);
}
};
($fmt:expr, $($arg:tt)*) => {
{
use crate::util::{ console_log_to_js_console };
console_log_to_js_console(format!($fmt, $($arg)*));
crate::dbg::console_log_to_js_console(format!($fmt, $($arg)*));
}
};
}
@ -47,52 +85,14 @@ macro_rules! console_log {
macro_rules! dbg_log {
($fmt:expr) => {
{
use crate::util::{ DEBUG, log_to_js_console };
use crate::dbg::{ DEBUG, log_to_js_console };
if DEBUG { log_to_js_console($fmt); }
}
};
($fmt:expr, $($arg:tt)*) => {
{
use crate::util::{ DEBUG, log_to_js_console };
use crate::dbg::{ DEBUG, log_to_js_console };
if DEBUG { log_to_js_console(format!($fmt, $($arg)*)); }
}
};
}
#[cfg(target_arch = "wasm32")]
#[allow(unused_macros)]
macro_rules! dbg_assert {
($cond:expr) => {{
use crate::util::{abort, log_to_js_console, DEBUG};
if DEBUG && !$cond {
log_to_js_console(format!(
"Assertion failed at {}:{}:{}: '{}'",
file!(),
line!(),
column!(),
stringify!($cond),
));
#[allow(unused_unsafe)]
unsafe {
abort();
}
}
}};
($cond:expr, $desc:expr) => {{
use crate::util::{abort, log_to_js_console, DEBUG};
if DEBUG && !$cond {
log_to_js_console(format!(
"Assertion failed at {}:{}:{}: '{}' - '{}'",
file!(),
line!(),
column!(),
stringify!($cond),
$desc,
));
#[allow(unused_unsafe)]
unsafe {
abort();
}
}
}};
}

View file

@ -20,7 +20,6 @@ use crate::page::Page;
use crate::profiler;
use crate::profiler::stat;
use crate::state_flags::CachedStateFlags;
use crate::util::SafeToU16;
use crate::wasmgen::wasm_builder::{Label, WasmBuilder, WasmLocal};
#[derive(Copy, Clone, Eq, Hash, PartialEq)]
@ -486,7 +485,7 @@ fn jit_find_basic_blocks(
let mut pages: HashSet<Page> = HashSet::new();
let mut page_blacklist = HashSet::new();
// 16-bit doesn't not work correctly, most likely due to instruction pointer wrap-around
// 16-bit doesn't work correctly, most likely due to instruction pointer wrap-around
let max_pages = if cpu.state_flags.is_32() { unsafe { MAX_PAGES } } else { 1 };
for virt_addr in entry_points {
@ -540,13 +539,22 @@ fn jit_find_basic_blocks(
..cpu
};
let analysis = analysis::analyze_step(&mut cpu);
current_block.number_of_instructions += 1;
let has_next_instruction = !analysis.no_next_instruction;
current_address = cpu.eip;
dbg_assert!(Page::page_of(current_address) == Page::page_of(addr_before_instruction));
let current_virt_addr = to_visit & !0xFFF | current_address as i32 & 0xFFF;
if analysis.ty == AnalysisType::STI && is_near_end_of_page(current_address) {
// cut off before the STI so that it is handled by interpreted mode
profiler::stat_increment(stat::COMPILE_CUT_OFF_AT_END_OF_PAGE);
break;
}
current_block.number_of_instructions += 1;
current_block.last_instruction_addr = addr_before_instruction;
current_block.end_addr = current_address;
match analysis.ty {
AnalysisType::Normal | AnalysisType::STI => {
dbg_assert!(has_next_instruction);
@ -558,26 +566,22 @@ fn jit_find_basic_blocks(
marked_as_entry.insert(current_virt_addr);
to_visit_stack.push(current_virt_addr);
current_block.last_instruction_addr = addr_before_instruction;
current_block.end_addr = current_address;
break;
}
if analysis.ty == AnalysisType::STI {
current_block.has_sti = true;
dbg_assert!(
!is_near_end_of_page(current_address),
"TODO: Handle STI instruction near end of page"
"should be handled above"
);
current_block.has_sti = true;
}
else {
// Only split non-STI blocks (one instruction needs to run after STI before
// handle_irqs may be called)
if basic_blocks.contains_key(&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),
@ -630,9 +634,6 @@ fn jit_find_basic_blocks(
jump_offset_is_32: is_32,
};
current_block.last_instruction_addr = addr_before_instruction;
current_block.end_addr = current_address;
break;
},
AnalysisType::Jump {
@ -670,8 +671,6 @@ fn jit_find_basic_blocks(
jump_offset: offset,
jump_offset_is_32: is_32,
};
current_block.last_instruction_addr = addr_before_instruction;
current_block.end_addr = current_address;
break;
},
@ -691,24 +690,25 @@ fn jit_find_basic_blocks(
current_block.ty = BasicBlockType::AbsoluteEip;
}
current_block.last_instruction_addr = addr_before_instruction;
current_block.end_addr = current_address;
break;
},
}
if is_near_end_of_page(current_address) {
current_block.last_instruction_addr = addr_before_instruction;
current_block.end_addr = current_address;
profiler::stat_increment(stat::COMPILE_CUT_OFF_AT_END_OF_PAGE);
break;
}
}
if current_block.number_of_instructions == 0 {
// Empty basic block, don't insert (only happens when STI is found near end of page)
continue;
}
let previous_block = basic_blocks
.range(..current_block.addr)
.next_back()
.filter(|(_, previous_block)| (!previous_block.has_sti))
.filter(|(_, previous_block)| !previous_block.has_sti)
.map(|(_, previous_block)| previous_block);
if let Some(previous_block) = previous_block {
@ -1230,28 +1230,30 @@ fn jit_generate_module(
let mut index_for_addr = HashMap::new();
for (i, &addr) in entry_blocks.iter().enumerate() {
index_for_addr.insert(addr, i as i32);
dbg_assert!(i < 0x10000);
index_for_addr.insert(addr, i as u16);
}
for b in basic_blocks.values() {
if !index_for_addr.contains_key(&b.addr) {
let i = index_for_addr.len();
index_for_addr.insert(b.addr, i as i32);
dbg_assert!(i < 0x10000);
index_for_addr.insert(b.addr, i as u16);
}
}
let mut label_for_addr: HashMap<u32, (Label, Option<i32>)> = HashMap::new();
let mut label_for_addr: HashMap<u32, (Label, Option<u16>)> = HashMap::new();
enum Work {
WasmStructure(WasmStructure),
BlockEnd {
label: Label,
targets: Vec<u32>,
olds: HashMap<u32, (Label, Option<i32>)>,
olds: HashMap<u32, (Label, Option<u16>)>,
},
LoopEnd {
label: Label,
entries: Vec<u32>,
olds: HashMap<u32, (Label, Option<i32>)>,
olds: HashMap<u32, (Label, Option<u16>)>,
},
}
let mut work: VecDeque<Work> = structure
@ -1423,10 +1425,10 @@ fn jit_generate_module(
if next_addr.unwrap().len() > 1 {
let target_index = *index_for_addr.get(&next_block_addr).unwrap();
if cfg!(feature = "profiler") {
ctx.builder.const_i32(target_index);
ctx.builder.const_i32(target_index.into());
ctx.builder.call_fn1("debug_set_dispatcher_target");
}
ctx.builder.const_i32(target_index);
ctx.builder.const_i32(target_index.into());
ctx.builder.set_local(target_block);
codegen::gen_profiler_stat_increment(
ctx.builder,
@ -1444,10 +1446,10 @@ fn jit_generate_module(
let &(br, target_index) = label_for_addr.get(&next_block_addr).unwrap();
if let Some(target_index) = target_index {
if cfg!(feature = "profiler") {
ctx.builder.const_i32(target_index);
ctx.builder.const_i32(target_index.into());
ctx.builder.call_fn1("debug_set_dispatcher_target");
}
ctx.builder.const_i32(target_index);
ctx.builder.const_i32(target_index.into());
ctx.builder.set_local(target_block);
codegen::gen_profiler_stat_increment(
ctx.builder,
@ -1571,10 +1573,10 @@ fn jit_generate_module(
let target_index =
*index_for_addr.get(&next_block_addr).unwrap();
if cfg!(feature = "profiler") {
ctx.builder.const_i32(target_index);
ctx.builder.const_i32(target_index.into());
ctx.builder.call_fn1("debug_set_dispatcher_target");
}
ctx.builder.const_i32(target_index);
ctx.builder.const_i32(target_index.into());
ctx.builder.set_local(target_block);
codegen::gen_profiler_stat_increment(
ctx.builder,
@ -1595,10 +1597,10 @@ fn jit_generate_module(
if cfg!(feature = "profiler") {
// Note: Currently called unconditionally, even if the
// br_if below doesn't branch
ctx.builder.const_i32(target_index);
ctx.builder.const_i32(target_index.into());
ctx.builder.call_fn1("debug_set_dispatcher_target");
}
ctx.builder.const_i32(target_index);
ctx.builder.const_i32(target_index.into());
ctx.builder.set_local(target_block);
}
@ -1751,11 +1753,11 @@ fn jit_generate_module(
let target_index_not_taken =
*index_for_addr.get(&next_block_addr).unwrap();
ctx.builder.const_i32(target_index_taken);
ctx.builder.const_i32(target_index_taken.into());
ctx.builder.set_local(target_block);
ctx.builder.else_();
ctx.builder.const_i32(target_index_not_taken);
ctx.builder.const_i32(target_index_not_taken.into());
ctx.builder.set_local(target_block);
ctx.builder.block_end();
@ -1768,9 +1770,9 @@ fn jit_generate_module(
codegen::gen_condition_fn(ctx, condition);
ctx.builder.if_i32();
ctx.builder.const_i32(target_index_taken);
ctx.builder.const_i32(target_index_taken.into());
ctx.builder.else_();
ctx.builder.const_i32(target_index_not_taken);
ctx.builder.const_i32(target_index_not_taken.into());
ctx.builder.block_end();
ctx.builder.set_local(target_block);
}
@ -1824,7 +1826,7 @@ fn jit_generate_module(
let index = *index_for_addr.get(&addr).unwrap();
let &(label, _) = label_for_addr.get(&addr).unwrap();
ctx.builder.get_local(target_block);
ctx.builder.const_i32(index);
ctx.builder.const_i32(index.into());
ctx.builder.eq_i32();
ctx.builder.br_if(label);
}
@ -1986,8 +1988,7 @@ fn jit_generate_module(
// Note: We also insert blocks that weren't originally marked as entries here
// This doesn't have any downside, besides making the hash table slightly larger
let initial_state = index.safe_to_u16();
(block.addr, initial_state)
(block.addr, index)
}));
for b in basic_blocks.values() {

View file

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

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(unsafe { std::mem::transmute(src) }) }
fn of_f64x(src: f64) -> F80 { F80::of_f64(f64::to_bits(src)) }
pub fn to_f32(&self) -> i32 { unsafe { extF80M_to_f32(self) } }
pub fn to_f64(&self) -> u64 { unsafe { extF80M_to_f64(self) } }
fn to_f64x(&self) -> f64 { unsafe { std::mem::transmute(extF80M_to_f64(self)) } }
fn to_f64x(&self) -> f64 { f64::from_bits(self.to_f64()) }
pub fn to_i32(&self) -> i32 { unsafe { extF80M_to_i32(self, softfloat_roundingMode, false) } }
pub fn to_i64(&self) -> i64 { unsafe { extF80M_to_i64(self, softfloat_roundingMode, false) } }

View file

@ -1,98 +0,0 @@
pub trait SafeToU8 {
fn safe_to_u8(self) -> u8;
}
pub trait SafeToU16 {
fn safe_to_u16(self) -> u16;
}
impl SafeToU8 for u16 {
fn safe_to_u8(self) -> u8 {
dbg_assert!(self <= ::std::u8::MAX as u16);
self as u8
}
}
impl SafeToU8 for u32 {
fn safe_to_u8(self) -> u8 {
dbg_assert!(self <= ::std::u8::MAX as u32);
self as u8
}
}
impl SafeToU8 for i32 {
fn safe_to_u8(self) -> u8 {
dbg_assert!(self >= 0 && self <= ::std::u8::MAX as i32);
self as u8
}
}
impl SafeToU8 for usize {
fn safe_to_u8(self) -> u8 {
dbg_assert!(self <= ::std::u8::MAX as usize);
self as u8
}
}
impl SafeToU16 for u32 {
fn safe_to_u16(self) -> u16 {
dbg_assert!(self <= ::std::u16::MAX as u32);
self as u16
}
}
impl SafeToU16 for i32 {
fn safe_to_u16(self) -> u16 {
dbg_assert!(self >= 0 && self <= ::std::u16::MAX as i32);
self as u16
}
}
impl SafeToU16 for usize {
fn safe_to_u16(self) -> u16 {
dbg_assert!(self <= ::std::u16::MAX as usize);
self as u16
}
}
#[allow(dead_code)]
pub const DEBUG: bool = cfg!(debug_assertions);
#[cfg(target_arch = "wasm32")]
extern "C" {
pub fn log_from_wasm(ptr: *const u8, len: usize);
pub fn console_log_from_wasm(ptr: *const u8, len: usize);
pub fn abort();
}
extern "C" {
pub fn dbg_trace_from_wasm();
}
#[cfg(target_arch = "wasm32")]
use std::string::ToString;
#[cfg(target_arch = "wasm32")]
pub fn log_to_js_console<T: ToString>(s: T) {
let s = s.to_string();
let len = s.len();
unsafe {
log_from_wasm(s.as_bytes().as_ptr(), len);
}
}
#[cfg(target_arch = "wasm32")]
pub fn console_log_to_js_console<T: ToString>(s: T) {
let s = s.to_string();
let len = s.len();
unsafe {
console_log_from_wasm(s.as_bytes().as_ptr(), len);
}
}
pub fn dbg_trace() {
if DEBUG {
unsafe {
dbg_trace_from_wasm();
}
}
}

View file

@ -4,9 +4,28 @@ use std::mem::transmute;
use crate::leb::{
write_fixed_leb16_at_idx, write_fixed_leb32_at_idx, write_leb_i32, write_leb_i64, write_leb_u32,
};
use crate::util::{SafeToU16, SafeToU8};
use crate::wasmgen::wasm_opcodes as op;
pub trait SafeToU8 {
fn safe_to_u8(self) -> u8;
}
impl SafeToU8 for usize {
fn safe_to_u8(self) -> u8 {
dbg_assert!(self <= ::std::u8::MAX as usize);
self as u8
}
}
pub trait SafeToU16 {
fn safe_to_u16(self) -> u16;
}
impl SafeToU16 for usize {
fn safe_to_u16(self) -> u16 {
dbg_assert!(self <= ::std::u16::MAX as usize);
self as u16
}
}
#[derive(PartialEq)]
#[allow(non_camel_case_types)]
enum FunctionType {

View file

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

View file

@ -1,25 +1,14 @@
"use strict";
import { h } from "./lib.js";
import { dbg_assert, dbg_log } from "./log.js";
import { CPU } from "./cpu.js";
/** @const */
var STATE_VERSION = 6;
/** @const */
var STATE_MAGIC = 0x86768676|0;
/** @const */
var STATE_INDEX_MAGIC = 0;
/** @const */
var STATE_INDEX_VERSION = 1;
/** @const */
var STATE_INDEX_TOTAL_LEN = 2;
/** @const */
var STATE_INDEX_INFO_LEN = 3;
/** @const */
var STATE_INFO_BLOCK_START = 16;
const STATE_VERSION = 6;
const STATE_MAGIC = 0x86768676|0;
const STATE_INDEX_MAGIC = 0;
const STATE_INDEX_VERSION = 1;
const STATE_INDEX_TOTAL_LEN = 2;
const STATE_INDEX_INFO_LEN = 3;
const STATE_INFO_BLOCK_START = 16;
const ZSTD_MAGIC = 0xFD2FB528;
@ -31,6 +20,7 @@ function StateLoadError(msg)
StateLoadError.prototype = new Error;
const CONSTRUCTOR_TABLE = {
"Map": Map,
"Uint8Array": Uint8Array,
"Int8Array": Int8Array,
"Uint16Array": Uint16Array,
@ -54,6 +44,17 @@ function save_object(obj, saved_buffers)
return obj.map(x => save_object(x, saved_buffers));
}
if(obj instanceof Map)
{
return {
"__state_type__": "Map",
"args": Array.from(obj.entries()).map(([k, v]) => [
save_object(k, saved_buffers),
save_object(v, saved_buffers),
]),
};
}
if(obj.constructor === Object)
{
console.log(obj);
@ -119,14 +120,19 @@ function restore_buffers(obj, buffers)
const constructor = CONSTRUCTOR_TABLE[type];
dbg_assert(constructor, "Unkown type: " + type);
if(obj["args"] !== undefined) {
return new constructor(obj["args"]);
}
const buffer = buffers[obj["buffer_id"]];
return new constructor(buffer);
}
CPU.prototype.save_state = function()
/* @param {CPU} cpu */
export function save_state(cpu)
{
var saved_buffers = [];
var state = save_object(this, saved_buffers);
var state = save_object(cpu, saved_buffers);
var buffer_infos = [];
var total_buffer_size = 0;
@ -188,9 +194,10 @@ CPU.prototype.save_state = function()
dbg_log("State: Total buffers size " + (buffer_block.byteLength >> 10) + "k");
return result;
};
}
CPU.prototype.restore_state = function(state)
/* @param {CPU} cpu */
export function restore_state(cpu, state)
{
state = new Uint8Array(state);
@ -235,19 +242,19 @@ CPU.prototype.restore_state = function(state)
if(new Uint32Array(state.buffer, 0, 1)[0] === ZSTD_MAGIC)
{
const ctx = this.zstd_create_ctx(state.length);
const ctx = cpu.zstd_create_ctx(state.length);
new Uint8Array(this.wasm_memory.buffer, this.zstd_get_src_ptr(ctx), state.length).set(state);
new Uint8Array(cpu.wasm_memory.buffer, cpu.zstd_get_src_ptr(ctx) >>> 0, state.length).set(state);
let ptr = this.zstd_read(ctx, 16);
const header_block = new Uint8Array(this.wasm_memory.buffer, ptr, 16);
let ptr = cpu.zstd_read(ctx, 16);
const header_block = new Uint8Array(cpu.wasm_memory.buffer, ptr >>> 0, 16);
const info_block_len = read_state_header(header_block, false);
this.zstd_read_free(ptr, 16);
cpu.zstd_read_free(ptr, 16);
ptr = this.zstd_read(ctx, info_block_len);
const info_block_buffer = new Uint8Array(this.wasm_memory.buffer, ptr, info_block_len);
ptr = cpu.zstd_read(ctx, info_block_len);
const info_block_buffer = new Uint8Array(cpu.wasm_memory.buffer, ptr >>> 0, info_block_len);
const info_block_obj = read_info_block(info_block_buffer);
this.zstd_read_free(ptr, info_block_len);
cpu.zstd_read_free(ptr, info_block_len);
let state_object = info_block_obj["state"];
const buffer_infos = info_block_obj["buffer_infos"];
@ -262,8 +269,8 @@ CPU.prototype.restore_state = function(state)
if(buffer_info.length > CHUNK_SIZE)
{
const ptr = this.zstd_read(ctx, front_padding);
this.zstd_read_free(ptr, front_padding);
const ptr = cpu.zstd_read(ctx, front_padding) >>> 0;
cpu.zstd_read_free(ptr, front_padding);
const buffer = new Uint8Array(buffer_info.length);
buffers.push(buffer.buffer);
@ -275,28 +282,28 @@ CPU.prototype.restore_state = function(state)
dbg_assert(remaining >= 0);
const to_read = Math.min(remaining, CHUNK_SIZE);
const ptr = this.zstd_read(ctx, to_read);
buffer.set(new Uint8Array(this.wasm_memory.buffer, ptr, to_read), have);
this.zstd_read_free(ptr, to_read);
const ptr = cpu.zstd_read(ctx, to_read);
buffer.set(new Uint8Array(cpu.wasm_memory.buffer, ptr >>> 0, to_read), have);
cpu.zstd_read_free(ptr, to_read);
have += to_read;
}
}
else
{
const ptr = this.zstd_read(ctx, front_padding + buffer_info.length);
const offset = ptr + front_padding;
buffers.push(this.wasm_memory.buffer.slice(offset, offset + buffer_info.length));
this.zstd_read_free(ptr, front_padding + buffer_info.length);
const ptr = cpu.zstd_read(ctx, front_padding + buffer_info.length);
const offset = (ptr >>> 0) + front_padding;
buffers.push(cpu.wasm_memory.buffer.slice(offset, offset + buffer_info.length));
cpu.zstd_read_free(ptr, front_padding + buffer_info.length);
}
position += front_padding + buffer_info.length;
}
state_object = restore_buffers(state_object, buffers);
this.set_state(state_object);
cpu.set_state(state_object);
this.zstd_free_ctx(ctx);
cpu.zstd_free_ctx(ctx);
}
else
{
@ -320,6 +327,6 @@ CPU.prototype.restore_state = function(state)
});
state_object = restore_buffers(state_object, buffers);
this.set_state(state_object);
cpu.set_state(state_object);
}
};
}

View file

@ -1,4 +1,10 @@
"use strict";
import { LOG_SERIAL } from "./const.js";
import { h } from "./lib.js";
import { dbg_log } from "./log.js";
// For Types Only
import { CPU } from "./cpu.js";
import { BusConnector } from "./bus.js";
/*
* Serial ports
@ -7,35 +13,36 @@
* https://www.freebsd.org/doc/en/articles/serial-uart/
*/
/** @const */
var DLAB = 0x80;
const DLAB = 0x80;
const UART_IER_MSI = 0x08; /* Modem Status Changed int. */
const UART_IER_THRI = 0x02; /* Enable Transmitter holding register int. */
const UART_IER_RDI = 0x01; /* Enable receiver data interrupt */
/** @const */ var UART_IER_MSI = 0x08; /* Modem Status Changed int. */
/** @const */ var UART_IER_THRI = 0x02; /* Enable Transmitter holding register int. */
/** @const */ var UART_IER_RDI = 0x01; /* Enable receiver data interrupt */
const UART_IIR_MSI = 0x00; /* Modem status interrupt (Low priority) */
const UART_IIR_NO_INT = 0x01;
const UART_IIR_THRI = 0x02; /* Transmitter holding register empty */
const UART_IIR_RDI = 0x04; /* Receiver data interrupt */
const UART_IIR_RLSI = 0x06; /* Receiver line status interrupt (High p.) */
const UART_IIR_CTI = 0x0c; /* Character timeout */
/** @const */var UART_IIR_MSI = 0x00; /* Modem status interrupt (Low priority) */
/** @const */var UART_IIR_NO_INT = 0x01;
/** @const */var UART_IIR_THRI = 0x02; /* Transmitter holding register empty */
/** @const */var UART_IIR_RDI = 0x04; /* Receiver data interrupt */
/** @const */var UART_IIR_RLSI = 0x06; /* Receiver line status interrupt (High p.) */
/** @const */var UART_IIR_CTI = 0x0c; /* Character timeout */
// Modem control register
const UART_MCR_LOOPBACK = 0x10;
/** @const */ var UART_LSR_DATA_READY = 0x1; // data available
/** @const */ var UART_LSR_TX_EMPTY = 0x20; // TX (THR) buffer is empty
/** @const */ var UART_LSR_TRANSMITTER_EMPTY = 0x40; // TX empty and line is idle
const UART_LSR_DATA_READY = 0x1; // data available
const UART_LSR_TX_EMPTY = 0x20; // TX (THR) buffer is empty
const UART_LSR_TRANSMITTER_EMPTY = 0x40; // TX empty and line is idle
// Modem status register
/** @const */ var UART_MSR_DCD = 0x7; // Data Carrier Detect
/** @const */ var UART_MSR_RI = 0x6; // Ring Indicator
/** @const */ var UART_MSR_DSR = 0x5; // Data Set Ready
/** @const */ var UART_MSR_CTS = 0x4; // Clear To Send
const UART_MSR_DCD = 0x7; // Data Carrier Detect
const UART_MSR_RI = 0x6; // Ring Indicator
const UART_MSR_DSR = 0x5; // Data Set Ready
const UART_MSR_CTS = 0x4; // Clear To Send
// Delta bits
/** @const */ var UART_MSR_DDCD = 0x3; // Delta DCD
/** @const */ var UART_MSR_TERI = 0x2; // Trailing Edge RI
/** @const */ var UART_MSR_DDSR = 0x1; // Delta DSR
/** @const */ var UART_MSR_DCTS = 0x0; // Delta CTS
const UART_MSR_DDCD = 0x3; // Delta DCD
const UART_MSR_TERI = 0x2; // Trailing Edge RI
const UART_MSR_DDSR = 0x1; // Delta DSR
const UART_MSR_DCTS = 0x0; // Delta CTS
/**
@ -44,7 +51,7 @@ var DLAB = 0x80;
* @param {number} port
* @param {BusConnector} bus
*/
function UART(cpu, port, bus)
export function UART(cpu, port, bus)
{
/** @const @type {BusConnector} */
this.bus = bus;
@ -395,7 +402,11 @@ UART.prototype.write_data = function(out_byte)
this.ThrowInterrupt(UART_IIR_THRI);
this.bus.send("serial" + this.com + "-output-byte", out_byte);
if(this.modem_control & UART_MCR_LOOPBACK) {
this.data_received(out_byte);
} else {
this.bus.send("serial" + this.com + "-output-byte", out_byte);
}
if(DEBUG)
{

View file

@ -1,4 +1,13 @@
"use strict";
import { LOG_VGA } from "./const.js";
import { h } from "./lib.js";
import { dbg_assert, dbg_log } from "./log.js";
// For Types Only
import { CPU } from "./cpu.js";
import { ScreenAdapter } from "./browser/screen.js";
import { BusConnector } from "./bus.js";
import { DummyScreenAdapter } from "./browser/dummy_screen.js";
import { round_up_to_next_power_of_2, view } from "./lib.js";
// Always 64k
const VGA_BANK_SIZE = 64 * 1024;
@ -34,7 +43,6 @@ const VGA_HOST_MEMORY_SPACE_START = Uint32Array.from([
]);
/**
* @const
* @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm#06}
*/
const VGA_HOST_MEMORY_SPACE_SIZE = Uint32Array.from([
@ -51,7 +59,7 @@ const VGA_HOST_MEMORY_SPACE_SIZE = Uint32Array.from([
* @param {ScreenAdapter|DummyScreenAdapter} screen
* @param {number} vga_memory_size
*/
function VGAScreen(cpu, bus, screen, vga_memory_size)
export function VGAScreen(cpu, bus, screen, vga_memory_size)
{
this.cpu = cpu;
@ -216,7 +224,7 @@ function VGAScreen(cpu, bus, screen, vga_memory_size)
else
{
// required for pci code
this.vga_memory_size = v86util.round_up_to_next_power_of_2(this.vga_memory_size);
this.vga_memory_size = round_up_to_next_power_of_2(this.vga_memory_size);
}
dbg_log("effective vga memory size: " + this.vga_memory_size, LOG_VGA);
@ -356,7 +364,7 @@ function VGAScreen(cpu, bus, screen, vga_memory_size)
const vga_offset = cpu.svga_allocate_memory(this.vga_memory_size) >>> 0;
this.svga_memory = v86util.view(Uint8Array, cpu.wasm_memory, vga_offset, this.vga_memory_size);
this.svga_memory = view(Uint8Array, cpu.wasm_memory, vga_offset, this.vga_memory_size);
this.diff_addr_min = this.vga_memory_size;
this.diff_addr_max = 0;
@ -566,7 +574,7 @@ VGAScreen.prototype.vga_memory_read = function(addr)
// VGA chip only decodes addresses within the selected memory space.
if(addr < 0 || addr >= VGA_HOST_MEMORY_SPACE_SIZE[memory_space_select])
{
dbg_log("vga read outside memory space: addr:" + h(addr), LOG_VGA);
dbg_log("vga read outside memory space: addr:" + h(addr >>> 0), LOG_VGA);
return 0;
}
@ -640,7 +648,7 @@ VGAScreen.prototype.vga_memory_write = function(addr, value)
if(addr < 0 || addr >= VGA_HOST_MEMORY_SPACE_SIZE[memory_space_select])
{
dbg_log("vga write outside memory space: addr:" + h(addr) + ", value:" + h(value), LOG_VGA);
dbg_log("vga write outside memory space: addr:" + h(addr >>> 0) + ", value:" + h(value), LOG_VGA);
return;
}
@ -1210,12 +1218,18 @@ VGAScreen.prototype.update_vga_size = function()
// should always 8 pixels per character clock (except for 8 bit PEL width, in which
// case 4 pixels).
var virtual_width = this.offset_register << 4;
var bpp = 4;
// Pixel Width / PEL Width / Clock Select
if(this.attribute_mode & 0x40)
{
screen_width >>>= 1;
virtual_width >>>= 1;
bpp = 8;
}
else if(this.attribute_mode & 0x2)
{
bpp = 1;
}
var screen_height = this.scan_line_to_screen_row(vertical_scans);
@ -1231,7 +1245,7 @@ VGAScreen.prototype.update_vga_size = function()
const bytes_per_line = this.vga_bytes_per_line();
const virtual_height = bytes_per_line ? Math.ceil(available_bytes / bytes_per_line) : screen_height;
this.set_size_graphical(screen_width, screen_height, virtual_width, virtual_height, 8);
this.set_size_graphical(screen_width, screen_height, virtual_width, virtual_height, bpp);
this.update_vertical_retrace();
this.update_layers();
@ -2198,7 +2212,7 @@ VGAScreen.prototype.port1CF_write = function(value)
}
else
{
dbg_log("SVGA: disabled");
dbg_log("SVGA: disabled", LOG_VGA);
}
if(this.svga_enabled && !was_enabled)
@ -2212,6 +2226,16 @@ VGAScreen.prototype.port1CF_write = function(value)
this.set_size_graphical(this.svga_width, this.svga_height, this.svga_width, this.svga_height, this.svga_bpp);
}
if(was_enabled && !this.svga_enabled)
{
const is_graphical = (this.attribute_mode & 0x1) !== 0;
this.graphical_mode = is_graphical;
this.screen.set_mode(is_graphical);
this.update_vga_size();
this.set_font_bitmap(false);
this.complete_redraw();
}
if(!this.svga_enabled)
{
this.svga_bank_offset = 0;

View file

@ -1,4 +1,10 @@
"use strict";
import { LOG_VIRTIO } from "./const.js";
import { h, int_log2 } from "./lib.js";
import { dbg_assert, dbg_log } from "./log.js";
// For Types Only
import { CPU } from "./cpu.js";
import { PCI } from "./pci.js";
// http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html
@ -32,9 +38,9 @@ const VIRTIO_ISR_DEVICE_CFG = 2;
// Feature bits (bit positions).
const VIRTIO_F_RING_INDIRECT_DESC = 28;
const VIRTIO_F_RING_EVENT_IDX = 29;
const VIRTIO_F_VERSION_1 = 32;
export const VIRTIO_F_RING_INDIRECT_DESC = 28;
export const VIRTIO_F_RING_EVENT_IDX = 29;
export const VIRTIO_F_VERSION_1 = 32;
// Queue struct sizes.
@ -153,7 +159,7 @@ var VirtIO_Options;
* @param {CPU} cpu
* @param {VirtIO_Options} options
*/
function VirtIO(cpu, options)
export function VirtIO(cpu, options)
{
const io = cpu.io;
@ -224,7 +230,7 @@ function VirtIO(cpu, options)
];
// Prevent sparse arrays by preallocating.
this.pci_space = this.pci_space.concat(v86util.zeros(256 - this.pci_space.length));
this.pci_space = this.pci_space.concat(Array(256 - this.pci_space.length).fill(0));
// Remaining PCI space is appended by capabilities further below.
this.pci_id = options.pci_id;
@ -480,7 +486,7 @@ VirtIO.prototype.create_common_capability = function(options)
dbg_log("Warning: dev<" + this.name +"> " +
"Given queue size was not a power of 2. " +
"Rounding up to next power of 2.", LOG_VIRTIO);
data = 1 << (v86util.int_log2(data - 1) + 1);
data = 1 << (int_log2(data - 1) + 1);
}
if(data > this.queue_selected.size_supported)
{
@ -727,7 +733,7 @@ VirtIO.prototype.init_capabilities = function(capabilities)
// Round up to next power of 2,
// Minimum 16 bytes for its size to be detectable in general (esp. mmio).
bar_size = bar_size < 16 ? 16 : 1 << (v86util.int_log2(bar_size - 1) + 1);
bar_size = bar_size < 16 ? 16 : 1 << (int_log2(bar_size - 1) + 1);
dbg_assert((cap.port & (bar_size - 1)) === 0,
"VirtIO device<" + this.name + "> capability port should be aligned to pci bar size");
@ -819,6 +825,13 @@ VirtIO.prototype.init_capabilities = function(capabilities)
return read(addr & ~3) >> ((addr & 3) << 3) & 0xFF;
};
// archhurd does these reads
const shim_read32_on_16 = function(addr)
{
dbg_log("Warning: 32-bit read from 16-bit virtio port", LOG_VIRTIO);
return read(addr);
};
switch(field.bytes)
{
case 4:
@ -829,7 +842,7 @@ VirtIO.prototype.init_capabilities = function(capabilities)
this.cpu.io.register_write(port, this, undefined, undefined, write);
break;
case 2:
this.cpu.io.register_read(port, this, shim_read8_on_16, read);
this.cpu.io.register_read(port, this, shim_read8_on_16, read, shim_read32_on_16);
this.cpu.io.register_read(port + 1, this, shim_read8_on_16);
this.cpu.io.register_write(port, this, undefined, write);
break;
@ -1071,6 +1084,7 @@ VirtQueue.prototype.get_state = function()
state[6] = this.avail_last_idx;
state[7] = this.used_addr;
state[8] = this.num_staged_replies;
state[9] = 1;
return state;
};
@ -1088,6 +1102,7 @@ VirtQueue.prototype.set_state = function(state)
this.num_staged_replies = state[8];
this.mask = this.size - 1;
this.fix_wrapping = state[9] !== 1;
};
VirtQueue.prototype.reset = function()
@ -1126,6 +1141,10 @@ VirtQueue.prototype.set_size = function(size)
VirtQueue.prototype.count_requests = function()
{
dbg_assert(this.avail_addr, "VirtQueue addresses must be configured before use");
if(this.fix_wrapping) {
this.fix_wrapping = false;
this.avail_last_idx = (this.avail_get_idx() & ~this.mask) + (this.avail_last_idx & this.mask);
}
return (this.avail_get_idx() - this.avail_last_idx) & 0xFFFF;
};

View file

@ -1,7 +1,14 @@
"use strict";
// https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-2900003
import { LOG_PCI } from "./const.js";
import { dbg_log } from "./log.js";
import { VirtIO, VIRTIO_F_VERSION_1 } from "./virtio.js";
import * as marshall from "../lib/marshall.js";
// For Types Only
import { CPU } from "./cpu.js";
import { BusConnector } from "./bus.js";
const VIRTIO_BALLOON_F_MUST_TELL_HOST = 0;
const VIRTIO_BALLOON_F_STATS_VQ = 1;
const VIRTIO_BALLOON_F_DEFLATE_ON_OOM = 2;
@ -25,7 +32,7 @@ const STAT_NAMES = [
* @param {CPU} cpu
* @param {BusConnector} bus
*/
function VirtioBalloon(cpu, bus)
export function VirtioBalloon(cpu, bus)
{
/** @const @type {BusConnector} */
this.bus = bus;

View file

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

View file

@ -1,7 +1,15 @@
"use strict";
// https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-2900003
import { dbg_assert } from "./log.js";
import { VirtIO, VIRTIO_F_VERSION_1 } from "./virtio.js";
import { format_mac } from "./ne2k.js";
import * as marshall from "../lib/marshall.js";
// For Types Only
import { CPU } from "./cpu.js";
import { BusConnector } from "./bus.js";
const MTU_DEFAULT = 1500;
const VIRTIO_NET_F_MAC = 5;
const VIRTIO_NET_F_CTRL_VQ = 17;
@ -18,8 +26,9 @@ const VIRTIO_NET_CTRL_MAC_ADDR_SET = 1;
* @param {CPU} cpu
* @param {BusConnector} bus
* @param {Boolean} preserve_mac_from_state_image
* @param {number} mtu
*/
function VirtioNet(cpu, bus, preserve_mac_from_state_image)
export function VirtioNet(cpu, bus, preserve_mac_from_state_image, mtu = MTU_DEFAULT)
{
/** @const @type {BusConnector} */
this.bus = bus;
@ -171,7 +180,7 @@ function VirtioNet(cpu, bus, preserve_mac_from_state_image)
{
bytes: 2,
name: "mtu",
read: () => 1500,
read: () => mtu,
write: data => {},
}
])

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 (libv86-debug.js, v86-debug.wasm)
debug build (source files with v86-debug.wasm)
- `MAX_PARALLEL_TESTS=n`: Maximum number of tests to run in parallel. Defaults
to the number of cores in your system or less.
- `TEST_NAME="…"`: Run only the specified test (only expect, full, nasm)

87
tests/api/2g-mem.js Executable file
View file

@ -0,0 +1,87 @@
#!/usr/bin/env node
import url from "node:url";
import fs from "node:fs";
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD;
const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js");
process.on("unhandledRejection", exn => { throw exn; });
const config = {
bios: { url: __dirname + "/../../bios/seabios.bin" },
vga_bios: { url: __dirname + "/../../bios/vgabios.bin" },
bzimage: { url: __dirname + "/../../images/buildroot-bzimage68.bin" },
network_relay_url: "<UNUSED>",
autostart: true,
memory_size: 2 * 1024 * 1024 * 1024,
filesystem: {},
log_level: 0,
disable_jit: +process.env.DISABLE_JIT,
};
const emulator = new V86(config);
emulator.bus.register("emulator-started", function()
{
console.log("Booting now, please stand by");
emulator.create_file("test.lua", Buffer.from(`
local t = {}
local m = 1
while collectgarbage("count") < 1.8 * 1024 * 1024 do
t[m] = string.rep("A", 4096)
m = m + 1
if m % 10000 == 0 then
print(m, " ", collectgarbage("count"))
end
end
print("memory usage (kB) ", collectgarbage("count"))
print("page count ", m)
local ref = string.rep("A", 4096)
for i = 1, m - 1 do
assert(t[i] == ref)
end
print("ok")
`));
});
let ran_command = false;
let line = "";
emulator.add_listener("serial0-output-byte", async function(byte)
{
const chr = String.fromCharCode(byte);
if(chr < " " && chr !== "\n" && chr !== "\t" || chr > "~")
{
return;
}
if(chr === "\n")
{
var new_line = line;
console.error("Serial: %s", line);
line = "";
}
else if(chr >= " " && chr <= "~")
{
line += chr;
}
if(!ran_command && line.endsWith("~% "))
{
ran_command = true;
emulator.serial0_send("free -m\n");
emulator.serial0_send("time -v lua /mnt/test.lua\n");
emulator.serial0_send("echo test fini''shed\n");
}
if(chr === "\n" && new_line.startsWith("test finished"))
{
emulator.destroy();
}
});

View file

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

View file

@ -1,13 +1,13 @@
#!/usr/bin/env node
"use strict";
import url from "node:url";
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
// This test checks that calling emulator.destroy() will remove all event
// listeners, so that the nodejs process cleanly and automatically exits.
const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD;
const fs = require("fs");
var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86;
const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js");
process.on("unhandledRejection", exn => { throw exn; });

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