initial version of virtio and 9p filesystem support; props to the jor1k project for donating some code

This commit is contained in:
copy 2014-12-02 19:01:13 +01:00
parent 1f3d42499f
commit 4a63b1f70c
10 changed files with 2111 additions and 7 deletions

View file

@ -38,14 +38,15 @@ CLOSURE_FLAGS=\
CORE_FILES=const.js io.js main.js fpu.js ide.js pci.js floppy.js memory.js\
dma.js pit.js vga.js ps2.js pic.js rtc.js uart.js hpet.js acpi.js\
cpu_state.js ne2k.js
cpu_state.js ne2k.js virtio.js
LIB_FILES=../lib/9p.js ../lib/filesystem.js ../lib/jor1k.js ../lib/marshall.js ../lib/utf8.js
BROWSER_FILES=browser/main.js browser/screen.js\
browser/keyboard.js browser/mouse.js browser/serial.js\
browser/network.js browser/lib.js
NODE_FILES=node/main.js node/keyboard_sdl.js\
node/screen_sdl.js node/keyboard_tty.js node/screen_tty.js
build/v86_all.js: src/*.js src/browser/*.js build/cpu.js
build/v86_all.js: src/*.js src/browser/*.js build/cpu.js lib/*.js
-ls -lh build/v86_all.js
cd src &&\
java -jar $(CLOSURE) \

View file

@ -27,6 +27,7 @@ v86 emulates an x86-compatible CPU and hardware. Here's a list of emulated hardw
- A PCI bus. This one is partly incomplete and not used by every device.
- An IDE disk controller.
- An NE2000 (8390) PCI network card.
- A virtio filesystem.
How to build, run and embed?
@ -100,6 +101,7 @@ Credits
- https://github.com/creationix/node-sdl
- ascii.ttf (used in node) from http://www.apollosoft.de/ASCII/indexen.htm
- [Disk Images](https://github.com/copy/images)
- [The jor1k project](https://github.com/s-macke/jor1k) for 9p and filesystem drivers
More questions?

569
lib/9p.js Normal file
View file

@ -0,0 +1,569 @@
// -------------------------------------------------
// --------------------- 9P ------------------------
// -------------------------------------------------
// Implementation of the 9p filesystem device following the
// 9P2000.L protocol ( https://code.google.com/p/diod/wiki/protocol )
"use strict";
// TODO
// flush
// lock?
// correct hard links
var EPERM = 1; /* Operation not permitted */
var ENOENT = 2; /* No such file or directory */
var EINVAL = 22; /* Invalid argument */
var ENOTSUPP = 524; /* Operation is not supported */
var ENOTEMPTY = 39; /* Directory not empty */
var EPROTO = 71 /* Protocol error */
var P9_SETATTR_MODE = 0x00000001;
var P9_SETATTR_UID = 0x00000002;
var P9_SETATTR_GID = 0x00000004;
var P9_SETATTR_SIZE = 0x00000008;
var P9_SETATTR_ATIME = 0x00000010;
var P9_SETATTR_MTIME = 0x00000020;
var P9_SETATTR_CTIME = 0x00000040;
var P9_SETATTR_ATIME_SET = 0x00000080;
var P9_SETATTR_MTIME_SET = 0x00000100;
var P9_STAT_MODE_DIR = 0x80000000;
var P9_STAT_MODE_APPEND = 0x40000000;
var P9_STAT_MODE_EXCL = 0x20000000;
var P9_STAT_MODE_MOUNT = 0x10000000;
var P9_STAT_MODE_AUTH = 0x08000000;
var P9_STAT_MODE_TMP = 0x04000000;
var P9_STAT_MODE_SYMLINK = 0x02000000;
var P9_STAT_MODE_LINK = 0x01000000;
var P9_STAT_MODE_DEVICE = 0x00800000;
var P9_STAT_MODE_NAMED_PIPE = 0x00200000;
var P9_STAT_MODE_SOCKET = 0x00100000;
var P9_STAT_MODE_SETUID = 0x00080000;
var P9_STAT_MODE_SETGID = 0x00040000;
var P9_STAT_MODE_SETVTX = 0x00010000;
var FID_NONE = -1;
var FID_INODE = 1;
var FID_XATTR = 2;
/** @constructor */
function Virtio9p(filesystem) {
this.fs = filesystem;
this.SendReply = function(x) {};
this.deviceid = 0x9; // 9p filesystem
this.hostfeature = 0x1; // mountpoint
//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 = [0x6, 0x0, 0x68, 0x6F, 0x73, 0x74, 0x39, 0x70]; // length of string and "host9p" string
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.fid2inode = [];
this.fidtype = [];
this._state_skip = ["fs", "SendReply"];
}
Virtio9p.prototype.Reset = function() {
this.fid2inode = [];
this.fidtype = [];
}
Virtio9p.prototype.BuildReply = function(id, tag, payloadsize) {
Marshall(["w", "b", "h"], [payloadsize+7, id+1, tag], this.replybuffer, 0);
if ((payloadsize+7) >= this.replybuffer.length) {
DebugMessage("Error in 9p: payloadsize exceeds maximum length");
}
//for(var i=0; i<payload.length; i++)
// this.replybuffer[7+i] = payload[i];
this.replybuffersize = payloadsize+7;
return;
}
Virtio9p.prototype.SendError = function (tag, errormsg, errorcode) {
//var size = Marshall(["s", "w"], [errormsg, errorcode], this.replybuffer, 7);
var size = Marshall(["w"], [errorcode], this.replybuffer, 7);
this.BuildReply(6, tag, size);
}
Virtio9p.prototype.ReceiveRequest = function (index, GetByte) {
var header = Unmarshall2(["w", "b", "h"], GetByte);
//var size = header[0];
var id = header[1];
var tag = header[2];
//DebugMessage("size:" + size + " id:" + id + " tag:" + tag);
switch(id)
{
case 8: // statfs
var size = this.fs.GetTotalSize();
var req = [];
req[0] = 0x01021997;
req[1] = this.BLOCKSIZE; // optimal transfer block size
req[2] = Math.floor(1024*1024*1024/req[1]); // free blocks, let's say 1GB
req[3] = req[2] - Math.floor(size/req[1]); // free blocks in fs
req[4] = req[2] - Math.floor(size/req[1]); // free blocks avail to non-superuser
req[5] = this.fs.inodes.length; // total number of inodes
req[6] = 1024*1024;
req[7] = 0; // file system id?
req[8] = 256; // maximum length of filenames
size = Marshall(["w", "w", "d", "d", "d", "d", "d", "d", "w"], req, this.replybuffer, 7);
this.BuildReply(id, tag, size);
this.SendReply(index);
break;
case 112: // topen
case 12: // tlopen
var req = Unmarshall2(["w", "w"], GetByte);
var fid = req[0];
var mode = req[1];
DebugMessage("[open] fid=" + fid + ", mode=" + mode);
var inode = this.fs.GetInode(this.fid2inode[fid]);
req[0] = inode.qid;
req[1] = this.msize - 24;
Marshall(["Q", "w"], req, this.replybuffer, 7);
this.BuildReply(id, tag, 13+4);
DebugMessage("file open " + inode.name);
//if (inode.status == STATUS_LOADING) return;
var ret = this.fs.OpenInode(this.fid2inode[fid], mode);
this.fs.AddEvent(this.fid2inode[fid],
function() {
DebugMessage("file opened " + inode.name + " tag:"+tag);
req[0] = inode.qid;
req[1] = this.msize - 24;
Marshall(["Q", "w"], req, this.replybuffer, 7);
this.BuildReply(id, tag, 13+4);
this.SendReply(index);
}.bind(this)
);
break;
case 70: // link (just copying)
var req = Unmarshall2(["w", "w", "s"], GetByte);
var dfid = req[0];
var fid = req[1];
var name = req[2];
DebugMessage("[link] dfid=" + dfid + ", name=" + name);
var inode = this.fs.CreateInode();
var inodetarget = this.fs.GetInode(this.fid2inode[fid]);
var targetdata = this.fs.inodedata[this.fid2inode[fid]];
//inode = inodetarget;
inode.mode = inodetarget.mode;
inode.size = inodetarget.size;
inode.symlink = inodetarget.symlink;
var data = this.fs.inodedata[this.fs.inodes.length] = new Uint8Array(inode.size);
inode.waswritten = true;
for(var i=0; i<inode.size; i++) {
data[i] = targetdata[i];
}
inode.name = name;
inode.parentid = this.fid2inode[dfid];
this.fs.PushInode(inode);
//inode.uid = inodetarget.uid;
//inode.gid = inodetarget.gid;
//inode.mode = inodetarget.mode | S_IFLNK;
this.BuildReply(id, tag, 0);
this.SendReply(index);
break;
case 16: // symlink
var req = Unmarshall2(["w", "s", "s", "w"], GetByte);
var fid = req[0];
var name = req[1];
var symgt = req[2];
var gid = req[3];
DebugMessage("[symlink] fid=" + fid + ", name=" + name + ", symgt=" + symgt + ", gid=" + gid);
var idx = this.fs.CreateSymlink(name, this.fid2inode[fid], symgt);
var inode = this.fs.GetInode(idx);
inode.uid = gid;
inode.gid = gid;
Marshall(["Q"], [inode.qid], this.replybuffer, 7);
this.BuildReply(id, tag, 13);
this.SendReply(index);
break;
case 18: // mknod
var req = Unmarshall2(["w", "s", "w", "w", "w", "w"], GetByte);
var fid = req[0];
var name = req[1];
var mode = req[2];
var major = req[3];
var minor = req[4];
//var gid = req[5];
DebugMessage("[mknod] fid=" + fid + ", name=" + name + ", major=" + major + ", minor=" + minor+ "");
var idx = this.fs.CreateNode(name, this.fid2inode[fid], major, minor);
var inode = this.fs.GetInode(idx);
inode.mode = mode;
inode.uid = gid;
inode.gid = gid;
Marshall(["Q"], [inode.qid], this.replybuffer, 7);
this.BuildReply(id, tag, 13);
this.SendReply(index);
break;
case 22: // TREADLINK
var req = Unmarshall2(["w"], GetByte);
var fid = req[0];
DebugMessage("[readlink] fid=" + fid);
var inode = this.fs.GetInode(this.fid2inode[fid]);
var size = Marshall(["s"], [inode.symlink], this.replybuffer, 7);
this.BuildReply(id, tag, size);
this.SendReply(index);
break;
case 72: // tmkdir
var req = Unmarshall2(["w", "s", "w", "w"], GetByte);
var fid = req[0];
var name = req[1];
var mode = req[2];
var gid = req[3];
DebugMessage("[mkdir] fid=" + fid + ", name=" + name + ", mode=" + mode + ", gid=" + gid);
var idx = this.fs.CreateDirectory(name, this.fid2inode[fid]);
var inode = this.fs.GetInode(idx);
inode.mode = mode | S_IFDIR;
inode.uid = gid;
inode.gid = gid;
Marshall(["Q"], [inode.qid], this.replybuffer, 7);
this.BuildReply(id, tag, 13);
this.SendReply(index);
break;
case 14: // tlcreate
var req = Unmarshall2(["w", "s", "w", "w", "w"], GetByte);
var fid = req[0];
var name = req[1];
var flags = req[2];
var mode = req[3];
var gid = req[4];
DebugMessage("[create] fid=" + fid + ", name=" + name + ", flags=" + flags + ", mode=" + mode + ", gid=" + gid);
var idx = this.fs.CreateFile(name, this.fid2inode[fid]);
this.fid2inode[fid] = idx;
this.fidtype[fid] = FID_INODE;
var inode = this.fs.GetInode(idx);
inode.uid = gid;
inode.gid = gid;
inode.mode = mode;
Marshall(["Q", "w"], [inode.qid, this.msize - 24], this.replybuffer, 7);
this.BuildReply(id, tag, 13+4);
this.SendReply(index);
break;
case 52: // lock always suceed
DebugMessage("lock file\n");
Marshall(["w"], [0], this.replybuffer, 7);
this.BuildReply(id, tag, 1);
this.SendReply(index);
break;
/*
case 54: // getlock
break;
*/
case 24: // getattr
var req = Unmarshall2(["w", "d"], GetByte);
var fid = req[0];
var inode = this.fs.GetInode(this.fid2inode[fid]);
DebugMessage("[getattr]: fid=" + fid + " name=" + inode.name + " request mask=" + req[1]);
req[0] |= 0x1000; // P9_STATS_GEN
req[0] = req[1]; // request mask
req[1] = inode.qid;
req[2] = inode.mode;
req[3] = inode.uid; // user id
req[4] = inode.gid; // group id
req[5] = 0x1; // number of hard links
req[6] = (inode.major<<8) | (inode.minor); // device id low
req[7] = inode.size; // size low
req[8] = inode.size; // blk size low
req[9] = Math.floor(inode.size/this.BLOCKSIZE+1); // number of file system blocks
req[10] = inode.atime; // atime
req[11] = 0x0;
req[12] = inode.mtime; // mtime
req[13] = 0x0;
req[14] = inode.ctime; // ctime
req[15] = 0x0;
req[16] = 0x0; // btime
req[17] = 0x0;
req[18] = 0x0; // st_gen
req[19] = 0x0; // data_version
Marshall([
"d", "Q",
"w",
"w", "w",
"d", "d",
"d", "d", "d",
"d", "d", // atime
"d", "d", // mtime
"d", "d", // ctime
"d", "d", // btime
"d", "d",
], req, this.replybuffer, 7);
this.BuildReply(id, tag, 8 + 13 + 4 + 4+ 4 + 8*15);
this.SendReply(index);
break;
case 26: // setattr
var req = Unmarshall2(["w", "w",
"w", // mode
"w", "w", // uid, gid
"d", // size
"d", "d", // atime
"d", "d"] // mtime
, GetByte);
var fid = req[0];
var inode = this.fs.GetInode(this.fid2inode[fid]);
DebugMessage("[setattr]: fid=" + fid + " request mask=" + req[1] + " name=" +inode.name);
if (req[1] & P9_SETATTR_MODE) {
inode.mode = req[2];
}
if (req[1] & P9_SETATTR_UID) {
inode.uid = req[3];
}
if (req[1] & P9_SETATTR_GID) {
inode.gid = req[4];
}
if (req[1] & P9_SETATTR_ATIME_SET) {
inode.atime = req[6];
}
if (req[1] & P9_SETATTR_MTIME_SET) {
inode.atime = req[8];
}
if (req[1] & P9_SETATTR_ATIME) {
inode.atime = Math.floor((new Date()).getTime()/1000);
}
if (req[1] & P9_SETATTR_MTIME) {
inode.mtime = Math.floor((new Date()).getTime()/1000);
}
if (req[1] & P9_SETATTR_CTIME) {
inode.ctime = Math.floor((new Date()).getTime()/1000);
}
if (req[1] & P9_SETATTR_SIZE) {
this.fs.ChangeSize(this.fid2inode[fid], req[5]);
}
this.BuildReply(id, tag, 0);
this.SendReply(index);
break;
case 50: // fsync
var req = Unmarshall2(["w", "d"], GetByte);
var fid = req[0];
this.BuildReply(id, tag, 0);
this.SendReply(index);
break;
case 40: // TREADDIR
case 116: // read
var req = Unmarshall2(["w", "d", "w"], GetByte);
var fid = req[0];
var offset = req[1];
var count = req[2];
var inode = this.fs.GetInode(this.fid2inode[fid]);
if (id == 40) DebugMessage("[treaddir]: fid=" + fid + " offset=" + offset + " count=" + count);
if (id == 116) DebugMessage("[read]: fid=" + fid + " (" + inode.name + ") offset=" + offset + " count=" + count + " fidtype=" + this.fidtype[fid]);
if (this.fidtype[fid] == FID_XATTR) {
if (inode.caps.length < offset+count) count = inode.caps.length - offset;
for(var i=0; i<count; i++)
this.replybuffer[7+4+i] = inode.caps[offset+i];
Marshall(["w"], [count], this.replybuffer, 7);
this.BuildReply(id, tag, 4 + count);
this.SendReply(index);
} else {
this.fs.OpenInode(this.fid2inode[fid]);
this.fs.AddEvent(this.fid2inode[fid],
function() {
if (inode.size < offset+count) count = inode.size - offset;
var data = this.fs.inodedata[this.fid2inode[fid]];
if(data) {
for(var i=0; i<count; i++)
this.replybuffer[7+4+i] = data[offset+i];
}
Marshall(["w"], [count], this.replybuffer, 7);
this.BuildReply(id, tag, 4 + count);
this.SendReply(index);
}.bind(this)
);
}
break;
case 118: // write
var req = Unmarshall2(["w", "d", "w"], GetByte);
var fid = req[0];
var offset = req[1];
var count = req[2];
DebugMessage("[write]: fid=" + fid + " (" + this.fs.inodes[this.fid2inode[fid]].name + ") offset=" + offset + " count=" + count);
this.fs.Write(this.fid2inode[fid], offset, count, GetByte);
Marshall(["w"], [count], this.replybuffer, 7);
this.BuildReply(id, tag, 4);
this.SendReply(index);
break;
case 74: // RENAMEAT
var req = Unmarshall2(["w", "s", "w", "s"], GetByte);
var olddirfid = req[0];
var oldname = req[1];
var newdirfid = req[2];
var newname = req[3];
DebugMessage("[renameat]: oldname=" + oldname + " newname=" + newname);
var ret = this.fs.Rename(this.fid2inode[olddirfid], oldname, this.fid2inode[newdirfid], newname);
if (ret == false) {
this.SendError(tag, "No such file or directory", ENOENT);
this.SendReply(index);
break;
}
this.BuildReply(id, tag, 0);
this.SendReply(index);
break;
case 76: // TUNLINKAT
var req = Unmarshall2(["w", "s", "w"], GetByte);
var dirfd = req[0];
var name = req[1];
var flags = req[2];
DebugMessage("[unlink]: dirfd=" + dirfd + " name=" + name + " flags=" + flags);
var fid = this.fs.Search(this.fid2inode[dirfd], name);
if (fid == -1) {
this.SendError(tag, "No such file or directory", ENOENT);
this.SendReply(index);
break;
}
var ret = this.fs.Unlink(fid);
if (!ret) {
this.SendError(tag, "Directory not empty", ENOTEMPTY);
this.SendReply(index);
break;
}
this.BuildReply(fid, tag, 0);
this.SendReply(index);
break;
case 100: // version
var version = Unmarshall2(["w", "s"], GetByte);
DebugMessage("[version]: msize=" + version[0] + " version=" + version[1]);
this.msize = version[0];
var size = Marshall(["w", "s"], [this.msize, this.VERSION], this.replybuffer, 7);
this.BuildReply(id, tag, size);
this.SendReply(index);
break;
case 104: // attach
// return root directorie's QID
var req = Unmarshall2(["w", "w", "s", "s"], GetByte);
var fid = req[0];
DebugMessage("[attach]: fid=" + fid + " afid=" + hex8(req[1]) + " uname=" + req[2] + " aname=" + req[3]);
this.fid2inode[fid] = 0;
this.fidtype[fid] = FID_INODE;
var inode = this.fs.GetInode(this.fid2inode[fid]);
Marshall(["Q"], [inode.qid], this.replybuffer, 7);
this.BuildReply(id, tag, 13);
this.SendReply(index);
break;
case 108: // tflush
var req = Unmarshall2(["h"], GetByte);
var oldtag = req[0];
DebugMessage("[flush] " + tag);
//Marshall(["Q"], [inode.qid], this.replybuffer, 7);
this.BuildReply(id, tag, 0);
this.SendReply(index);
break;
case 110: // walk
var req = Unmarshall2(["w", "w", "h"], GetByte);
var fid = req[0];
var nwfid = req[1];
var nwname = req[2];
DebugMessage("[walk]: fid=" + req[0] + " nwfid=" + req[1] + " nwname=" + nwname);
if (nwname == 0) {
this.fid2inode[nwfid] = this.fid2inode[fid];
Marshall(["h"], [0], this.replybuffer, 7);
this.BuildReply(id, tag, 2);
this.SendReply(index);
break;
}
var wnames = [];
for(var i=0; i<nwname; i++) {
wnames.push("s");
}
var walk = Unmarshall2(wnames, GetByte);
var idx = this.fid2inode[fid];
var offset = 7+2;
var nwidx = 0;
//console.log(idx, this.fs.inodes[idx]);
DebugMessage("walk in dir " + this.fs.inodes[idx].name + " to: " + walk.toString());
for(var i=0; i<nwname; i++) {
idx = this.fs.Search(idx, walk[i]);
if (idx == -1) {
DebugMessage("Could not find: " + walk[i]);
break;
}
offset += Marshall(["Q"], [this.fs.inodes[idx].qid], this.replybuffer, offset);
nwidx++;
DebugMessage(this.fid2inode[nwfid]);
this.fid2inode[nwfid] = idx;
this.fidtype[nwfid] = FID_INODE;
}
Marshall(["h"], [nwidx], this.replybuffer, 7);
this.BuildReply(id, tag, offset-7);
this.SendReply(index);
break;
case 120: // clunk
var req = Unmarshall2(["w"], GetByte);
DebugMessage("[clunk]: fid=" + req[0]);
if (this.fid2inode[req[0]] >= 0) {
this.fs.CloseInode(this.fid2inode[req[0]]);
this.fid2inode[req[0]] = -1;
this.fidtype[req[0]] = FID_NONE;
}
this.BuildReply(id, tag, 0);
this.SendReply(index);
break;
case 32:
this.SendError(tag, "Operation i not supported", ENOTSUPP);
this.SendReply(index);
break;
case 30: // xattrwalk
var req = Unmarshall2(["w", "w", "s"], GetByte);
var fid = req[0];
var newfid = req[1];
var name = req[2];
DebugMessage("[xattrwalk]: fid=" + req[0] + " newfid=" + req[1] + " name=" + req[2]);
this.fid2inode[newfid] = this.fid2inode[fid];
this.fidtype[newfid] = FID_NONE;
var length = 0;
if (name == "security.capability") {
length = this.fs.PrepareCAPs(this.fid2inode[fid]);
this.fidtype[newfid] = FID_XATTR;
}
Marshall(["d"], [length], this.replybuffer, 7);
this.BuildReply(id, tag, 8);
this.SendReply(index);
break;
default:
DebugMessage("Error in Virtio9p: Unknown id " + id + " received");
abort();
//this.SendError(tag, "Operation i not supported", ENOTSUPP);
//this.SendReply(index);
break;
}
//consistency checks if there are problems with the filesystem
//this.fs.Check();
}

847
lib/filesystem.js Executable file
View file

@ -0,0 +1,847 @@
// -------------------------------------------------
// ----------------- FILESYSTEM---------------------
// -------------------------------------------------
// 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;
//var S_IFIFO 0010000
//var S_ISUID 0004000
//var S_ISGID 0002000
//var S_ISVTX 0001000
var O_RDONLY = 0x0000; // open for reading only
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_OPEN = 0x1;
var STATUS_ON_SERVER = 0x2;
var STATUS_LOADING = 0x3;
var STATUS_UNLINKED = 0x4;
/** @constructor */
function FS(baseurl) {
this.inodes = [];
this.events = [];
this.baseurl = baseurl;
this.qidnumber = 0x0;
this.filesinloadingqueue = 0;
this.OnLoaded = function() {};
//this.tar = new TAR(this);
this.userinfo = [];
this.inodedata = {};
//RegisterMessage("LoadFilesystem", this.LoadFilesystem.bind(this) );
//RegisterMessage("MergeFile", this.MergeFile.bind(this) );
//RegisterMessage("tar",
// function(data) {
// SendToMaster("tar", this.tar.Pack(data));
// }.bind(this)
//);
//RegisterMessage("sync",
// function(data) {
// SendToMaster("sync", this.tar.Pack(data));
// }.bind(this)
//);
// root entry
this.CreateDirectory("", -1);
this._state_skip = ["OnLoaded"];
}
// -----------------------------------------------------
FS.prototype.LoadFilesystem = function(userinfo)
{
this.userinfo = userinfo;
this.LoadFSXML(this.userinfo.basefsURL);
this.OnLoaded = function() {
}.bind(this);
}
// -----------------------------------------------------
FS.prototype.AddEvent = function(id, OnEvent) {
var inode = this.inodes[id];
if (inode.status == STATUS_OK) {
OnEvent();
return;
}
this.events.push({id: id, OnEvent: OnEvent});
}
FS.prototype.HandleEvent = function(id) {
if (this.filesinloadingqueue == 0) {
this.OnLoaded();
this.OnLoaded = function() {}
}
//DebugMessage("number of events: " + this.events.length);
for(var i = this.events.length - 1; i >= 0; i--) {
if (this.events[i].id != id) continue;
this.events[i].OnEvent();
this.events.splice(i, 1);
}
}
// -----------------------------------------------------
//FS.prototype.LoadImage = function(url)
//{
// dbg_assert(false);
// if (!url) return;
// //DebugMessage("Load Image " + url);
///*
// if (typeof Worker !== 'undefined') {
// LoadBZIP2Resource(url,
// function(m){ for(var i=0; i<m.size; i++) this.tar.Unpack(m.data[i]); }.bind(this),
// function(e){DebugMessage("Error: Could not load " + url + ". Skipping.");});
// return;
// }
//*/
// LoadBinaryResource(url,
// function(buffer){
// var buffer8 = new Uint8Array(buffer);
// bzip2.simple(buffer8, this.tar.Unpack.bind(this.tar));
// }.bind(this),
// function(error){DebugMessage("Error: Could not load " + url + ". Skipping.");});
//}
// -----------------------------------------------------
function ReadVariable(buffer, offset) {
var variable = [];
variable.name = "";
variable.value = "";
// read blanks
for(var i=offset; i<buffer.length; i++) {
if (buffer[i] == '>') return variable;
if (buffer[i] == '/') return variable;
if (buffer[i] != ' ') break;
}
offset = i;
if (buffer[i] == '>') return variable;
// read variable name
for(var i=offset; i<buffer.length; i++) {
if (buffer[i] == '>') break;
if (buffer[i] == '=') break;
variable.name = variable.name + buffer[i];
}
offset = i+1;
if (variable.name.length == 0) return variable;
// read variable value
for(var i=offset+1; i<buffer.length; i++) {
if (buffer[i] == '>') break;
if (buffer[i] == '\'') break;
variable.value = variable.value + buffer[i];
}
offset = i+1;
variable.offset = offset;
DebugMessage("read " + variable.name + "=" + variable.value);
return variable;
}
function ReadTag(buffer, offset) {
var tag = [];
tag.type = "";
tag.name = "";
tag.mode = 0x0;
tag.uid = 0x0;
tag.gid = 0x0;
tag.path = "";
tag.src = "";
tag.compressed = false;
tag.load = false;
if (buffer[offset] != '<') return tag;
for(var i=offset+1; i<buffer.length; i++) {
if (buffer[i] == ' ') break;
if (buffer[i] == '\n') break;
if (buffer[i] == '>') break;
tag.type = tag.type + buffer[i];
}
offset = i;
// read variables
do {
var variable = ReadVariable(buffer, offset);
if (variable.name == "name") tag.name = variable.value;
if (variable.name == "mode") tag.mode = parseInt(variable.value, 8);
if (variable.name == "uid") tag.uid = parseInt(variable.value, 10);
if (variable.name == "gid") tag.gid = parseInt(variable.value, 10);
if (variable.name == "path") tag.path = variable.value;
if (variable.name == "size") tag.size = parseInt(variable.value, 10);
if (variable.name == "src") tag.src = variable.value;
if (variable.name == "compressed") tag.compressed = true;
if (variable.name == "load") tag.load = true;
offset = variable.offset;
} while(variable.name.length != 0);
return tag;
};
FS.prototype.CheckEarlyload = function(path)
{
for(var i=0; i<this.userinfo.earlyload.length; i++) {
if (this.userinfo.earlyload[i] == path) {
return true;
}
}
return false;
}
FS.prototype.LoadFSXML = function(urls)
{
DebugMessage("Load filesystem information from " + urls);
LoadXMLResource(urls, this.OnJSONLoaded.bind(this), function(error){throw error;});
}
FS.prototype.OnJSONLoaded = function(fs)
{
//console.time("JSON");
var data = JSON.parse(fs);
//console.timeEnd("JSON");
var fsroot = data["fsroot"];
var me = this;
setTimeout(function()
{
//console.time("Load");
for(var i = 0; i < fsroot.length; i++) {
LoadRecursive(fsroot[i], 0);
}
//console.timeEnd("Load");
if(DEBUG)
{
//console.time("Check");
//me.Check();
//console.timeEnd("Check");
}
}, 100);
function LoadRecursive(data, parentid)
{
var inode = me.CreateInode();
inode.name = data.name;
inode.uid = data.uid || 0;
inode.gid = data.gid || 0;
inode.atime = Math.floor(data.atime) || inode.atime;
inode.ctime = Math.floor(data.ctime) || inode.ctime;
inode.mtime = Math.floor(data.mtime) || inode.mtime;
inode.parentid = parentid;
inode.mode = data.mode & 511;
inode.size = data.size || 0;
switch(data.type)
{
case "dir":
inode.updatedir = true;
inode.mode |= S_IFDIR;
var p = me.inodes.length;
me.PushInode(inode);
var children = data.children;
for(var i = 0; i < children.length; i++) {
LoadRecursive(children[i], p);
}
break;
case "file":
inode.mode |= S_IFREG;
var idx = me.inodes.length;
inode.status = STATUS_ON_SERVER;
me.PushInode(inode);
var url = me.baseurl + me.GetFullPath(idx);
inode.url = url;
break;
case "link":
inode.mode |= S_IFLNK;
inode.symlink = data.target;
me.PushInode(inode);
break;
default:
DebugMessage("Invalid message type: ", data.type);
abort();
}
}
};
// The filesystem is responsible to add the correct time. This is a hack
// Have to find a better solution.
FS.prototype.AppendDateHack = function(idx) {
if (this.GetFullPath(idx) != "etc/init.d/rcS") return;
var inode = this.inodes[idx];
var date = new Date();
var datestring =
"\ndate -s \"" +
date.getFullYear() +
"-" +
(date.getMonth()+1) +
"-" +
date.getDate() +
" " +
date.getHours() +
":" +
date.getMinutes() +
":" +
date.getSeconds() +
"\"\n";
var size = inode.size;
this.ChangeSize(idx, size+datestring.length);
var data = this.inodedata[idx];
for(var i=0; i<datestring.length; i++) {
data[i+size] = datestring.charCodeAt(i);
}
}
// Loads the data from a url for a specific inode
FS.prototype.LoadFile = function(idx) {
var inode = this.inodes[idx];
if (inode.status != STATUS_ON_SERVER) {
return;
}
inode.status = STATUS_LOADING;
this.filesinloadingqueue++;
//if (inode.compressed) {
// inode.data = new Uint8Array(inode.size);
// LoadBinaryResource(inode.url + ".bz2",
// function(buffer){
// var buffer8 = new Uint8Array(buffer);
// var ofs = 0;
// bzip2.simple(buffer8, function(x){inode.data[ofs++] = x;}.bind(this) );
// inode.status = STATUS_OK;
// this.filesinloadingqueue--;
// this.HandleEvent(idx);
// }.bind(this),
// function(error){throw error;});
// return;
//}
LoadBinaryResource(inode.url,
function(buffer){
var data = this.inodedata[idx] = new Uint8Array(buffer);
inode.size = data.length; // correct size if the previous was wrong.
inode.status = STATUS_OK;
if (inode.name == "rcS") {
this.AppendDateHack(idx);
}
this.filesinloadingqueue--;
this.HandleEvent(idx);
}.bind(this),
function(error){throw error;});
}
// -----------------------------------------------------
FS.prototype.PushInode = function(inode) {
if (inode.parentid != -1) {
this.inodes.push(inode);
this.inodes[inode.parentid].updatedir = true;
inode.nextid = this.inodes[inode.parentid].firstid;
this.inodes[inode.parentid].firstid = this.inodes.length-1;
return;
} else {
if (this.inodes.length == 0) { // if root directory
this.inodes.push(inode);
return;
}
}
DebugMessage("Error in Filesystem: Pushed inode with name = "+ inode.name + " has no parent");
abort();
}
FS.prototype.CreateInode = function() {
//console.log("CreateInode", Error().stack);
this.qidnumber++;
var now = Math.floor(Date.now() / 1000);
return {
updatedir : false, // did the directory listing changed?
parentid: -1,
firstid : -1, // first file id in directory
nextid : -1, // next id in directory
status : 0,
name : "",
size : 0x0,
uid : 0x0,
gid : 0x0,
ctime : now,
atime : now,
mtime : now,
major : 0x0,
minor : 0x0,
//data : new Uint8Array(0),
symlink : "",
mode : 0x01ED,
qid: {type: 0, version: 0, path: this.qidnumber},
url: "", // url to download the file
waswritten: false,
};
}
FS.prototype.CreateDirectory = function(name, parentid) {
var x = this.CreateInode();
x.name = name;
x.parentid = parentid;
x.mode = 0x01FF | S_IFDIR;
if (parentid >= 0) {
x.uid = this.inodes[parentid].uid;
x.gid = this.inodes[parentid].gid;
x.mode = (this.inodes[parentid].mode & 0x1FF) | S_IFDIR;
}
x.qid.type = S_IFDIR >> 8;
this.PushInode(x);
return this.inodes.length-1;
}
FS.prototype.CreateFile = function(filename, parentid) {
var x = this.CreateInode();
x.name = filename;
x.parentid = parentid;
x.uid = this.inodes[parentid].uid;
x.gid = this.inodes[parentid].gid;
x.qid.type = S_IFREG >> 8;
x.mode = (this.inodes[parentid].mode & 0x1B6) | S_IFREG;
this.PushInode(x);
return this.inodes.length-1;
}
FS.prototype.CreateNode = function(filename, parentid, major, minor) {
var x = this.CreateInode();
x.name = filename;
x.parentid = parentid;
x.major = major;
x.minor = minor;
x.uid = this.inodes[parentid].uid;
x.gid = this.inodes[parentid].gid;
x.qid.type = S_IFSOCK >> 8;
x.mode = (this.inodes[parentid].mode & 0x1B6);
this.PushInode(x);
return this.inodes.length-1;
}
FS.prototype.CreateSymlink = function(filename, parentid, symlink) {
var x = this.CreateInode();
x.name = filename;
x.parentid = parentid;
x.uid = this.inodes[parentid].uid;
x.gid = this.inodes[parentid].gid;
x.qid.type = S_IFLNK >> 8;
x.symlink = symlink;
x.mode = S_IFLNK;
this.PushInode(x);
return this.inodes.length-1;
}
FS.prototype.CreateTextFile = function(filename, parentid, str) {
var id = this.CreateFile(filename, parentid);
var x = this.inodes[id];
var data = this.inodedata[id] = new Uint8Array(str.length);
x.waswritten = true;
x.size = str.length;
for (var j in str) {
data[j] = str.charCodeAt(j);
}
return id;
}
FS.prototype.OpenInode = function(id, mode) {
var inode = this.GetInode(id);
if ((inode.mode&S_IFMT) == S_IFDIR) {
this.FillDirectory(id);
}
/*
var type = "";
switch(inode.mode&S_IFMT) {
case S_IFREG: type = "File"; break;
case S_IFBLK: type = "Block Device"; break;
case S_IFDIR: type = "Directory"; break;
case S_IFCHR: type = "Character Device"; break;
}
*/
//DebugMessage("open:" + this.GetFullPath(id) + " status:" + inode.status);
if (inode.status == STATUS_ON_SERVER) {
this.LoadFile(id);
return false;
}
return true;
}
FS.prototype.CloseInode = function(id) {
//DebugMessage("close: " + this.GetFullPath(id));
var inode = this.GetInode(id);
if (inode.status == STATUS_UNLINKED) {
//DebugMessage("Filesystem: Delete unlinked file");
inode.status == STATUS_INVALID;
delete this.inodedata[id];
inode.waswritten = true;
inode.size = 0;
}
}
FS.prototype.Rename = function(olddirid, oldname, newdirid, newname) {
//DebugMessage("Rename " + oldname + " to " + newname);
if ((olddirid == newdirid) && (oldname == newname)) {
return true;
}
var oldid = this.Search(olddirid, oldname);
if (oldid == -1) {
return false;
}
var newid = this.Search(newdirid, newname);
if (newid != -1) {
this.Unlink(newid);
}
var idx = oldid; // idx contains the id which we want to rename
var inode = this.inodes[idx];
// remove inode ids
if (this.inodes[inode.parentid].firstid == idx) {
this.inodes[inode.parentid].firstid = inode.nextid;
} else {
var id = this.FindPreviousID(idx);
if (id == -1) {
//DebugMessage("Error in Filesystem: Cannot find previous id of inode");
abort();
}
this.inodes[id].nextid = inode.nextid;
}
inode.parentid = newdirid;
inode.name = newname;
inode.qid.version++;
inode.nextid = this.inodes[inode.parentid].firstid;
this.inodes[inode.parentid].firstid = idx;
this.inodes[olddirid].updatedir = true;
this.inodes[newdirid].updatedir = true;
return true;
}
FS.prototype.Write = function(id, offset, count, GetByte) {
var inode = this.inodes[id];
var data = this.inodedata[id];
inode.waswritten = true;
if (!data || data.length < (offset+count)) {
this.ChangeSize(id, Math.floor(((offset+count)*3)/2) );
inode.size = offset + count;
data = this.inodedata[id];
} else
if (inode.size < (offset+count)) {
inode.size = offset + count;
}
for(var i=0; i<count; i++)
data[offset+i] = GetByte();
}
FS.prototype.Search = function(parentid, name) {
var id = this.inodes[parentid].firstid;
while(id != -1) {
if (this.inodes[id].parentid != parentid) { // consistency check
DebugMessage("Error in Filesystem: Found inode with wrong parent id");
}
if (this.inodes[id].name == name) return id;
id = this.inodes[id].nextid;
}
return -1;
}
FS.prototype.GetTotalSize = function() {
return 1234567;
//var size = 0;
//for(var i=0; i<this.inodes.length; i++) {
// var d = this.inodes[i].data;
// size += d ? d.length : 0;
//}
//return size;
}
FS.prototype.GetFullPath = function(idx) {
var path = "";
while(idx != 0) {
path = "/" + this.inodes[idx].name + path;
idx = this.inodes[idx].parentid;
}
return path.substring(1);
}
// no double linked list. So, we need this
FS.prototype.FindPreviousID = function(idx) {
var inode = this.GetInode(idx);
var id = this.inodes[inode.parentid].firstid;
while(id != -1) {
if (this.inodes[id].nextid == idx) return id;
id = this.inodes[id].nextid;
}
return id;
}
FS.prototype.Unlink = function(idx) {
if (idx == 0) return false; // root node cannot be deleted
var inode = this.GetInode(idx);
//DebugMessage("Unlink " + inode.name);
// check if directory is not empty
if ((inode.mode&S_IFMT) == S_IFDIR) {
if (inode.firstid != -1) return false;
}
// update ids
if (this.inodes[inode.parentid].firstid == idx) {
this.inodes[inode.parentid].firstid = inode.nextid;
} else {
var id = this.FindPreviousID(idx);
if (id == -1) {
DebugMessage("Error in Filesystem: Cannot find previous id of inode");
abort();
}
this.inodes[id].nextid = inode.nextid;
}
// don't delete the content. The file is still accessible
this.inodes[inode.parentid].updatedir = true;
inode.status = STATUS_UNLINKED;
inode.nextid = -1;
inode.firstid = -1;
inode.parentid = -1;
return true;
}
FS.prototype.GetInode = function(idx)
{
if (isNaN(idx)) {
DebugMessage("Error in filesystem: id is not a number ");
return 0;
}
if ((idx < 0) || (idx > this.inodes.length)) {
DebugMessage("Error in filesystem: Attempt to get inode with id " + idx);
return 0;
}
return this.inodes[idx];
}
FS.prototype.ChangeSize = function(idx, newsize)
{
var inode = this.GetInode(idx);
var temp = this.inodedata[idx];
//DebugMessage("change size to: " + newsize);
if (newsize == inode.size) return;
var data = this.inodedata[idx] = new Uint8Array(newsize);
inode.size = newsize;
inode.waswritten = true;
if(!temp) return;
var size = Math.min(temp.length, inode.size);
for(var i=0; i<size; i++) {
data[i] = temp[i];
}
}
FS.prototype.SearchPath = function(path) {
//path = path.replace(/\/\//g, "/");
path = path.replace("//", "/");
var walk = path.split("/");
var n = walk.length;
if (walk[n-1].length == 0) walk.pop();
if (walk[0].length == 0) walk.shift();
n = walk.length;
var parentid = 0;
var id = -1;
for(var i=0; i<n; i++) {
id = this.Search(parentid, walk[i]);
if (id == -1) {
if (i < n-1) return {id: -1, parentid: -1, name: walk[i]}; // one name of the path cannot be found
return {id: -1, parentid: parentid, name: walk[i]}; // the last element in the path does not exist, but the parent
}
parentid = id;
}
return {id: id, parentid: parentid, name: walk[i]};
}
// -----------------------------------------------------
FS.prototype.GetRecursiveList = function(dirid, list) {
var id = this.inodes[dirid].firstid;
while(id != -1) {
list.push(id);
if ((this.inodes[id].mode&S_IFMT) == S_IFDIR) {
this.GetRecursiveList(id, list);
}
id = this.inodes[id].nextid;
}
}
FS.prototype.MergeFile = function(file) {
throw "unimplemented";
//DebugMessage("Merge path:" + file.name);
//var ids = this.SearchPath(file.name);
//if (ids.parentid == -1) return; // not even the path seems to exist
//if (ids.id == -1) {
// ids.id = this.CreateFile(ids.name, ids.parentid);
//}
//this.inodes[ids.id].data = file.data;
//this.inodes[ids.id].size = file.data.length;
}
FS.prototype.Check = function() {
for(var i=1; i<this.inodes.length; i++)
{
if (this.inodes[i].status == STATUS_INVALID) continue;
if (this.inodes[i].nextid == i) {
DebugMessage("Error in filesystem: file points to itself");
abort();
}
var inode = this.GetInode(i);
if (inode.parentid < 0) {
DebugMessage("Error in filesystem: negative parent id " + i);
}
var n = inode.name.length;
if (n == 0) {
DebugMessage("Error in filesystem: inode with no name and id " + i);
}
for (var j in inode.name) {
var c = inode.name.charCodeAt(j);
if (c < 32) {
DebugMessage("Error in filesystem: Unallowed char in filename");
}
}
}
}
FS.prototype.FillDirectory = function(dirid) {
var inode = this.GetInode(dirid);
if (!inode.updatedir) return;
var parentid = inode.parentid;
if (parentid == -1) parentid = 0; // if root directory point to the root directory
// first get size
var size = 0;
var id = this.inodes[dirid].firstid;
while(id != -1) {
size += 13 + 8 + 1 + 2 + UTF8Length(this.inodes[id].name);
id = this.inodes[id].nextid;
}
size += 13 + 8 + 1 + 2 + 1; // "." entry
size += 13 + 8 + 1 + 2 + 2; // ".." entry
//DebugMessage("size of dir entry: " + size);
var data = this.inodedata[dirid] = new Uint8Array(size);
inode.waswritten = true;
inode.size = size;
var offset = 0x0;
offset += Marshall(
["Q", "d", "b", "s"],
[this.inodes[dirid].qid,
offset+13+8+1+2+1,
this.inodes[dirid].mode >> 12,
"."],
data, offset);
offset += Marshall(
["Q", "d", "b", "s"],
[this.inodes[parentid].qid,
offset+13+8+1+2+2,
this.inodes[parentid].mode >> 12,
".."],
data, offset);
id = this.inodes[dirid].firstid;
while(id != -1) {
offset += Marshall(
["Q", "d", "b", "s"],
[this.inodes[id].qid,
offset+13+8+1+2+UTF8Length(this.inodes[id].name),
this.inodes[id].mode >> 12,
this.inodes[id].name],
data, offset);
id = this.inodes[id].nextid;
}
inode.updatedir = false;
}
// -----------------------------------------------------
// only support for security.capabilities
// should return a "struct vfs_cap_data" defined in
// linux/capability for format
// check also:
// sys/capability.h
// http://lxr.free-electrons.com/source/security/commoncap.c#L376
// http://man7.org/linux/man-pages/man7/capabilities.7.html
// http://man7.org/linux/man-pages/man8/getcap.8.html
// http://man7.org/linux/man-pages/man3/libcap.3.html
FS.prototype.PrepareCAPs = function(id) {
var inode = this.GetInode(id);
if (inode.caps) return inode.caps.length;
inode.caps = new Uint8Array(12);
// format is little endian
// magic_etc (revision=0x01: 12 bytes)
inode.caps[0] = 0x00;
inode.caps[1] = 0x00;
inode.caps[2] = 0x00;
inode.caps[3] = 0x01;
// permitted (full capabilities)
inode.caps[4] = 0xFF;
inode.caps[5] = 0xFF;
inode.caps[6] = 0xFF;
inode.caps[7] = 0xFF;
// inheritable (full capabilities
inode.caps[8] = 0xFF;
inode.caps[9] = 0xFF;
inode.caps[10] = 0xFF;
inode.caps[11] = 0xFF;
return inode.caps.length;
}
FS.prototype.ClearCache = function()
{
for(var id in this.inodedata)
{
if(!this.inodes[id].waswritten)
{
delete this.inodedata[id];
}
}
};

108
lib/jor1k.js Normal file
View file

@ -0,0 +1,108 @@
"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;
var VRING_DESC_F_NEXT = 1; /* This marks a buffer as continuing via the next field. */
var VRING_DESC_F_WRITE = 2; /* This marks a buffer as write-only (otherwise read-only). */
var VRING_DESC_F_INDIRECT = 4; /* This means the buffer contains a list of buffer descriptors. */
function Swap16(x)
{
// OR1K is big endian, therefore no conversion needed for us
return x;
}
function Swap32(x)
{
return x;
}
function abort()
{
if(DEBUG)
{
throw "abort";
}
}
var hex8 = h;
var DebugMessage;
if(DEBUG)
{
DebugMessage = console.log.bind(console);
}
else
{
DebugMessage = function() {};
}
function LoadXMLResource(url, OnSuccess, OnError) {
var req = new XMLHttpRequest();
req.open('GET', url, true);
//req.overrideMimeType('text/xml');
req.onreadystatechange = function () {
if (req.readyState != 4) {
return;
}
if ((req.status != 200) && (req.status != 0)) {
OnError("Error: Could not load XML file " + url);
return;
}
OnSuccess(req.responseText);
};
req.send(null);
}
function LoadBinaryResource(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);
}

166
lib/marshall.js Normal file
View file

@ -0,0 +1,166 @@
// -------------------------------------------------
// ------------------ Marshall ---------------------
// -------------------------------------------------
// helper functions for virtio and 9p.
// Inserts data from an array to a byte aligned struct in memory
function Marshall(typelist, input, struct, offset) {
var item;
var size = 0;
for (var i=0; i < typelist.length; i++) {
item = input[i];
switch (typelist[i]) {
case "w":
struct[offset++] = item & 0xFF;
struct[offset++] = (item >> 8) & 0xFF;
struct[offset++] = (item >> 16) & 0xFF;
struct[offset++] = (item >> 24) & 0xFF;
size += 4;
break;
case "d": // double word
struct[offset++] = item & 0xFF;
struct[offset++] = (item >> 8) & 0xFF;
struct[offset++] = (item >> 16) & 0xFF;
struct[offset++] = (item >> 24) & 0xFF;
struct[offset++] = 0x0;
struct[offset++] = 0x0;
struct[offset++] = 0x0;
struct[offset++] = 0x0;
size += 8;
break;
case "h":
struct[offset++] = item & 0xFF;
struct[offset++] = item >> 8;
size += 2;
break;
case "b":
struct[offset++] = item;
size += 1;
break;
case "s":
var lengthoffset = offset;
var length = 0;
struct[offset++] = 0; // set the length later
struct[offset++] = 0;
size += 2;
for (var j in item) {
var utf8 = UnicodeToUTF8Stream(item.charCodeAt(j));
utf8.forEach( function(c) {
struct[offset++] = c;
size += 1;
length++;
});
}
struct[lengthoffset+0] = length & 0xFF;
struct[lengthoffset+1] = (length >> 8) & 0xFF;
break;
case "Q":
Marshall(["b", "w", "d"], [item.type, item.version, item.path], struct, offset)
offset += 13;
size += 13;
break;
default:
DebugMessage("Marshall: Unknown type=" + typelist[i]);
break;
}
}
return size;
};
// Extracts data from a byte aligned struct in memory to an array
function Unmarshall(typelist, struct, offset) {
var output = [];
for (var i=0; i < typelist.length; i++) {
switch (typelist[i]) {
case "w":
var val = struct[offset++];
val += struct[offset++] << 8;
val += struct[offset++] << 16;
val += (struct[offset++] << 24) >>> 0;
output.push(val);
break;
case "d":
var val = struct[offset++];
val += struct[offset++] << 8;
val += struct[offset++] << 16;
val += (struct[offset++] << 24) >>> 0;
offset += 4;
output.push(val);
break;
case "h":
var val = struct[offset++];
output.push(val + (struct[offset++] << 8));
break;
case "b":
output.push(struct[offset++]);
break;
case "s":
var len = struct[offset++];
len += struct[offset++] << 8;
var str = '';
var utf8converter = new UTF8StreamToUnicode();
for (var j=0; j < len; j++) {
var c = utf8converter.Put(struct[offset++])
if (c == -1) continue;
str += String.fromCharCode(c);
}
output.push(str);
break;
default:
DebugMessage("Error in Unmarshall: Unknown type=" + typelist[i]);
break;
}
}
return output;
};
// Extracts data from a byte aligned struct in memory to an array
function Unmarshall2(typelist, GetByte) {
var output = [];
for (var i=0; i < typelist.length; i++) {
switch (typelist[i]) {
case "w":
var val = GetByte();
val += GetByte() << 8;
val += GetByte() << 16;
val += (GetByte() << 24) >>> 0;
output.push(val);
break;
case "d":
var val = GetByte();
val += GetByte() << 8;
val += GetByte() << 16;
val += (GetByte() << 24) >>> 0;
GetByte();GetByte();GetByte();GetByte();
output.push(val);
break;
case "h":
var val = GetByte();
output.push(val + (GetByte() << 8));
break;
case "b":
output.push(GetByte());
break;
case "s":
var len = GetByte();
len += GetByte() << 8;
var str = '';
var utf8converter = new UTF8StreamToUnicode();
for (var j=0; j < len; j++) {
var c = utf8converter.Put(GetByte())
if (c == -1) continue;
str += String.fromCharCode(c);
}
output.push(str);
break;
default:
DebugMessage("Error in Unmarshall2: Unknown type=" + typelist[i]);
break;
}
}
return output;
};

61
lib/utf8.js Normal file
View file

@ -0,0 +1,61 @@
// -------------------------------------------------
// ------------------ UTF8 Helpers -----------------
// -------------------------------------------------
"use strict";
/** @constructor */
function UTF8StreamToUnicode() {
this.stream = new Uint8Array(5);
this.ofs = 0;
this.Put = function(key) {
this.stream[this.ofs] = key;
this.ofs++;
switch(this.ofs) {
case 1:
if (this.stream[0] < 128) {
this.ofs = 0;
return this.stream[0];
}
break;
case 2:
if ((this.stream[0]&0xE0) == 0xC0)
if ((this.stream[1]&0xC0) == 0x80) {
this.ofs = 0;
return ((this.stream[0]&0x1F)<<6) | (this.stream[1]&0x3F);
}
break;
case 3:
break;
case 4:
break;
default:
return -1;
this.ofs = 0;
break;
}
return -1;
}
}
function UnicodeToUTF8Stream(key)
{
if (key < 0x80) return [key];
if (key < 0x800) return [0xC0|((key>>6)&0x1F), 0x80|(key&0x3F)];
}
function UTF8Length(s)
{
var length = 0;
for(var i=0; i<s.length; i++) {
var c = s.charCodeAt(i);
length += c<128?1:2;
}
return length;
}

View file

@ -2,13 +2,14 @@
(function()
{
var
CORE_FILES = "const.js io.js main.js ide.js fpu.js pci.js floppy.js " +
var CORE_FILES = "const.js io.js main.js ide.js fpu.js pci.js floppy.js " +
"memory.js dma.js pit.js vga.js ps2.js pic.js rtc.js uart.js acpi.js hpet.js " +
"ne2k.js cpu_state.js",
BROWSER_FILES = "main.js screen.js keyboard.js mouse.js serial.js lib.js network.js",
LIB_FILES = "esprima.js walk.js";
"ne2k.js cpu_state.js virtio.js";
var BROWSER_FILES = "main.js screen.js keyboard.js mouse.js serial.js lib.js network.js";
var LIB_FILES = "esprima.js walk.js";
// jor1k stuff
LIB_FILES += " jor1k.js 9p.js filesystem.js marshall.js utf8.js";
load_scripts("cpu.js", "build/");
load_scripts(CORE_FILES, "src/");

View file

@ -743,6 +743,11 @@ v86.prototype.init = function(settings)
{
this.devices.net = new Ne2k(this, settings.network_adapter);
}
if(settings.fs9p)
{
this.devices.virtio = new VirtIO(this, settings.fs9p);
}
}
if(DEBUG)

344
src/virtio.js Normal file
View file

@ -0,0 +1,344 @@
"use strict";
/** @constructor */
function VirtIO(cpu, filesystem)
{
// http://ozlabs.org/~rusty/virtio-spec/virtio-0.9.5.pdf
this.pci_space = [
0xf4, 0x1a, 0x09, 0x10, 0x07, 0x05, 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0xa8, 0x00, 0x00, 0x00, 0x10, 0xbf, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x1a, 0x09, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00,
];
this.pci_id = 0x06 << 3;
this.pci_bars = [
{
size: 128 * 1024,
},
];
cpu.devices.pci.register_device(this);
var io = cpu.io;
io.register_read(0xA800, this, undefined, undefined, function()
{
// device features
return 1;
});
io.register_write(0xA804, this, undefined, undefined, function(data)
{
// write guest features
dbg_log("Guest feature selection: " + h(data, 8), LOG_VIRTIO);
});
io.register_write(0xA80E, this, undefined, function(data)
{
// rw queue select
dbg_log("Queue select: " + h(data, 4), LOG_VIRTIO);
this.queue_select = data;
}, undefined);
io.register_read(0xA80C, this, undefined, function()
{
// read queue size
dbg_log("Read queue size", LOG_VIRTIO);
return this.queue_size;
}, undefined);
io.register_read(0xA808, this, undefined, undefined, function()
{
// rw queue address
dbg_log("Read queue address", LOG_VIRTIO);
if(this.queue_select === 0)
{
return this.queue_address;
}
else
{
// queue does not exist
return 0;
}
});
io.register_write(0xA808, this, undefined, undefined, function(data)
{
// rw queue address
dbg_log("Write queue address: " + h(data, 8), LOG_VIRTIO);
this.queue_address = data;
});
io.register_write(0xA812, this, function(data)
{
dbg_log("Write device status: " + h(data, 2), LOG_VIRTIO);
this.device_status = data;
});
io.register_read(0xA812, this, function()
{
dbg_log("Read device status", LOG_VIRTIO);
return this.device_status;
});
io.register_read(0xA813, this, function()
{
dbg_log("Read isr", LOG_VIRTIO);
// reading resets the isr
var isr = this.isr;
this.isr = 0;
return isr;
});
io.register_write(0xA810, this, undefined, function(data)
{
dbg_log("Write queue notify: " + h(data, 4), LOG_VIRTIO);
// only queue 0 supported
dbg_assert(data === 0);
var queue_start = this.queue_address << 12;
var ring_start = queue_start + 16 * this.queue_size;
var ring_desc_start = ring_start + 4;
var flags = this.memory.read16(ring_start),
// index of the next free ring
idx = this.memory.read16(ring_start + 2);
dbg_log("idx=" + h(idx, 4), LOG_VIRTIO);
//dbg_assert(idx < this.queue_size);
var mask = this.queue_size - 1;
idx &= mask;
while(this.last_idx !== idx)
{
var desc_idx = this.memory.read16(ring_desc_start + this.last_idx * 2);
this.handle_descriptor(desc_idx);
this.last_idx = this.last_idx + 1 & mask;
}
});
this.irq = 0xC;
this.pic = cpu.devices.pic;
this.queue_select = 0;
this.device_status = 0;
this.isr = 0;
// these should be stored per queue if there is more than one queue
this.last_idx = 0;
this.queue_size = 32;
this.queue_address = 0;
this.memory = cpu.memory;
for(var i = 0; i < 128; i++)
{
io.register_read(0xA814 + i, this, function(port)
{
dbg_log("Read device " + h(port), LOG_VIRTIO);
//dbg_assert(typeof this.device.configspace[port] === "number");
return this.device.configspace[port];
}.bind(this, i), undefined, undefined);
io.register_write(0xA814 + i, this, function(port, data)
{
dbg_log("Write device " + h(port) + ": " + h(data, 2), LOG_VIRTIO);
}.bind(this, i), undefined, undefined);
}
// should be generalized to support more devices than just the filesystem
this.device = new Virtio9p(filesystem);
this.device.SendReply = this.device_reply.bind(this);
this._state_skip = ["memory", "pic"];
this._state_restore = function()
{
this.device.SendReply = this.device_reply.bind(this);
};
}
VirtIO.prototype.handle_descriptor = function(idx)
{
var next = idx;
var desc_start = this.queue_address << 12;
var buffer_idx = 0;
var buffers = [];
do
{
var addr = desc_start + next * 16;
var flags = this.memory.read16(addr + 12);
if(flags & VRING_DESC_F_WRITE)
{
break;
}
if(flags & VRING_DESC_F_INDIRECT) {
dbg_assert(false, "unsupported");
}
var addr_low = this.memory.read32s(addr);
var addr_high = this.memory.read32s(addr + 4);
var len = this.memory.read32s(addr + 8) >>> 0;
buffers.push({
addr_low: addr_low,
addr_high: addr_high,
len: len,
});
dbg_log("descriptor: addr=" + h(addr_high, 8) + ":" + h(addr_low, 8) +
" len=" + h(len, 8) + " flags=" + h(flags, 4) + " next=" + h(next, 4), LOG_VIRTIO);
if(flags & VRING_DESC_F_NEXT)
{
next = this.memory.read16(addr + 14);
dbg_assert(next < this.queue_size);
}
else
{
next = -1;
break;
}
}
while(true);
var buffer_len = -1;
var pointer = 0;
var infos = {
start: idx,
next: next,
};
this.device.ReceiveRequest(infos, function()
{
// return one byte
if(pointer >= buffer_len)
{
if(buffer_idx === buffers.length)
{
dbg_log("Read more data than descriptor has", LOG_VIRTIO);
return 0;
}
var buf = buffers[buffer_idx++];
addr_low = buf.addr_low;
buffer_len = buf.len;
pointer = 0;
}
return this.memory.read8(addr_low + pointer++);
}.bind(this));
};
VirtIO.prototype.device_reply = function(infos)
{
if(infos.next === -1)
{
dbg_log("Reply to invalid index", LOG_VIRTIO);
return;
}
var mask = this.queue_size - 1;
var result_length = this.device.replybuffersize;
var next = infos.next;
var desc_start = this.queue_address << 12;
var buffer_idx = 0;
var buffers = [];
do
{
var addr = desc_start + next * 16;
var flags = this.memory.read16(addr + 12);
if((flags & VRING_DESC_F_WRITE) === 0)
{
dbg_log("Bug: Readonly ring after writeonly ring", LOG_VIRTIO);
break;
}
var addr_low = this.memory.read32s(addr);
var addr_high = this.memory.read32s(addr + 4);
var len = this.memory.read32s(addr + 8) >>> 0;
buffers.push({
addr_low: addr_low,
addr_high: addr_high,
len: len,
});
dbg_log("descriptor: addr=" + h(addr_high, 8) + ":" + h(addr_low, 8) +
" len=" + h(len, 8) + " flags=" + h(flags, 4) + " next=" + h(next, 4), LOG_VIRTIO);
if(flags & VRING_DESC_F_NEXT)
{
next = this.memory.read16(addr + 14);
dbg_assert(next < this.queue_size);
}
else
{
break;
}
}
while(true);
var buffer_len = -1;
var pointer = 0;
for(var i = 0; i < result_length; i++)
{
var data = this.device.replybuffer[i];
if(pointer >= buffer_len)
{
if(buffer_idx === buffers.length)
{
dbg_log("Write more data than descriptor has", LOG_VIRTIO);
return 0;
}
var buf = buffers[buffer_idx++];
addr_low = buf.addr_low;
buffer_len = buf.len;
pointer = 0;
}
this.memory.write8(addr_low + pointer++, data);
}
var used_desc_start = (this.queue_address << 12) + 16 * this.queue_size + 4 + 2 * this.queue_size;
used_desc_start = used_desc_start + 4095 & ~4095;
var flags = this.memory.read16(used_desc_start);
var used_idx = this.memory.read16(used_desc_start + 2);
this.memory.write16(used_desc_start + 2, used_idx + 1);
dbg_log("used descriptor: addr=" + h(used_desc_start, 8) + " flags=" + h(flags, 4) + " idx=" + h(used_idx, 4), LOG_VIRTIO);
used_idx &= mask;
var used_desc_offset = used_desc_start + 4 + used_idx * 8;
this.memory.write32(used_desc_offset, infos.start);
this.memory.write32(used_desc_offset + 4, result_length);
this.isr |= 1;
this.pic.push_irq(this.irq);
};