Skip to content
This repository was archived by the owner on Jul 3, 2020. It is now read-only.

Commit 18e8236

Browse files
authored
Merge pull request #133 from runtimejs/disk-driver
Disk driver!
2 parents b177050 + f854db6 commit 18e8236

10 files changed

Lines changed: 325 additions & 4 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright 2016-present runtime.js project authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
'use strict';
16+
17+
const typeutils = require('typeutils');
18+
19+
const busHandle = Symbol('bus');
20+
const formatInfoHandle = Symbol('formatInfo');
21+
const nameHandle = Symbol('name');
22+
const setNameHandle = Symbol('setName');
23+
24+
class BlockDeviceInterface {
25+
constructor(bus = '', init = {}) {
26+
this[busHandle] = bus;
27+
this[formatInfoHandle] = {};
28+
this.onread = null;
29+
this.onwrite = null;
30+
this.ongetonline = null;
31+
if (typeutils.isObject(init)) {
32+
if (typeutils.isFunction(init.read)) {
33+
this.onread = init.read;
34+
}
35+
if (typeutils.isFunction(init.write)) {
36+
this.write = init.write;
37+
}
38+
if (typeutils.isObject(init.formatInfo)) {
39+
this[formatInfoHandle] = init.formatInfo;
40+
}
41+
if (typeutils.isFunction(init.isOnline)) {
42+
this.ongetonline = init.isOnline;
43+
}
44+
}
45+
}
46+
get name() {
47+
return this[nameHandle];
48+
}
49+
get bus() {
50+
return this[busHandle];
51+
}
52+
[setNameHandle](newName) {
53+
this[nameHandle] = newName;
54+
}
55+
read(sector, u8) {
56+
if (!this.onread) {
57+
throw new Error('driver was not initialized');
58+
}
59+
return this.onread(sector, u8);
60+
}
61+
write(sector, u8) {
62+
if (!this.onwrite) {
63+
throw new Error('driver was not initialized');
64+
}
65+
return this.onwrite(sector, u8);
66+
}
67+
get formatInfo() {
68+
return this[formatInfoHandle];
69+
}
70+
get isOnline() {
71+
if (!this.ongetonline) {
72+
throw new Error('driver was not initialized');
73+
}
74+
return this.ongetonline();
75+
}
76+
}
77+
78+
module.exports = {
79+
BlockDeviceInterface,
80+
setNameHandle,
81+
};

js/core/block/devices.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2016-present runtime.js project authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
'use strict';
16+
17+
const { setNameHandle } = require('./block-device-interface');
18+
const availableBuses = Object.create(null); // not using a Map because the buses should be easy to access directly
19+
const availableDevices = [];
20+
21+
module.exports = {
22+
registerDevice(device) {
23+
if (!availableBuses[device.bus]) availableBuses[device.bus] = [];
24+
const i = availableDevices.push(device) - 1;
25+
device[setNameHandle](`${device.bus}${i}`);
26+
availableBuses[device.bus].push(device);
27+
28+
console.log(`[block] registered block device ${device.name}`);
29+
},
30+
getDevices() {
31+
return availableDevices;
32+
},
33+
getBuses() {
34+
return availableBuses;
35+
},
36+
};

js/core/block/index.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2016-present runtime.js project authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
'use strict';
16+
const { BlockDeviceInterface } = require('./block-device-interface');
17+
const { registerDevice, getDevices, getBuses } = require('./devices');
18+
19+
module.exports = {
20+
BlockDeviceInterface,
21+
registerDevice,
22+
get devices() {
23+
return getDevices();
24+
},
25+
get buses() {
26+
return getBuses();
27+
},
28+
};

js/core/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const { allocator } = require('./resources');
1818
require('./polyfill');
1919

2020
const random = require('./random');
21+
const block = require('./block');
2122
const keyboard = require('./keyboard');
2223
const ps2 = require('./ps2');
2324
const pci = require('./pci');
@@ -28,6 +29,7 @@ class Runtime {
2829
constructor() {
2930
Object.assign(this, {
3031
random,
32+
block,
3133
keyboard,
3234
pci,
3335
ps2,

js/driver/virtio/blk.js

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Copyright 2016-present runtime.js project authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
'use strict';
16+
const VirtioDevice = require('./device');
17+
const runtime = require('../../core');
18+
const { Uint64LE } = require('int64-buffer');
19+
20+
const VIRTIO_BLK_T_IN = 0;
21+
const VIRTIO_BLK_T_OUT = 1;
22+
23+
function initializeBlockDevice(pciDevice) {
24+
const ioSpace = pciDevice.getBAR(0).resource;
25+
const irq = pciDevice.getIRQ();
26+
27+
const features = {
28+
VIRTIO_BLK_F_SIZE_MAX: 1,
29+
VIRTIO_BLK_F_SEG_MAX: 2,
30+
VIRTIO_BLK_F_GEOMETRY: 4,
31+
VIRTIO_BLK_F_RO: 5,
32+
VIRTIO_BLK_F_BLK_SIZE: 6,
33+
VIRTIO_BLK_F_FLUSH: 9,
34+
VIRTIO_BLK_F_TOPOLOGY: 10,
35+
VIRTIO_BLK_F_CONFIG_WCE: 11,
36+
};
37+
38+
const dev = new VirtioDevice('blk', ioSpace);
39+
dev.setDriverAck();
40+
41+
const driverFeatures = {
42+
// VIRTIO_BLK_F_SIZE_MAX: true,
43+
VIRTIO_BLK_F_SEG_MAX: true,
44+
VIRTIO_BLK_F_GEOMETRY: true,
45+
VIRTIO_BLK_F_BLK_SIZE: true,
46+
VIRTIO_BLK_F_TOPOLOGY: true,
47+
};
48+
49+
const deviceFeatures = dev.readDeviceFeatures(features);
50+
debug(JSON.stringify(deviceFeatures));
51+
52+
if (!dev.writeGuestFeatures(features, driverFeatures, deviceFeatures)) {
53+
debug('[virtio] blk driver is unable to start');
54+
return;
55+
}
56+
57+
const QUEUE_ID_REQ = 0;
58+
59+
const reqQueue = dev.queueSetup(QUEUE_ID_REQ);
60+
const promiseQueue = [];
61+
62+
const sectorSize = 512;
63+
const sectorCount = dev.blkReadSectorCount();
64+
const totalSectorCount = dev.blkReadTotalSectorCount();
65+
66+
function buildHeader(type, sector) {
67+
const u8 = new Uint8Array(16);
68+
const view = new DataView(u8.buffer);
69+
view.setUint32(0, type, true);
70+
view.setUint32(4, 0, true); // priority: low
71+
u8.set((new Uint64LE(sector)).toArray(), 8);
72+
return u8;
73+
}
74+
75+
const diskDriver = new runtime.block.BlockDeviceInterface('virtio', {
76+
read(sector, data) {
77+
return new Promise((resolve, reject) => {
78+
if (sector > totalSectorCount) {
79+
reject(new RangeError(`sector ${sector} out of bounds (max ${totalSectorCount}, non-inclusive)`));
80+
return;
81+
}
82+
const status = new Uint8Array(1);
83+
promiseQueue.push([resolve, reject, VIRTIO_BLK_T_IN, data, status]);
84+
reqQueue.placeBuffers([buildHeader(VIRTIO_BLK_T_IN, sector), data, status], false, [false, true, true]);
85+
86+
if (reqQueue.isNotificationNeeded()) {
87+
dev.queueNotify(QUEUE_ID_REQ);
88+
}
89+
});
90+
},
91+
write(sector, data) {
92+
return new Promise((resolve, reject) => {
93+
const status = new Uint8Array(1);
94+
promiseQueue.push([resolve, reject, VIRTIO_BLK_T_OUT, data, status]);
95+
reqQueue.placeBuffers([buildHeader(VIRTIO_BLK_T_OUT, sector), data, status], false, [false, false, true]);
96+
97+
if (reqQueue.isNotificationNeeded()) {
98+
dev.queueNotify(QUEUE_ID_REQ);
99+
}
100+
});
101+
},
102+
formatInfo: {
103+
sectorSize,
104+
sectorCount,
105+
totalSectorCount,
106+
},
107+
isOnline() {
108+
return true; // TODO: actually check if the disk is online or not
109+
},
110+
});
111+
112+
runtime.block.registerDevice(diskDriver);
113+
114+
function recvBuffer() {
115+
if (promiseQueue.length === 0) {
116+
return;
117+
}
118+
const [resolve, reject, type, data, status] = promiseQueue.shift();
119+
setImmediate(() => {
120+
if (status[0] !== 0) return reject(new Error('IO error'));
121+
if (type === VIRTIO_BLK_T_IN) {
122+
resolve(data);
123+
} else {
124+
resolve();
125+
}
126+
});
127+
}
128+
129+
irq.on(() => {
130+
if (!dev.hasPendingIRQ()) {
131+
return;
132+
}
133+
reqQueue.fetchBuffers(recvBuffer);
134+
});
135+
136+
dev.setDriverReady();
137+
}
138+
139+
module.exports = initializeBlockDevice;

js/driver/virtio/device.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,17 @@ class VirtioDevice {
4747
ioPorts.NETWORK_DEVICE_STATUS = 0x1A; // 16 bit r
4848
}
4949

50+
if (deviceType === 'blk') {
51+
// Block device
52+
ioPorts.BLOCK_TOTAL_SECTOR_COUNT = 0x14; // 64 bit r
53+
ioPorts.BLOCK_MAX_SEGMENT_SIZE = 0x1c; // 32 bit r
54+
ioPorts.BLOCK_MAX_SEGMENT_COUNT = 0x20; // 32 bit r
55+
ioPorts.BLOCK_CYLINDER_COUNT = 0x24; // 16 bit r
56+
ioPorts.BLOCK_HEAD_COUNT = 0x26; // 8 bit r
57+
ioPorts.BLOCK_SECTOR_COUNT = 0x27; // 8 bit r
58+
ioPorts.BLOCK_BLOCK_LENGTH = 0x28; // 32 bit r
59+
}
60+
5061
const ports = {};
5162
for (const portName of Object.keys(ioPorts)) {
5263
const portOffset = ioPorts[portName];
@@ -128,6 +139,16 @@ class VirtioDevice {
128139
netReadStatus() {
129140
return !!(1 & this.io.NETWORK_DEVICE_STATUS.read16());
130141
}
142+
143+
// [block device]
144+
blkReadSectorCount() {
145+
return this.io.BLOCK_SECTOR_COUNT.read8();
146+
}
147+
148+
// [block device]
149+
blkReadTotalSectorCount() {
150+
return this.io.BLOCK_TOTAL_SECTOR_COUNT.read32();
151+
}
131152
}
132153

133154
module.exports = VirtioDevice;

js/driver/virtio/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414

1515
'use strict';
1616
const virtioNet = require('./net');
17+
const virtioBlk = require('./blk');
1718
const virtioRNG = require('./rng');
1819
const VIRTIO_SUBSYSTEM_NETWORK = 1;
20+
const VIRTIO_SUBSYSTEM_BLOCK = 2;
1921
const VIRTIO_SUBSYSTEM_RNG = 4;
2022
const runtime = require('../../core');
2123

@@ -26,6 +28,9 @@ const driver = {
2628
if (subsystemId === VIRTIO_SUBSYSTEM_NETWORK) {
2729
return virtioNet(pciDevice);
2830
}
31+
if (subsystemId === VIRTIO_SUBSYSTEM_BLOCK) {
32+
return virtioBlk(pciDevice);
33+
}
2934
if (subsystemId === VIRTIO_SUBSYSTEM_RNG) {
3035
return virtioRNG(pciDevice);
3136
}

js/driver/virtio/vring/descriptor-table.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,9 @@ class DescriptorTable {
8484
* @param buffers {array} array of Uint8Array buffers
8585
* @param lengths {array} array of corresponding buffer lengths (same size as buffers)
8686
* @param isWriteOnly {bool} set writeOnly flag for each buffer
87+
* @param isWriteOnlyArray {array} optional array of writeOnly flags, one value for each buffer
8788
*/
88-
placeBuffers(buffers, lengths, isWriteOnly) {
89+
placeBuffers(buffers, lengths, isWriteOnly, isWriteOnlyArray) {
8990
const count = buffers.length;
9091
if (this.descriptorsAvailable < count) {
9192
return -1;
@@ -95,11 +96,17 @@ class DescriptorTable {
9596
const first = head;
9697
for (let i = 0; i < count; ++i) {
9798
const d = buffers[i];
99+
let bufWriteOnly = false;
100+
if (isWriteOnlyArray) {
101+
bufWriteOnly = isWriteOnlyArray[i];
102+
} else {
103+
bufWriteOnly = isWriteOnly;
104+
}
98105
let flags = 0;
99106
if (count !== i + 1) {
100107
flags |= VRING_DESC_F_NEXT;
101108
}
102-
if (isWriteOnly) {
109+
if (bufWriteOnly) {
103110
flags |= VRING_DESC_F_WRITE;
104111
}
105112

0 commit comments

Comments
 (0)