newest versions from Fabrice Bellard

This commit is contained in:
Jeff Carr 2025-05-30 02:57:36 -05:00
parent 5e4018a0c0
commit 53ccdd71af
6 changed files with 1953 additions and 419 deletions

View File

@ -5,3 +5,6 @@ wget-javascript:
rm -f *.js rm -f *.js
wget -c https://bellard.org/jslinux/term.js wget -c https://bellard.org/jslinux/term.js
wget -c https://bellard.org/jslinux/jslinux.js wget -c https://bellard.org/jslinux/jslinux.js
wget -c https://bellard.org/jslinux/x86emu-wasm.js
wget -c https://bellard.org/jslinux/riscvemu64-wasm.js
wget -c https://bellard.org/jslinux/riscvemu32-wasm.js

View File

@ -1,126 +1,661 @@
/* /*
Linux launcher * JS Linux main
*
Copyright (c) 2011-2012 Fabrice Bellard * Copyright (c) 2017 Fabrice Bellard
*
Redistribution or commercial use is prohibited without the author's * Permission is hereby granted, free of charge, to any person obtaining a copy
permission. * 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.
*/
"use strict"; "use strict";
var term, pc, boot_start_time, init_state; var term, console_write1, console_resize_event;
var graphic_display, display_key_event, display_mouse_event;
var net_state, net_write_packet, net_set_carrier;
var display_wheel_event;
var fs_import_file;
var Module = {};
var downloading_timer_pending = false;
var downloading_timer;
function term_start() function on_update_file(f)
{ {
term = new Term(80, 30, term_handler); var f, reader;
reader = new FileReader();
term.open(); reader.onload = function (ev) {
var buf, buf_addr, buf_len;
buf = new Uint8Array(reader.result);
buf_len = buf.length;
buf_addr = _malloc(buf_len);
HEAPU8.set(buf, buf_addr);
/* the buffer is freed by the function */
fs_import_file(f.name, buf_addr, buf_len);
};
reader.readAsArrayBuffer(f);
}
function on_update_files(files)
{
var i, n;
n = files.length;
for(i = 0; i < n; i++) {
on_update_file(files[i]);
}
} }
/* send chars to the serial port */
function term_handler(str) function term_handler(str)
{ {
pc.serial.send_chars(str); var i;
for(i = 0; i < str.length; i++) {
console_write1(str.charCodeAt(i));
}
} }
function clipboard_set(val) function downloading_timer_cb()
{
var el = document.getElementById("net_progress");
el.style.visibility = "hidden";
downloading_timer_pending = false;
}
function update_downloading(flag)
{ {
var el; var el;
el = document.getElementById("text_clipboard"); if (flag) {
el.value = val; if (downloading_timer_pending) {
clearTimeout(downloading_timer);
downloading_timer_pending = false;
} else {
el = document.getElementById("net_progress");
el.style.visibility = "visible";
}
} else {
downloading_timer_pending = true;
downloading_timer = setTimeout(downloading_timer_cb, 500);
}
} }
function clipboard_get() function get_params()
{ {
var el; var url, query_str, p, tab, i, params, tab2;
el = document.getElementById("text_clipboard"); query_str = window.location.href;
return el.value; p = query_str.indexOf("?");
if (p < 0)
return {};
query_str = query_str.substr(p + 1);
tab = query_str.split("&");
params = {};
for(i = 0; i < tab.length; i++) {
tab2 = tab[i].split("=");
params[decodeURIComponent(tab2[0])] = decodeURIComponent(tab2[1]);
}
return params;
} }
function clear_clipboard() function get_absolute_url(fname)
{ {
var el; var path, p;
el = document.getElementById("text_clipboard");
el.value = "";
}
/* just used to display the boot time in the VM */
function get_boot_time()
{
return (+new Date()) - boot_start_time;
}
function start()
{
var params;
init_state = new Object(); if (fname.indexOf(":") >= 0)
return fname;
params = new Object(); path = window.location.pathname;
p = path.lastIndexOf("/");
/* serial output chars */ if (p < 0)
params.serial_write = term.write.bind(term); return fname;
return window.location.origin + path.slice(0, p + 1) + fname;
/* memory size (in bytes) */
params.mem_size = 31 * 1024 * 1024;
/* clipboard I/O */
params.clipboard_get = clipboard_get;
params.clipboard_set = clipboard_set;
params.get_boot_time = get_boot_time;
/* IDE drive. The raw disk image is split into files of
* 'block_size' KB.
*/
//params.hda = { url: "hda%d.bin", block_size: 64, nb_blocks: 912 };
params.hda = { url: "bin/hda%d.bin", block_size: 64, nb_blocks: 912 };
//modify by hao
pc = new PCEmulator(params);
init_state.params = params;
pc.load_binary("vmlinux-2.6.20.bin", 0x00100000, start2);
} }
function start2(ret) function GraphicDisplay(parent_el, width, height)
{ {
if (ret < 0) this.width = width;
return; this.height = height;
init_state.start_addr = 0x10000;
pc.load_binary("linuxstart.bin", init_state.start_addr, start3); this.canvas_el = document.createElement("canvas");
this.canvas_el.width = width; /* logical width */
this.canvas_el.height = height; /* logical width */
/* displayed size */
this.canvas_el.style.width = width + "px";
this.canvas_el.style.height = height + "px";
this.canvas_el.style.cursor = "none";
parent_el.appendChild(this.canvas_el);
this.ctx = this.canvas_el.getContext("2d");
/* clear the display */
this.ctx.fillStyle = "#000000";
this.ctx.fillRect(0, 0, width, height);
this.image = this.ctx.createImageData(width, height);
this.key_pressed = new Uint8Array(128);
document.addEventListener("keydown",
this.keyDownHandler.bind(this), false);
document.addEventListener("keyup",
this.keyUpHandler.bind(this), false);
document.addEventListener("blur",
this.blurHandler.bind(this), false);
this.canvas_el.onmousedown = this.mouseMoveHandler.bind(this);
this.canvas_el.onmouseup = this.mouseMoveHandler.bind(this);
this.canvas_el.onmousemove = this.mouseMoveHandler.bind(this);
this.canvas_el.oncontextmenu = this.onContextMenuHandler.bind(this);
this.canvas_el.onwheel = this.wheelHandler.bind(this);
} }
function start3(ret) GraphicDisplay.code_to_input_map = {
"Escape": 0x01,
"Digit1": 0x02,
"Digit2": 0x03,
"Digit3": 0x04,
"Digit4": 0x05,
"Digit5": 0x06,
"Digit6": 0x07,
"Digit7": 0x08,
"Digit8": 0x09,
"Digit9": 0x0a,
"Digit0": 0x0b,
"Minus": 0x0c,
"Equal": 0x0d,
"Backspace": 0x0e,
"Tab": 0x0f,
"KeyQ": 0x10,
"KeyW": 0x11,
"KeyE": 0x12,
"KeyR": 0x13,
"KeyT": 0x14,
"KeyY": 0x15,
"KeyU": 0x16,
"KeyI": 0x17,
"KeyO": 0x18,
"KeyP": 0x19,
"BracketLeft": 0x1a,
"BracketRight": 0x1b,
"Enter": 0x1c,
"ControlLeft": 0x1d,
"KeyA": 0x1e,
"KeyS": 0x1f,
"KeyD": 0x20,
"KeyF": 0x21,
"KeyG": 0x22,
"KeyH": 0x23,
"KeyJ": 0x24,
"KeyK": 0x25,
"KeyL": 0x26,
"Semicolon": 0x27,
"Quote": 0x28,
"Backquote": 0x29,
"ShiftLeft": 0x2a,
"Backslash": 0x2b,
"KeyZ": 0x2c,
"KeyX": 0x2d,
"KeyC": 0x2e,
"KeyV": 0x2f,
"KeyB": 0x30,
"KeyN": 0x31,
"KeyM": 0x32,
"Comma": 0x33,
"Period": 0x34,
"Slash": 0x35,
"ShiftRight": 0x36,
"NumpadMultiply": 0x37,
"AltLeft": 0x38,
"Space": 0x39,
"CapsLock": 0x3a,
"F1": 0x3b,
"F2": 0x3c,
"F3": 0x3d,
"F4": 0x3e,
"F5": 0x3f,
"F6": 0x40,
"F7": 0x41,
"F8": 0x42,
"F9": 0x43,
"F10": 0x44,
"NumLock": 0x45,
"ScrollLock": 0x46,
"Numpad7": 0x47,
"Numpad8": 0x48,
"Numpad9": 0x49,
"NumpadSubtract": 0x4a,
"Numpad4": 0x4b,
"Numpad5": 0x4c,
"Numpad6": 0x4d,
"NumpadAdd": 0x4e,
"Numpad1": 0x4f,
"Numpad2": 0x50,
"Numpad3": 0x51,
"Numpad0": 0x52,
"NumpadDecimal": 0x53,
"IntlBackslash": 0x56,
"F11": 0x57,
"F12": 0x58,
"NumpadEnter": 96,
"ControlRight": 97,
"NumpadDivide": 98,
"AltRight": 100,
"Home": 102,
"ArrowUp": 103,
"PageUp": 104,
"ArrowLeft": 105,
"ArrowRight": 106,
"End": 107,
"ArrowDown": 108,
"PageDown": 109,
"Insert": 110,
"Delete": 111,
"OSLeft": 125,
"OSRight": 126,
"ContextMenu": 127,
};
GraphicDisplay.key_code_to_input_map = new Uint8Array([
0, 0, 0, 0, 0, 0, 0, 0,
0x0E, 0x0F, 0, 0, 0, 0x1C, 0, 0,
0x2A, 0x1D, 0x38, 0, 0x3A, 0, 0, 0, /* 0x10 */
0, 0, 0, 0x01, 0, 0, 0, 0,
0x39, 104, 109, 107, 102, 105, 103, 106, /* 0x20 */
0x50, 0, 0, 0, 0, 0x52, 0x53, 0,
0x0B, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, /* 0x30 */
0x09, 0x0A, 0, 0x27, 0, 0x0D, 0, 0,
0, 0x1E, 0x30, 0x2E, 0x20, 0x12, 0x21, 0x22, /* 0x40 */
0x23, 0x17, 0x24, 0x25, 0x26, 0x32, 0x31, 0x18,
0x19, 0x10, 0x13, 0x1F, 0x14, 0x16, 0x2F, 0x11, /* 0x50 */
0x2D, 0x15, 0x2C, 125, 126, 127, 0, 0,
0x52, 0x4F, 0x50, 0x51, 0x4B, 0x4C, 0x4D, 0x47, /* 0x60 */
0x48, 0x49, 0x37, 0x4e, 0, 0x4a, 0x53, 98,
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, /* 0x70 */
0x43, 0x44, 0x57, 0x58, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 */
0, 0, 0, 0, 0, 0, 0, 0,
0x45, 0, 0, 0, 0, 0, 0, 0, /* 0x90 */
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 */
0, 0, 0, 0, 0, 0x0C, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 */
0, 0, 0x27, 0x0D, 0x33, 0x0C, 0x34, 0x35,
0x29, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 */
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 */
0, 0, 0, 0x1A, 0x2B, 0x1B, 0x28, 0,
125, 100, 0, 0, 0, 0, 0, 0, /* 0xe0 */
0, 0, 0, 0, 0, 0, 0, 0,
]);
GraphicDisplay.prototype.keyHandler = function keyHandler(ev, isDown)
{ {
var block_list; var code, input_key_code;
if (ret < 0)
return; /* At least avoid exiting the navigator if Ctrl-Q or Ctrl-W are
/* Preload blocks so that the boot time does not depend on the * pressed */
* time to load the required disk data (optional) */ if (ev.ctrlKey) {
block_list = [ 0, 7, 3, 643, 720, 256, 336, 644, 781, 387, 464, 475, 131, 589, 468, 472, 474, 776, 777, 778, 779, 465, 466, 473, 467, 469, 470, 512, 592, 471, 691, 697, 708, 792, 775, 769 ]; window.onbeforeunload = function() {
pc.ide0.drives[0].bs.preload(block_list, start4); window.onbeforeunload = null;
return "CTRL-W or Ctrl-Q cannot be sent to the emulator.";
};
} else {
window.onbeforeunload = null;
}
if (typeof ev.code != "undefined") {
code = ev.code;
input_key_code = GraphicDisplay.code_to_input_map[code];
if (typeof input_key_code != "undefined") {
// console.log("code=" + code + " isDown=" + isDown + " input_key_code=" + input_key_code);
this.key_pressed[input_key_code] = isDown;
display_key_event(isDown, input_key_code);
if (ev.stopPropagation)
ev.stopPropagation();
if (ev.preventDefault)
ev.preventDefault();
return false;
}
} else {
/* fallback using keyCodes. Works only with an US keyboard */
code = ev.keyCode;
if (code < 256) {
input_key_code = GraphicDisplay.key_code_to_input_map[code];
// console.log("keyCode=" + code + " isDown=" + isDown + " input_key_code=" + input_key_code);
if (input_key_code) {
this.key_pressed[input_key_code] = isDown;
display_key_event(isDown, input_key_code);
if (ev.stopPropagation)
ev.stopPropagation();
if (ev.preventDefault)
ev.preventDefault();
return false;
}
}
}
return true;
} }
function start4(ret) GraphicDisplay.prototype.keyDownHandler = function keyDownHandler(ev)
{ {
var cmdline_addr; return this.keyHandler(ev, 1);
if (ret < 0)
return;
/* set the Linux kernel command line */
cmdline_addr = 0xf800;
pc.cpu.write_string(cmdline_addr, "console=ttyS0 root=/dev/hda ro init=/sbin/init notsc=1 hdb=none");
pc.cpu.set_reg(8, init_state.start_addr); /* eip */
pc.cpu.set_reg(0, init_state.params.mem_size); /* eax */
pc.cpu.set_reg(3, 0); /* ebx = initrd_size (no longer used) */
pc.cpu.set_reg(1, cmdline_addr); /* ecx */
boot_start_time = (+new Date());
pc.start();
} }
term_start(); GraphicDisplay.prototype.keyUpHandler = function keyUpHandler(ev)
{
return this.keyHandler(ev, 0);
}
GraphicDisplay.prototype.blurHandler = function blurHandler(ev, isDown)
{
var i, n, key_pressed;
/* allow unloading the page */
window.onbeforeunload = null;
/* release all keys */
key_pressed = this.key_pressed;
for(i = 0; i < key_pressed.length; i++) {
if (key_pressed[i]) {
display_key_event(0, i);
key_pressed[i] = 0;
}
}
}
GraphicDisplay.prototype.mouseMoveHandler = function (ev)
{
var x, y, rect, buttons;
rect = this.canvas_el.getBoundingClientRect();
x = ev.clientX - rect.left;
y = ev.clientY - rect.top;
buttons = ev.buttons & 7;
// console.log("mouse: x=" + x + " y=" + y + " buttons=" + buttons);
display_mouse_event(x, y, buttons);
if (ev.stopPropagation)
ev.stopPropagation();
if (ev.preventDefault)
ev.preventDefault();
return false;
}
GraphicDisplay.prototype.wheelHandler = function (ev)
{
if (ev.deltaY < 0) {
display_wheel_event(1);
} else if (ev.deltaY > 0) {
display_wheel_event(-1);
}
if (ev.stopPropagation)
ev.stopPropagation();
if (ev.preventDefault)
ev.preventDefault();
}
/* disable contextual menu */
GraphicDisplay.prototype.onContextMenuHandler = function (ev)
{
if (ev.stopPropagation)
ev.stopPropagation();
if (ev.preventDefault)
ev.preventDefault();
return false;
}
/* Network support */
function Ethernet(url)
{
try {
this.socket = new WebSocket(url);
} catch(err) {
this.socket = null;
console.log("Could not open websocket url=" + url);
return;
}
this.socket.binaryType = 'arraybuffer';
this.socket.onmessage = this.messageHandler.bind(this);
this.socket.onclose = this.closeHandler.bind(this);
this.socket.onopen = this.openHandler.bind(this);
this.socket.onerror = this.errorHandler.bind(this);
}
Ethernet.prototype.openHandler = function(e)
{
net_set_carrier(1);
}
Ethernet.prototype.closeHandler = function(e)
{
net_set_carrier(0);
}
Ethernet.prototype.errorHandler = function(e)
{
console.log("Websocket error=" + e);
}
Ethernet.prototype.messageHandler = function(e)
{
var str, buf_len, buf_addr, buf;
if (e.data instanceof ArrayBuffer) {
buf_len = e.data.byteLength;
buf = new Uint8Array(e.data);
buf_addr = _malloc(buf_len);
HEAPU8.set(buf, buf_addr);
net_write_packet(buf_addr, buf_len);
_free(buf_addr);
} else {
str = e.data.toString();
if (str.substring(0, 5) == "ping:") {
try {
this.socket.send('pong:' + str.substring(5));
} catch (err) {
}
}
}
}
Ethernet.prototype.recv_packet = function(buf)
{
if (this.socket) {
try {
this.socket.send(buf);
} catch (err) {
}
}
}
function start_vm(user, pwd)
{
var url, mem_size, cpu, params, vm_url, cmdline, cols, rows, guest_url;
var font_size, graphic_enable, width, height, net_url, alloc_size;
var drive_url, vm_file;
function loadScript(src, f) {
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.src = src;
var done = false;
script.onload = script.onreadystatechange = function() {
// attach to both events for cross browser finish detection:
if ( !done && (!this.readyState ||
this.readyState == "loaded" || this.readyState == "complete") ) {
done = true;
if (f) {
f();
}
script.onload = script.onreadystatechange = null;
head.removeChild(script);
}
};
head.appendChild(script);
}
function start()
{
/* C functions called from javascript */
console_write1 = Module.cwrap('console_queue_char', null, ['number']);
console_resize_event = Module.cwrap('console_resize_event', null, []);
fs_import_file = Module.cwrap('fs_import_file', null, ['string', 'number', 'number']);
display_key_event = Module.cwrap('display_key_event', null, ['number', 'number']);
display_mouse_event = Module.cwrap('display_mouse_event', null, ['number', 'number', 'number']);
display_wheel_event = Module.cwrap('display_wheel_event', null, ['number']);
net_write_packet = Module.cwrap('net_write_packet', null, ['number', 'number']);
net_set_carrier = Module.cwrap('net_set_carrier', null, ['number']);
net_state = null;
if (net_url != "") {
net_state = new Ethernet(net_url);
}
Module.ccall("vm_start", null, ["string", "number", "string", "string", "number", "number", "number", "string"], [url, mem_size, cmdline, pwd, width, height, (net_state != null) | 0, drive_url]);
pwd = null;
}
function term_wrap_onclick_handler()
{
var term_wrap_el, w, h, term_bar_el, bar_h;
term_wrap_el = document.getElementById("term_wrap");
term_bar_el = document.getElementById("term_bar");
w = term_wrap_el.clientWidth;
h = term_wrap_el.clientHeight;
bar_h = term_bar_el.clientHeight;
if (term.resizePixel(w, h - bar_h)) {
console_resize_event();
}
}
/* read the parameters */
params = get_params();
cpu = params["cpu"] || "x86";
url = params["url"];
url = get_absolute_url(url);
mem_size = (params["mem"] | 0) || 128; /* in mb */
cmdline = params["cmdline"] || "";
cols = (params["cols"] | 0) || 80;
rows = (params["rows"] | 0) || 30;
font_size = (params["font_size"] | 0) || 15;
guest_url = params["guest_url"] || "";
width = (params["w"] | 0) || 1024;
height = (params["h"] | 0) || 640;
graphic_enable = params["graphic"] | 0;
net_url = params["net_url"]; /* empty string means no network */
if (typeof net_url == "undefined")
net_url = "wss://relay.widgetry.org/";
drive_url = params["drive_url"] || "";
if (user) {
cmdline += " LOGIN_USER=" + user;
} else if (guest_url) {
cmdline += " GUEST_URL=" + guest_url;
}
if (graphic_enable) {
graphic_display = new GraphicDisplay(document.getElementById("term_container"), width, height);
} else {
var term_wrap_el;
width = 0;
height = 0;
/* start the terminal */
term = new Term({ cols: cols, rows: rows, scrollback: 10000, fontSize: font_size });
term.setKeyHandler(term_handler);
term.open(document.getElementById("term_container"),
document.getElementById("term_paste"));
term_wrap_el = document.getElementById("term_wrap")
term_wrap_el.style.width = term.term_el.style.width;
term_wrap_el.onclick = term_wrap_onclick_handler;
term.write("Loading...\r\n");
}
// console.log("cpu=" + cpu + " url=" + url + " mem=" + mem_size);
switch(cpu) {
case "x86":
vm_file = "x86emu";
break;
case "riscv64":
case "riscv":
vm_file = "riscvemu64";
break;
case "riscv32":
vm_file = "riscvemu32";
break;
default:
term.writeln("Unknown cpu=" + cpu);
return;
}
if (typeof WebAssembly === "object") {
/* wasm support : the memory grows automatically */
vm_url = vm_file + "-wasm.js";
} else {
/* set the total memory */
alloc_size = mem_size;
if (cpu == "x86")
alloc_size += 16;
if (graphic_enable) {
/* frame buffer memory */
alloc_size += (width * height * 4 + 1048576 - 1) >> 20;
}
alloc_size += 32; /* extra space (XXX: reduce it ?) */
alloc_size = (alloc_size + 15) & -16; /* align to 16 MB */
Module.TOTAL_MEMORY = alloc_size << 20;
vm_url = vm_file + ".js";
}
Module.preRun = start;
loadScript(vm_url, null);
}
function on_login()
{
var login_wrap_el = document.getElementById("wrap");
var term_wrap_el = document.getElementById("term_wrap");
var form = document.getElementById("form");
var status = document.getElementById("status");
var user = form.user.value;
var pwd = form.password.value;
if (user.length <= 1) {
status.innerHTML = "User name must be provided";
return false;
}
login_wrap_el.style.display = "none";
term_wrap_el.style.display = "block";
form.password.value = "";
form.user.value = "";
start_vm(user, pwd);
return false;
}
(function() {
var login, params;
params = get_params();
login = params["login"] || 0;
if (login) {
var login_wrap_el = document.getElementById("wrap");
login_wrap_el.style.display = "block";
} else {
var term_wrap_el = document.getElementById("term_wrap");
term_wrap_el.style.display = "block";
start_vm(null, null);
}
})();

4
riscvemu32-wasm.js Normal file

File diff suppressed because one or more lines are too long

4
riscvemu64-wasm.js Normal file

File diff suppressed because one or more lines are too long

1630
term.js

File diff suppressed because it is too large Load Diff

4
x86emu-wasm.js Normal file

File diff suppressed because one or more lines are too long