@aptre/v86 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/LICENSE.MIT +22 -0
- package/Readme.md +237 -0
- package/dist/v86.browser.js +26666 -0
- package/dist/v86.browser.js.map +7 -0
- package/dist/v86.js +26632 -0
- package/dist/v86.js.map +7 -0
- package/gen/generate_analyzer.ts +512 -0
- package/gen/generate_interpreter.ts +522 -0
- package/gen/generate_jit.ts +624 -0
- package/gen/rust_ast.ts +107 -0
- package/gen/util.ts +35 -0
- package/gen/x86_table.ts +1836 -0
- package/lib/9p.ts +1547 -0
- package/lib/filesystem.ts +1879 -0
- package/lib/marshall.ts +168 -0
- package/lib/softfloat/softfloat.c +32501 -0
- package/lib/zstd/zstddeclib.c +13520 -0
- package/package.json +75 -0
- package/src/acpi.ts +267 -0
- package/src/browser/dummy_screen.ts +106 -0
- package/src/browser/fake_network.ts +1771 -0
- package/src/browser/fetch_network.ts +361 -0
- package/src/browser/filestorage.ts +124 -0
- package/src/browser/inbrowser_network.ts +57 -0
- package/src/browser/keyboard.ts +564 -0
- package/src/browser/main.ts +3415 -0
- package/src/browser/mouse.ts +255 -0
- package/src/browser/network.ts +142 -0
- package/src/browser/print_stats.ts +336 -0
- package/src/browser/screen.ts +978 -0
- package/src/browser/serial.ts +316 -0
- package/src/browser/speaker.ts +1223 -0
- package/src/browser/starter.ts +1688 -0
- package/src/browser/wisp_network.ts +332 -0
- package/src/browser/worker_bus.ts +64 -0
- package/src/buffer.ts +652 -0
- package/src/bus.ts +78 -0
- package/src/const.ts +128 -0
- package/src/cpu.ts +2891 -0
- package/src/dma.ts +474 -0
- package/src/elf.ts +251 -0
- package/src/floppy.ts +1778 -0
- package/src/ide.ts +3455 -0
- package/src/io.ts +504 -0
- package/src/iso9660.ts +317 -0
- package/src/kernel.ts +250 -0
- package/src/lib.ts +645 -0
- package/src/log.ts +149 -0
- package/src/main.ts +199 -0
- package/src/ne2k.ts +1589 -0
- package/src/pci.ts +815 -0
- package/src/pit.ts +406 -0
- package/src/ps2.ts +820 -0
- package/src/rtc.ts +537 -0
- package/src/rust/analysis.rs +101 -0
- package/src/rust/codegen.rs +2660 -0
- package/src/rust/config.rs +3 -0
- package/src/rust/control_flow.rs +425 -0
- package/src/rust/cpu/apic.rs +658 -0
- package/src/rust/cpu/arith.rs +1207 -0
- package/src/rust/cpu/call_indirect.rs +2 -0
- package/src/rust/cpu/cpu.rs +4501 -0
- package/src/rust/cpu/fpu.rs +923 -0
- package/src/rust/cpu/global_pointers.rs +112 -0
- package/src/rust/cpu/instructions.rs +2486 -0
- package/src/rust/cpu/instructions_0f.rs +5261 -0
- package/src/rust/cpu/ioapic.rs +316 -0
- package/src/rust/cpu/memory.rs +351 -0
- package/src/rust/cpu/misc_instr.rs +613 -0
- package/src/rust/cpu/mod.rs +16 -0
- package/src/rust/cpu/modrm.rs +133 -0
- package/src/rust/cpu/pic.rs +402 -0
- package/src/rust/cpu/sse_instr.rs +361 -0
- package/src/rust/cpu/string.rs +701 -0
- package/src/rust/cpu/vga.rs +175 -0
- package/src/rust/cpu_context.rs +69 -0
- package/src/rust/dbg.rs +98 -0
- package/src/rust/gen/analyzer.rs +3807 -0
- package/src/rust/gen/analyzer0f.rs +3992 -0
- package/src/rust/gen/interpreter.rs +4447 -0
- package/src/rust/gen/interpreter0f.rs +5404 -0
- package/src/rust/gen/jit.rs +5080 -0
- package/src/rust/gen/jit0f.rs +5547 -0
- package/src/rust/gen/mod.rs +14 -0
- package/src/rust/jit.rs +2443 -0
- package/src/rust/jit_instructions.rs +7881 -0
- package/src/rust/js_api.rs +6 -0
- package/src/rust/leb.rs +46 -0
- package/src/rust/lib.rs +29 -0
- package/src/rust/modrm.rs +330 -0
- package/src/rust/opstats.rs +249 -0
- package/src/rust/page.rs +15 -0
- package/src/rust/paging.rs +25 -0
- package/src/rust/prefix.rs +15 -0
- package/src/rust/profiler.rs +155 -0
- package/src/rust/regs.rs +38 -0
- package/src/rust/softfloat.rs +286 -0
- package/src/rust/state_flags.rs +27 -0
- package/src/rust/wasmgen/mod.rs +2 -0
- package/src/rust/wasmgen/wasm_builder.rs +1047 -0
- package/src/rust/wasmgen/wasm_opcodes.rs +221 -0
- package/src/rust/zstd.rs +105 -0
- package/src/sb16.ts +1928 -0
- package/src/state.ts +359 -0
- package/src/uart.ts +472 -0
- package/src/vga.ts +2791 -0
- package/src/virtio.ts +1756 -0
- package/src/virtio_balloon.ts +273 -0
- package/src/virtio_console.ts +372 -0
- package/src/virtio_net.ts +326 -0
package/src/virtio.ts
ADDED
|
@@ -0,0 +1,1756 @@
|
|
|
1
|
+
declare let DEBUG: boolean
|
|
2
|
+
|
|
3
|
+
import { LOG_VIRTIO } from './const.js'
|
|
4
|
+
import { h, int_log2 } from './lib.js'
|
|
5
|
+
import { dbg_assert, dbg_log } from './log.js'
|
|
6
|
+
|
|
7
|
+
// Minimal interface for the CPU fields VirtIO needs
|
|
8
|
+
interface VirtioCPU {
|
|
9
|
+
io: VirtioIO
|
|
10
|
+
devices: {
|
|
11
|
+
pci: VirtioPCI
|
|
12
|
+
net?: unknown
|
|
13
|
+
}
|
|
14
|
+
read16(addr: number): number
|
|
15
|
+
read32s(addr: number): number
|
|
16
|
+
write16(addr: number, value: number): void
|
|
17
|
+
write32(addr: number, value: number): void
|
|
18
|
+
read_blob(addr: number, length: number): Uint8Array
|
|
19
|
+
write_blob(blob: Uint8Array, addr: number): void
|
|
20
|
+
zero_memory(addr: number, length: number): void
|
|
21
|
+
memory_size: Int32Array
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface VirtioIO {
|
|
25
|
+
register_read(
|
|
26
|
+
port: number,
|
|
27
|
+
device: object,
|
|
28
|
+
r8?: ((port: number) => number) | undefined,
|
|
29
|
+
r16?: ((port: number) => number) | undefined,
|
|
30
|
+
r32?: ((port: number) => number) | undefined,
|
|
31
|
+
): void
|
|
32
|
+
register_write(
|
|
33
|
+
port: number,
|
|
34
|
+
device: object,
|
|
35
|
+
w8?: ((port: number) => void) | undefined,
|
|
36
|
+
w16?: ((port: number) => void) | undefined,
|
|
37
|
+
w32?: ((port: number) => void) | undefined,
|
|
38
|
+
): void
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface VirtioPCI {
|
|
42
|
+
register_device(device: VirtIO): void
|
|
43
|
+
raise_irq(pci_id: number): void
|
|
44
|
+
lower_irq(pci_id: number): void
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html
|
|
48
|
+
|
|
49
|
+
const VIRTIO_PCI_VENDOR_ID = 0x1af4
|
|
50
|
+
// Identifies vendor-specific PCI capability.
|
|
51
|
+
const VIRTIO_PCI_CAP_VENDOR = 0x09
|
|
52
|
+
// Length (bytes) of VIRTIO_PCI_CAP linked list entry.
|
|
53
|
+
const VIRTIO_PCI_CAP_LENGTH = 16
|
|
54
|
+
|
|
55
|
+
// Capability types.
|
|
56
|
+
|
|
57
|
+
const VIRTIO_PCI_CAP_COMMON_CFG = 1
|
|
58
|
+
const VIRTIO_PCI_CAP_NOTIFY_CFG = 2
|
|
59
|
+
const VIRTIO_PCI_CAP_ISR_CFG = 3
|
|
60
|
+
const VIRTIO_PCI_CAP_DEVICE_CFG = 4
|
|
61
|
+
const VIRTIO_PCI_CAP_PCI_CFG = 5
|
|
62
|
+
|
|
63
|
+
// Status bits (device_status values).
|
|
64
|
+
|
|
65
|
+
const VIRTIO_STATUS_ACKNOWLEDGE = 1
|
|
66
|
+
const VIRTIO_STATUS_DRIVER = 2
|
|
67
|
+
const VIRTIO_STATUS_DRIVER_OK = 4
|
|
68
|
+
const VIRTIO_STATUS_FEATURES_OK = 8
|
|
69
|
+
const VIRTIO_STATUS_DEVICE_NEEDS_RESET = 64
|
|
70
|
+
const VIRTIO_STATUS_FAILED = 128
|
|
71
|
+
|
|
72
|
+
// ISR bits (isr_status values).
|
|
73
|
+
|
|
74
|
+
const VIRTIO_ISR_QUEUE = 1
|
|
75
|
+
const VIRTIO_ISR_DEVICE_CFG = 2
|
|
76
|
+
|
|
77
|
+
// Feature bits (bit positions).
|
|
78
|
+
|
|
79
|
+
export const VIRTIO_F_RING_INDIRECT_DESC = 28
|
|
80
|
+
export const VIRTIO_F_RING_EVENT_IDX = 29
|
|
81
|
+
export const VIRTIO_F_VERSION_1 = 32
|
|
82
|
+
|
|
83
|
+
// Queue struct sizes.
|
|
84
|
+
|
|
85
|
+
// Size (bytes) of the virtq_desc struct per queue size.
|
|
86
|
+
const VIRTQ_DESC_ENTRYSIZE = 16
|
|
87
|
+
// Size (bytes) of the virtq_avail struct ignoring ring entries.
|
|
88
|
+
const _VIRTQ_AVAIL_BASESIZE = 6
|
|
89
|
+
// Size (bytes) of the virtq_avail struct per queue size.
|
|
90
|
+
const VIRTQ_AVAIL_ENTRYSIZE = 2
|
|
91
|
+
// Size (bytes) of the virtq_used struct ignoring ring entries.
|
|
92
|
+
const _VIRTQ_USED_BASESIZE = 6
|
|
93
|
+
// Size (bytes) of the virtq_desc struct per queue size.
|
|
94
|
+
const VIRTQ_USED_ENTRYSIZE = 8
|
|
95
|
+
// Mask for wrapping the idx field of the virtq_used struct so that the value
|
|
96
|
+
// naturally overflows after 65535 (idx is a word).
|
|
97
|
+
const VIRTQ_IDX_MASK = 0xffff
|
|
98
|
+
|
|
99
|
+
// Queue flags.
|
|
100
|
+
|
|
101
|
+
const VIRTQ_DESC_F_NEXT = 1
|
|
102
|
+
const VIRTQ_DESC_F_WRITE = 2
|
|
103
|
+
const VIRTQ_DESC_F_INDIRECT = 4
|
|
104
|
+
const VIRTQ_AVAIL_F_NO_INTERRUPT = 1
|
|
105
|
+
const _VIRTQ_USED_F_NO_NOTIFY = 1
|
|
106
|
+
|
|
107
|
+
// TypeScript types.
|
|
108
|
+
|
|
109
|
+
export interface VirtIO_CapabilityStructEntry {
|
|
110
|
+
bytes: number
|
|
111
|
+
name: string
|
|
112
|
+
read: () => number
|
|
113
|
+
write: (data: number) => void
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export type VirtIO_CapabilityStruct = VirtIO_CapabilityStructEntry[]
|
|
117
|
+
|
|
118
|
+
export interface VirtIO_CapabilityInfo {
|
|
119
|
+
type: number
|
|
120
|
+
bar: number
|
|
121
|
+
port: number
|
|
122
|
+
use_mmio: boolean
|
|
123
|
+
offset: number
|
|
124
|
+
extra: Uint8Array
|
|
125
|
+
struct: VirtIO_CapabilityStruct
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface VirtQueue_Options {
|
|
129
|
+
size_supported: number
|
|
130
|
+
notify_offset: number
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface VirtIO_CommonCapabilityOptions {
|
|
134
|
+
initial_port: number
|
|
135
|
+
queues: VirtQueue_Options[]
|
|
136
|
+
features: number[]
|
|
137
|
+
on_driver_ok: () => void
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface VirtIO_NotificationCapabilityOptions {
|
|
141
|
+
initial_port: number
|
|
142
|
+
single_handler: boolean
|
|
143
|
+
handlers: ((queue_id: number) => void)[]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface VirtIO_ISRCapabilityOptions {
|
|
147
|
+
initial_port: number
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface VirtIO_DeviceSpecificCapabilityOptions {
|
|
151
|
+
initial_port: number
|
|
152
|
+
struct: VirtIO_CapabilityStruct
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface VirtIO_Options {
|
|
156
|
+
name: string
|
|
157
|
+
pci_id: number
|
|
158
|
+
device_id: number
|
|
159
|
+
subsystem_device_id: number
|
|
160
|
+
common: VirtIO_CommonCapabilityOptions
|
|
161
|
+
notification: VirtIO_NotificationCapabilityOptions
|
|
162
|
+
isr_status: VirtIO_ISRCapabilityOptions
|
|
163
|
+
device_specific?: VirtIO_DeviceSpecificCapabilityOptions
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
interface VirtQueueDescriptor {
|
|
167
|
+
addr_low: number
|
|
168
|
+
addr_high: number
|
|
169
|
+
len: number
|
|
170
|
+
flags: number
|
|
171
|
+
next: number
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export class VirtIO {
|
|
175
|
+
cpu: VirtioCPU
|
|
176
|
+
pci: VirtioPCI
|
|
177
|
+
device_id: number
|
|
178
|
+
pci_space: number[]
|
|
179
|
+
pci_id: number
|
|
180
|
+
pci_bars: { size: number }[]
|
|
181
|
+
name: string
|
|
182
|
+
device_feature_select: number
|
|
183
|
+
driver_feature_select: number
|
|
184
|
+
device_feature: Uint32Array
|
|
185
|
+
driver_feature: Uint32Array
|
|
186
|
+
features_ok: boolean
|
|
187
|
+
device_status: number
|
|
188
|
+
config_has_changed: boolean
|
|
189
|
+
config_generation: number
|
|
190
|
+
queues: VirtQueue[]
|
|
191
|
+
queue_select: number
|
|
192
|
+
queue_selected: VirtQueue | null
|
|
193
|
+
isr_status: number
|
|
194
|
+
|
|
195
|
+
constructor(cpu: VirtioCPU, options: VirtIO_Options) {
|
|
196
|
+
this.cpu = cpu
|
|
197
|
+
this.pci = cpu.devices.pci
|
|
198
|
+
this.device_id = options.device_id
|
|
199
|
+
|
|
200
|
+
this.pci_space = [
|
|
201
|
+
// Vendor ID
|
|
202
|
+
VIRTIO_PCI_VENDOR_ID & 0xff,
|
|
203
|
+
VIRTIO_PCI_VENDOR_ID >> 8,
|
|
204
|
+
// Device ID
|
|
205
|
+
options.device_id & 0xff,
|
|
206
|
+
options.device_id >> 8,
|
|
207
|
+
// Command
|
|
208
|
+
0x07,
|
|
209
|
+
0x05,
|
|
210
|
+
// Status - enable capabilities list
|
|
211
|
+
0x10,
|
|
212
|
+
0x00,
|
|
213
|
+
// Revision ID
|
|
214
|
+
0x01,
|
|
215
|
+
// Prof IF, Subclass, Class code
|
|
216
|
+
0x00,
|
|
217
|
+
0x02,
|
|
218
|
+
0x00,
|
|
219
|
+
// Cache line size
|
|
220
|
+
0x00,
|
|
221
|
+
// Latency Timer
|
|
222
|
+
0x00,
|
|
223
|
+
// Header Type
|
|
224
|
+
0x00,
|
|
225
|
+
// Built-in self test
|
|
226
|
+
0x00,
|
|
227
|
+
// BAR0
|
|
228
|
+
0x01,
|
|
229
|
+
0xa8,
|
|
230
|
+
0x00,
|
|
231
|
+
0x00,
|
|
232
|
+
// BAR1
|
|
233
|
+
0x00,
|
|
234
|
+
0x10,
|
|
235
|
+
0xbf,
|
|
236
|
+
0xfe,
|
|
237
|
+
// BAR2
|
|
238
|
+
0x00,
|
|
239
|
+
0x00,
|
|
240
|
+
0x00,
|
|
241
|
+
0x00,
|
|
242
|
+
// BAR3
|
|
243
|
+
0x00,
|
|
244
|
+
0x00,
|
|
245
|
+
0x00,
|
|
246
|
+
0x00,
|
|
247
|
+
// BAR4
|
|
248
|
+
0x00,
|
|
249
|
+
0x00,
|
|
250
|
+
0x00,
|
|
251
|
+
0x00,
|
|
252
|
+
// BAR5
|
|
253
|
+
0x00,
|
|
254
|
+
0x00,
|
|
255
|
+
0x00,
|
|
256
|
+
0x00,
|
|
257
|
+
// CardBus CIS pointer
|
|
258
|
+
0x00,
|
|
259
|
+
0x00,
|
|
260
|
+
0x00,
|
|
261
|
+
0x00,
|
|
262
|
+
// Subsystem vendor ID
|
|
263
|
+
VIRTIO_PCI_VENDOR_ID & 0xff,
|
|
264
|
+
VIRTIO_PCI_VENDOR_ID >> 8,
|
|
265
|
+
// Subsystem ID
|
|
266
|
+
options.subsystem_device_id & 0xff,
|
|
267
|
+
options.subsystem_device_id >> 8,
|
|
268
|
+
// Expansion ROM base address
|
|
269
|
+
0x00,
|
|
270
|
+
0x00,
|
|
271
|
+
0x00,
|
|
272
|
+
0x00,
|
|
273
|
+
// Capabilities pointer
|
|
274
|
+
0x40,
|
|
275
|
+
// Reserved
|
|
276
|
+
0x00,
|
|
277
|
+
0x00,
|
|
278
|
+
0x00,
|
|
279
|
+
// Reserved
|
|
280
|
+
0x00,
|
|
281
|
+
0x00,
|
|
282
|
+
0x00,
|
|
283
|
+
0x00,
|
|
284
|
+
// Interrupt line
|
|
285
|
+
0x00,
|
|
286
|
+
// Interrupt pin
|
|
287
|
+
0x01,
|
|
288
|
+
// Min grant
|
|
289
|
+
0x00,
|
|
290
|
+
// Max latency
|
|
291
|
+
0x00,
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
// Prevent sparse arrays by preallocating.
|
|
295
|
+
this.pci_space = this.pci_space.concat(
|
|
296
|
+
Array(256 - this.pci_space.length).fill(0),
|
|
297
|
+
)
|
|
298
|
+
// Remaining PCI space is appended by capabilities further below.
|
|
299
|
+
|
|
300
|
+
this.pci_id = options.pci_id
|
|
301
|
+
|
|
302
|
+
// PCI bars gets filled in by capabilities further below.
|
|
303
|
+
this.pci_bars = []
|
|
304
|
+
|
|
305
|
+
this.name = options.name
|
|
306
|
+
|
|
307
|
+
// Feature bits grouped in dwords, dword selected by device_feature_select.
|
|
308
|
+
this.device_feature_select = 0
|
|
309
|
+
this.driver_feature_select = 0
|
|
310
|
+
|
|
311
|
+
// Unspecified upper bound. Assume 4*32=128 bits.
|
|
312
|
+
this.device_feature = new Uint32Array(4)
|
|
313
|
+
this.driver_feature = new Uint32Array(4)
|
|
314
|
+
for (const f of options.common.features) {
|
|
315
|
+
dbg_assert(
|
|
316
|
+
f >= 0,
|
|
317
|
+
'VirtIO device<' +
|
|
318
|
+
this.name +
|
|
319
|
+
'> feature bit numbers must be non-negative',
|
|
320
|
+
)
|
|
321
|
+
dbg_assert(
|
|
322
|
+
f < 128,
|
|
323
|
+
'VirtIO device<' +
|
|
324
|
+
this.name +
|
|
325
|
+
'> feature bit numbers assumed less than 128 in implementation',
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
// Feature bits are grouped in 32 bits.
|
|
329
|
+
this.device_feature[f >>> 5] |= 1 << (f & 0x1f)
|
|
330
|
+
this.driver_feature[f >>> 5] |= 1 << (f & 0x1f)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
dbg_assert(
|
|
334
|
+
options.common.features.includes(VIRTIO_F_VERSION_1),
|
|
335
|
+
'VirtIO device<' +
|
|
336
|
+
this.name +
|
|
337
|
+
'> only non-transitional devices are supported',
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
// Indicates whether driver_feature bits is subset of device_feature bits.
|
|
341
|
+
this.features_ok = true
|
|
342
|
+
|
|
343
|
+
this.device_status = 0
|
|
344
|
+
|
|
345
|
+
this.config_has_changed = false
|
|
346
|
+
this.config_generation = 0
|
|
347
|
+
|
|
348
|
+
this.queues = []
|
|
349
|
+
for (const queue_options of options.common.queues) {
|
|
350
|
+
this.queues.push(new VirtQueue(cpu, this, queue_options))
|
|
351
|
+
}
|
|
352
|
+
this.queue_select = 0
|
|
353
|
+
this.queue_selected = this.queues[0]
|
|
354
|
+
|
|
355
|
+
this.isr_status = 0
|
|
356
|
+
|
|
357
|
+
// Verify notification options.
|
|
358
|
+
if (DEBUG) {
|
|
359
|
+
const offsets = new Set<number>()
|
|
360
|
+
for (const offset of this.queues.map((q) => q.notify_offset)) {
|
|
361
|
+
const effective_offset = options.notification.single_handler
|
|
362
|
+
? 0
|
|
363
|
+
: offset
|
|
364
|
+
offsets.add(effective_offset)
|
|
365
|
+
dbg_assert(
|
|
366
|
+
!!options.notification.handlers[effective_offset],
|
|
367
|
+
'VirtIO device<' +
|
|
368
|
+
this.name +
|
|
369
|
+
"> every queue's notifier must exist",
|
|
370
|
+
)
|
|
371
|
+
}
|
|
372
|
+
for (const [
|
|
373
|
+
index,
|
|
374
|
+
handler,
|
|
375
|
+
] of options.notification.handlers.entries()) {
|
|
376
|
+
dbg_assert(
|
|
377
|
+
!handler || offsets.has(index),
|
|
378
|
+
'VirtIO device<' +
|
|
379
|
+
this.name +
|
|
380
|
+
'> no defined notify handler should be unused',
|
|
381
|
+
)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const capabilities: VirtIO_CapabilityInfo[] = []
|
|
386
|
+
capabilities.push(this.create_common_capability(options.common))
|
|
387
|
+
capabilities.push(
|
|
388
|
+
this.create_notification_capability(options.notification),
|
|
389
|
+
)
|
|
390
|
+
capabilities.push(this.create_isr_capability(options.isr_status))
|
|
391
|
+
if (options.device_specific) {
|
|
392
|
+
capabilities.push(
|
|
393
|
+
this.create_device_specific_capability(options.device_specific),
|
|
394
|
+
)
|
|
395
|
+
}
|
|
396
|
+
this.init_capabilities(capabilities)
|
|
397
|
+
|
|
398
|
+
cpu.devices.pci.register_device(this)
|
|
399
|
+
this.reset()
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
create_common_capability(
|
|
403
|
+
options: VirtIO_CommonCapabilityOptions,
|
|
404
|
+
): VirtIO_CapabilityInfo {
|
|
405
|
+
return {
|
|
406
|
+
type: VIRTIO_PCI_CAP_COMMON_CFG,
|
|
407
|
+
bar: 0,
|
|
408
|
+
port: options.initial_port,
|
|
409
|
+
use_mmio: false,
|
|
410
|
+
offset: 0,
|
|
411
|
+
extra: new Uint8Array(0),
|
|
412
|
+
struct: [
|
|
413
|
+
{
|
|
414
|
+
bytes: 4,
|
|
415
|
+
name: 'device_feature_select',
|
|
416
|
+
read: () => this.device_feature_select,
|
|
417
|
+
write: (data: number) => {
|
|
418
|
+
this.device_feature_select = data
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
bytes: 4,
|
|
423
|
+
name: 'device_feature',
|
|
424
|
+
read: () =>
|
|
425
|
+
this.device_feature[this.device_feature_select] || 0,
|
|
426
|
+
write: (_data: number) => {
|
|
427
|
+
/* read only */
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
bytes: 4,
|
|
432
|
+
name: 'driver_feature_select',
|
|
433
|
+
read: () => this.driver_feature_select,
|
|
434
|
+
write: (data: number) => {
|
|
435
|
+
this.driver_feature_select = data
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
bytes: 4,
|
|
440
|
+
name: 'driver_feature',
|
|
441
|
+
read: () =>
|
|
442
|
+
this.driver_feature[this.driver_feature_select] || 0,
|
|
443
|
+
write: (data: number) => {
|
|
444
|
+
const supported_feature =
|
|
445
|
+
this.device_feature[this.driver_feature_select]
|
|
446
|
+
|
|
447
|
+
if (
|
|
448
|
+
this.driver_feature_select <
|
|
449
|
+
this.driver_feature.length
|
|
450
|
+
) {
|
|
451
|
+
// Note: only set subset of device_features is set.
|
|
452
|
+
// Required in our implementation for is_feature_negotiated().
|
|
453
|
+
this.driver_feature[this.driver_feature_select] =
|
|
454
|
+
data & supported_feature
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Check that driver features is an inclusive subset of device features.
|
|
458
|
+
const invalid_bits = data & ~supported_feature
|
|
459
|
+
this.features_ok = this.features_ok && !invalid_bits
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
bytes: 2,
|
|
464
|
+
name: 'msix_config',
|
|
465
|
+
read: () => {
|
|
466
|
+
dbg_log('No msi-x capability supported.', LOG_VIRTIO)
|
|
467
|
+
return 0xffff
|
|
468
|
+
},
|
|
469
|
+
write: (_data: number) => {
|
|
470
|
+
dbg_log('No msi-x capability supported.', LOG_VIRTIO)
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
bytes: 2,
|
|
475
|
+
name: 'num_queues',
|
|
476
|
+
read: () => this.queues.length,
|
|
477
|
+
write: (_data: number) => {
|
|
478
|
+
/* read only */
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
bytes: 1,
|
|
483
|
+
name: 'device_status',
|
|
484
|
+
read: () => this.device_status,
|
|
485
|
+
write: (data: number) => {
|
|
486
|
+
if (data === 0) {
|
|
487
|
+
dbg_log(
|
|
488
|
+
'Reset device<' + this.name + '>',
|
|
489
|
+
LOG_VIRTIO,
|
|
490
|
+
)
|
|
491
|
+
this.reset()
|
|
492
|
+
} else if (data & VIRTIO_STATUS_FAILED) {
|
|
493
|
+
dbg_log(
|
|
494
|
+
'Warning: Device<' +
|
|
495
|
+
this.name +
|
|
496
|
+
'> status failed',
|
|
497
|
+
LOG_VIRTIO,
|
|
498
|
+
)
|
|
499
|
+
} else {
|
|
500
|
+
dbg_log(
|
|
501
|
+
'Device<' +
|
|
502
|
+
this.name +
|
|
503
|
+
'> status: ' +
|
|
504
|
+
(data & VIRTIO_STATUS_ACKNOWLEDGE
|
|
505
|
+
? 'ACKNOWLEDGE '
|
|
506
|
+
: '') +
|
|
507
|
+
(data & VIRTIO_STATUS_DRIVER
|
|
508
|
+
? 'DRIVER '
|
|
509
|
+
: '') +
|
|
510
|
+
(data & VIRTIO_STATUS_DRIVER_OK
|
|
511
|
+
? 'DRIVER_OK'
|
|
512
|
+
: '') +
|
|
513
|
+
(data & VIRTIO_STATUS_FEATURES_OK
|
|
514
|
+
? 'FEATURES_OK '
|
|
515
|
+
: '') +
|
|
516
|
+
(data & VIRTIO_STATUS_DEVICE_NEEDS_RESET
|
|
517
|
+
? 'DEVICE_NEEDS_RESET'
|
|
518
|
+
: ''),
|
|
519
|
+
LOG_VIRTIO,
|
|
520
|
+
)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (
|
|
524
|
+
data &
|
|
525
|
+
~this.device_status &
|
|
526
|
+
VIRTIO_STATUS_DRIVER_OK &&
|
|
527
|
+
this.device_status &
|
|
528
|
+
VIRTIO_STATUS_DEVICE_NEEDS_RESET
|
|
529
|
+
) {
|
|
530
|
+
// We couldn't notify NEEDS_RESET earlier because DRIVER_OK was not set.
|
|
531
|
+
// Now it has been set, notify now.
|
|
532
|
+
this.notify_config_changes()
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Don't set FEATURES_OK if our device doesn't support requested features.
|
|
536
|
+
if (!this.features_ok) {
|
|
537
|
+
if (DEBUG && data & VIRTIO_STATUS_FEATURES_OK) {
|
|
538
|
+
dbg_log('Removing FEATURES_OK', LOG_VIRTIO)
|
|
539
|
+
}
|
|
540
|
+
data &= ~VIRTIO_STATUS_FEATURES_OK
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
this.device_status = data
|
|
544
|
+
|
|
545
|
+
if (
|
|
546
|
+
data &
|
|
547
|
+
~this.device_status &
|
|
548
|
+
VIRTIO_STATUS_DRIVER_OK
|
|
549
|
+
) {
|
|
550
|
+
options.on_driver_ok()
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
bytes: 1,
|
|
556
|
+
name: 'config_generation',
|
|
557
|
+
read: () => this.config_generation,
|
|
558
|
+
write: (_data: number) => {
|
|
559
|
+
/* read only */
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
bytes: 2,
|
|
564
|
+
name: 'queue_select',
|
|
565
|
+
read: () => this.queue_select,
|
|
566
|
+
write: (data: number) => {
|
|
567
|
+
this.queue_select = data
|
|
568
|
+
|
|
569
|
+
if (this.queue_select < this.queues.length) {
|
|
570
|
+
this.queue_selected = this.queues[this.queue_select]
|
|
571
|
+
} else {
|
|
572
|
+
// Allow queue_select >= num_queues.
|
|
573
|
+
this.queue_selected = null
|
|
574
|
+
// Drivers can then detect that the queue is not available
|
|
575
|
+
// using the below fields.
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
bytes: 2,
|
|
581
|
+
name: 'queue_size',
|
|
582
|
+
read: () =>
|
|
583
|
+
this.queue_selected ? this.queue_selected.size : 0,
|
|
584
|
+
write: (data: number) => {
|
|
585
|
+
if (!this.queue_selected) {
|
|
586
|
+
return
|
|
587
|
+
}
|
|
588
|
+
if (data & (data - 1)) {
|
|
589
|
+
dbg_log(
|
|
590
|
+
'Warning: dev<' +
|
|
591
|
+
this.name +
|
|
592
|
+
'> ' +
|
|
593
|
+
'Given queue size was not a power of 2. ' +
|
|
594
|
+
'Rounding up to next power of 2.',
|
|
595
|
+
LOG_VIRTIO,
|
|
596
|
+
)
|
|
597
|
+
data = 1 << (int_log2(data - 1) + 1)
|
|
598
|
+
}
|
|
599
|
+
if (data > this.queue_selected.size_supported) {
|
|
600
|
+
dbg_log(
|
|
601
|
+
'Warning: dev<' +
|
|
602
|
+
this.name +
|
|
603
|
+
'> ' +
|
|
604
|
+
'Trying to set queue size greater than supported. ' +
|
|
605
|
+
'Clamping to supported size.',
|
|
606
|
+
LOG_VIRTIO,
|
|
607
|
+
)
|
|
608
|
+
data = this.queue_selected.size_supported
|
|
609
|
+
}
|
|
610
|
+
this.queue_selected.set_size(data)
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
bytes: 2,
|
|
615
|
+
name: 'queue_msix_vector',
|
|
616
|
+
read: () => {
|
|
617
|
+
dbg_log('No msi-x capability supported.', LOG_VIRTIO)
|
|
618
|
+
return 0xffff
|
|
619
|
+
},
|
|
620
|
+
write: (_data: number) => {
|
|
621
|
+
dbg_log('No msi-x capability supported.', LOG_VIRTIO)
|
|
622
|
+
},
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
bytes: 2,
|
|
626
|
+
name: 'queue_enable',
|
|
627
|
+
read: () =>
|
|
628
|
+
this.queue_selected
|
|
629
|
+
? this.queue_selected.enabled
|
|
630
|
+
? 1
|
|
631
|
+
: 0
|
|
632
|
+
: 0,
|
|
633
|
+
write: (data: number) => {
|
|
634
|
+
if (!this.queue_selected) {
|
|
635
|
+
return
|
|
636
|
+
}
|
|
637
|
+
if (data === 1) {
|
|
638
|
+
if (this.queue_selected.is_configured()) {
|
|
639
|
+
this.queue_selected.enable()
|
|
640
|
+
} else {
|
|
641
|
+
dbg_log(
|
|
642
|
+
'Driver bug: tried enabling unconfigured queue',
|
|
643
|
+
LOG_VIRTIO,
|
|
644
|
+
)
|
|
645
|
+
}
|
|
646
|
+
} else if (data === 0) {
|
|
647
|
+
dbg_log(
|
|
648
|
+
'Driver bug: tried writing 0 to queue_enable',
|
|
649
|
+
LOG_VIRTIO,
|
|
650
|
+
)
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
bytes: 2,
|
|
656
|
+
name: 'queue_notify_off',
|
|
657
|
+
read: () =>
|
|
658
|
+
this.queue_selected
|
|
659
|
+
? this.queue_selected.notify_offset
|
|
660
|
+
: 0,
|
|
661
|
+
write: (_data: number) => {
|
|
662
|
+
/* read only */
|
|
663
|
+
},
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
bytes: 4,
|
|
667
|
+
name: 'queue_desc (low dword)',
|
|
668
|
+
read: () =>
|
|
669
|
+
this.queue_selected ? this.queue_selected.desc_addr : 0,
|
|
670
|
+
write: (data: number) => {
|
|
671
|
+
if (this.queue_selected)
|
|
672
|
+
this.queue_selected.desc_addr = data
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
bytes: 4,
|
|
677
|
+
name: 'queue_desc (high dword)',
|
|
678
|
+
read: () => 0,
|
|
679
|
+
write: (data: number) => {
|
|
680
|
+
if (data !== 0)
|
|
681
|
+
dbg_log(
|
|
682
|
+
'Warning: High dword of 64 bit queue_desc ignored:' +
|
|
683
|
+
data,
|
|
684
|
+
LOG_VIRTIO,
|
|
685
|
+
)
|
|
686
|
+
},
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
bytes: 4,
|
|
690
|
+
name: 'queue_avail (low dword)',
|
|
691
|
+
read: () =>
|
|
692
|
+
this.queue_selected
|
|
693
|
+
? this.queue_selected.avail_addr
|
|
694
|
+
: 0,
|
|
695
|
+
write: (data: number) => {
|
|
696
|
+
if (this.queue_selected)
|
|
697
|
+
this.queue_selected.avail_addr = data
|
|
698
|
+
},
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
bytes: 4,
|
|
702
|
+
name: 'queue_avail (high dword)',
|
|
703
|
+
read: () => 0,
|
|
704
|
+
write: (data: number) => {
|
|
705
|
+
if (data !== 0)
|
|
706
|
+
dbg_log(
|
|
707
|
+
'Warning: High dword of 64 bit queue_avail ignored:' +
|
|
708
|
+
data,
|
|
709
|
+
LOG_VIRTIO,
|
|
710
|
+
)
|
|
711
|
+
},
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
bytes: 4,
|
|
715
|
+
name: 'queue_used (low dword)',
|
|
716
|
+
read: () =>
|
|
717
|
+
this.queue_selected ? this.queue_selected.used_addr : 0,
|
|
718
|
+
write: (data: number) => {
|
|
719
|
+
if (this.queue_selected)
|
|
720
|
+
this.queue_selected.used_addr = data
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
bytes: 4,
|
|
725
|
+
name: 'queue_used (high dword)',
|
|
726
|
+
read: () => 0,
|
|
727
|
+
write: (data: number) => {
|
|
728
|
+
if (data !== 0)
|
|
729
|
+
dbg_log(
|
|
730
|
+
'Warning: High dword of 64 bit queue_used ignored:' +
|
|
731
|
+
data,
|
|
732
|
+
LOG_VIRTIO,
|
|
733
|
+
)
|
|
734
|
+
},
|
|
735
|
+
},
|
|
736
|
+
],
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
create_notification_capability(
|
|
741
|
+
options: VirtIO_NotificationCapabilityOptions,
|
|
742
|
+
): VirtIO_CapabilityInfo {
|
|
743
|
+
const notify_struct: VirtIO_CapabilityStruct = []
|
|
744
|
+
let notify_off_multiplier: number
|
|
745
|
+
|
|
746
|
+
if (options.single_handler) {
|
|
747
|
+
dbg_assert(
|
|
748
|
+
options.handlers.length === 1,
|
|
749
|
+
'VirtIO device<' +
|
|
750
|
+
this.name +
|
|
751
|
+
'> too many notify handlers specified: expected single handler',
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
// Forces all queues to use the same address for notifying.
|
|
755
|
+
notify_off_multiplier = 0
|
|
756
|
+
} else {
|
|
757
|
+
notify_off_multiplier = 2
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
for (const [i, handler] of options.handlers.entries()) {
|
|
761
|
+
notify_struct.push({
|
|
762
|
+
bytes: 2,
|
|
763
|
+
name: 'notify' + i,
|
|
764
|
+
read: () => 0xffff,
|
|
765
|
+
write: handler || ((_data: number) => {}),
|
|
766
|
+
})
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
return {
|
|
770
|
+
type: VIRTIO_PCI_CAP_NOTIFY_CFG,
|
|
771
|
+
bar: 1,
|
|
772
|
+
port: options.initial_port,
|
|
773
|
+
use_mmio: false,
|
|
774
|
+
offset: 0,
|
|
775
|
+
extra: new Uint8Array([
|
|
776
|
+
notify_off_multiplier & 0xff,
|
|
777
|
+
(notify_off_multiplier >> 8) & 0xff,
|
|
778
|
+
(notify_off_multiplier >> 16) & 0xff,
|
|
779
|
+
notify_off_multiplier >> 24,
|
|
780
|
+
]),
|
|
781
|
+
struct: notify_struct,
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
create_isr_capability(
|
|
786
|
+
options: VirtIO_ISRCapabilityOptions,
|
|
787
|
+
): VirtIO_CapabilityInfo {
|
|
788
|
+
return {
|
|
789
|
+
type: VIRTIO_PCI_CAP_ISR_CFG,
|
|
790
|
+
bar: 2,
|
|
791
|
+
port: options.initial_port,
|
|
792
|
+
use_mmio: false,
|
|
793
|
+
offset: 0,
|
|
794
|
+
extra: new Uint8Array(0),
|
|
795
|
+
struct: [
|
|
796
|
+
{
|
|
797
|
+
bytes: 1,
|
|
798
|
+
name: 'isr_status',
|
|
799
|
+
read: () => {
|
|
800
|
+
const isr_status = this.isr_status
|
|
801
|
+
this.lower_irq()
|
|
802
|
+
return isr_status
|
|
803
|
+
},
|
|
804
|
+
write: (_data: number) => {
|
|
805
|
+
/* read only */
|
|
806
|
+
},
|
|
807
|
+
},
|
|
808
|
+
],
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
create_device_specific_capability(
|
|
813
|
+
options: VirtIO_DeviceSpecificCapabilityOptions,
|
|
814
|
+
): VirtIO_CapabilityInfo {
|
|
815
|
+
dbg_assert(
|
|
816
|
+
!(options.initial_port & 0x3),
|
|
817
|
+
'VirtIO device<' +
|
|
818
|
+
this.name +
|
|
819
|
+
'> device specific cap offset must be 4-byte aligned',
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
return {
|
|
823
|
+
type: VIRTIO_PCI_CAP_DEVICE_CFG,
|
|
824
|
+
bar: 3,
|
|
825
|
+
port: options.initial_port,
|
|
826
|
+
use_mmio: false,
|
|
827
|
+
offset: 0,
|
|
828
|
+
extra: new Uint8Array(0),
|
|
829
|
+
struct: options.struct,
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Writes capabilities into pci_space and hook up IO/MMIO handlers.
|
|
834
|
+
// Call only within constructor.
|
|
835
|
+
init_capabilities(capabilities: VirtIO_CapabilityInfo[]): void {
|
|
836
|
+
// Next available offset for capabilities linked list.
|
|
837
|
+
let cap_next = (this.pci_space[0x34] = 0x40)
|
|
838
|
+
|
|
839
|
+
// Current offset.
|
|
840
|
+
let cap_ptr = cap_next
|
|
841
|
+
|
|
842
|
+
for (const cap of capabilities) {
|
|
843
|
+
const cap_len = VIRTIO_PCI_CAP_LENGTH + cap.extra.length
|
|
844
|
+
|
|
845
|
+
cap_ptr = cap_next
|
|
846
|
+
cap_next = cap_ptr + cap_len
|
|
847
|
+
|
|
848
|
+
dbg_assert(
|
|
849
|
+
cap_next <= 256,
|
|
850
|
+
'VirtIO device<' +
|
|
851
|
+
this.name +
|
|
852
|
+
"> can't fit all capabilities into 256byte configspace",
|
|
853
|
+
)
|
|
854
|
+
|
|
855
|
+
dbg_assert(
|
|
856
|
+
0 <= cap.bar && cap.bar < 6,
|
|
857
|
+
'VirtIO device<' +
|
|
858
|
+
this.name +
|
|
859
|
+
'> capability invalid bar number',
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
let bar_size = cap.struct.reduce(
|
|
863
|
+
(bytes, field) => bytes + field.bytes,
|
|
864
|
+
0,
|
|
865
|
+
)
|
|
866
|
+
bar_size += cap.offset
|
|
867
|
+
|
|
868
|
+
// Round up to next power of 2,
|
|
869
|
+
// Minimum 16 bytes for its size to be detectable in general (esp. mmio).
|
|
870
|
+
bar_size = bar_size < 16 ? 16 : 1 << (int_log2(bar_size - 1) + 1)
|
|
871
|
+
|
|
872
|
+
dbg_assert(
|
|
873
|
+
(cap.port & (bar_size - 1)) === 0,
|
|
874
|
+
'VirtIO device<' +
|
|
875
|
+
this.name +
|
|
876
|
+
'> capability port should be aligned to pci bar size',
|
|
877
|
+
)
|
|
878
|
+
|
|
879
|
+
this.pci_bars[cap.bar] = {
|
|
880
|
+
size: bar_size,
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
this.pci_space[cap_ptr] = VIRTIO_PCI_CAP_VENDOR
|
|
884
|
+
this.pci_space[cap_ptr + 1] = cap_next
|
|
885
|
+
this.pci_space[cap_ptr + 2] = cap_len
|
|
886
|
+
this.pci_space[cap_ptr + 3] = cap.type
|
|
887
|
+
this.pci_space[cap_ptr + 4] = cap.bar
|
|
888
|
+
|
|
889
|
+
this.pci_space[cap_ptr + 5] = 0 // Padding.
|
|
890
|
+
this.pci_space[cap_ptr + 6] = 0 // Padding.
|
|
891
|
+
this.pci_space[cap_ptr + 7] = 0 // Padding.
|
|
892
|
+
|
|
893
|
+
this.pci_space[cap_ptr + 8] = cap.offset & 0xff
|
|
894
|
+
this.pci_space[cap_ptr + 9] = (cap.offset >>> 8) & 0xff
|
|
895
|
+
this.pci_space[cap_ptr + 10] = (cap.offset >>> 16) & 0xff
|
|
896
|
+
this.pci_space[cap_ptr + 11] = cap.offset >>> 24
|
|
897
|
+
|
|
898
|
+
this.pci_space[cap_ptr + 12] = bar_size & 0xff
|
|
899
|
+
this.pci_space[cap_ptr + 13] = (bar_size >>> 8) & 0xff
|
|
900
|
+
this.pci_space[cap_ptr + 14] = (bar_size >>> 16) & 0xff
|
|
901
|
+
this.pci_space[cap_ptr + 15] = bar_size >>> 24
|
|
902
|
+
|
|
903
|
+
for (const [i, extra_byte] of cap.extra.entries()) {
|
|
904
|
+
this.pci_space[cap_ptr + 16 + i] = extra_byte
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
const bar_offset = 0x10 + 4 * cap.bar
|
|
908
|
+
this.pci_space[bar_offset] = (cap.port & 0xfe) | +!cap.use_mmio
|
|
909
|
+
this.pci_space[bar_offset + 1] = (cap.port >>> 8) & 0xff
|
|
910
|
+
this.pci_space[bar_offset + 2] = (cap.port >>> 16) & 0xff
|
|
911
|
+
this.pci_space[bar_offset + 3] = (cap.port >>> 24) & 0xff
|
|
912
|
+
|
|
913
|
+
let port = cap.port + cap.offset
|
|
914
|
+
|
|
915
|
+
for (const field of cap.struct) {
|
|
916
|
+
let read: (...args: any[]) => number = field.read
|
|
917
|
+
let write: (...args: any[]) => void = field.write
|
|
918
|
+
|
|
919
|
+
if (DEBUG) {
|
|
920
|
+
read = () => {
|
|
921
|
+
const val = field.read()
|
|
922
|
+
|
|
923
|
+
dbg_log(
|
|
924
|
+
'Device<' +
|
|
925
|
+
this.name +
|
|
926
|
+
'> ' +
|
|
927
|
+
'cap[' +
|
|
928
|
+
cap.type +
|
|
929
|
+
'] ' +
|
|
930
|
+
'read[' +
|
|
931
|
+
field.name +
|
|
932
|
+
'] ' +
|
|
933
|
+
'=> ' +
|
|
934
|
+
h(val, field.bytes * 8),
|
|
935
|
+
LOG_VIRTIO,
|
|
936
|
+
)
|
|
937
|
+
|
|
938
|
+
return val
|
|
939
|
+
}
|
|
940
|
+
write = (data: number) => {
|
|
941
|
+
dbg_log(
|
|
942
|
+
'Device<' +
|
|
943
|
+
this.name +
|
|
944
|
+
'> ' +
|
|
945
|
+
'cap[' +
|
|
946
|
+
cap.type +
|
|
947
|
+
'] ' +
|
|
948
|
+
'write[' +
|
|
949
|
+
field.name +
|
|
950
|
+
'] ' +
|
|
951
|
+
'<= ' +
|
|
952
|
+
h(data, field.bytes * 8),
|
|
953
|
+
LOG_VIRTIO,
|
|
954
|
+
)
|
|
955
|
+
|
|
956
|
+
field.write(data)
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
if (cap.use_mmio) {
|
|
961
|
+
dbg_assert(
|
|
962
|
+
false,
|
|
963
|
+
'VirtIO device <' +
|
|
964
|
+
this.name +
|
|
965
|
+
'> mmio capability not implemented.',
|
|
966
|
+
)
|
|
967
|
+
} else {
|
|
968
|
+
// DSL (2.4 kernel) does these reads
|
|
969
|
+
const shim_read8_on_16 = function (addr: number): number {
|
|
970
|
+
dbg_log(
|
|
971
|
+
'Warning: 8-bit read from 16-bit virtio port',
|
|
972
|
+
LOG_VIRTIO,
|
|
973
|
+
)
|
|
974
|
+
return (read(addr & ~1) >> ((addr & 1) << 3)) & 0xff
|
|
975
|
+
}
|
|
976
|
+
const shim_read8_on_32 = function (addr: number): number {
|
|
977
|
+
dbg_log(
|
|
978
|
+
'Warning: 8-bit read from 32-bit virtio port',
|
|
979
|
+
LOG_VIRTIO,
|
|
980
|
+
)
|
|
981
|
+
return (read(addr & ~3) >> ((addr & 3) << 3)) & 0xff
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// archhurd does these reads
|
|
985
|
+
const shim_read32_on_16 = function (addr: number): number {
|
|
986
|
+
dbg_log(
|
|
987
|
+
'Warning: 32-bit read from 16-bit virtio port',
|
|
988
|
+
LOG_VIRTIO,
|
|
989
|
+
)
|
|
990
|
+
return read(addr)
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
switch (field.bytes) {
|
|
994
|
+
case 4:
|
|
995
|
+
this.cpu.io.register_read(
|
|
996
|
+
port,
|
|
997
|
+
this,
|
|
998
|
+
shim_read8_on_32,
|
|
999
|
+
undefined,
|
|
1000
|
+
read,
|
|
1001
|
+
)
|
|
1002
|
+
this.cpu.io.register_read(
|
|
1003
|
+
port + 1,
|
|
1004
|
+
this,
|
|
1005
|
+
shim_read8_on_32,
|
|
1006
|
+
)
|
|
1007
|
+
this.cpu.io.register_read(
|
|
1008
|
+
port + 2,
|
|
1009
|
+
this,
|
|
1010
|
+
shim_read8_on_32,
|
|
1011
|
+
)
|
|
1012
|
+
this.cpu.io.register_read(
|
|
1013
|
+
port + 3,
|
|
1014
|
+
this,
|
|
1015
|
+
shim_read8_on_32,
|
|
1016
|
+
)
|
|
1017
|
+
this.cpu.io.register_write(
|
|
1018
|
+
port,
|
|
1019
|
+
this,
|
|
1020
|
+
undefined,
|
|
1021
|
+
undefined,
|
|
1022
|
+
write,
|
|
1023
|
+
)
|
|
1024
|
+
break
|
|
1025
|
+
case 2:
|
|
1026
|
+
this.cpu.io.register_read(
|
|
1027
|
+
port,
|
|
1028
|
+
this,
|
|
1029
|
+
shim_read8_on_16,
|
|
1030
|
+
read,
|
|
1031
|
+
shim_read32_on_16,
|
|
1032
|
+
)
|
|
1033
|
+
this.cpu.io.register_read(
|
|
1034
|
+
port + 1,
|
|
1035
|
+
this,
|
|
1036
|
+
shim_read8_on_16,
|
|
1037
|
+
)
|
|
1038
|
+
this.cpu.io.register_write(
|
|
1039
|
+
port,
|
|
1040
|
+
this,
|
|
1041
|
+
undefined,
|
|
1042
|
+
write,
|
|
1043
|
+
)
|
|
1044
|
+
break
|
|
1045
|
+
case 1:
|
|
1046
|
+
this.cpu.io.register_read(port, this, read)
|
|
1047
|
+
this.cpu.io.register_write(port, this, write)
|
|
1048
|
+
break
|
|
1049
|
+
default:
|
|
1050
|
+
dbg_assert(
|
|
1051
|
+
false,
|
|
1052
|
+
'VirtIO device <' +
|
|
1053
|
+
this.name +
|
|
1054
|
+
'> invalid capability field width of ' +
|
|
1055
|
+
field.bytes +
|
|
1056
|
+
' bytes',
|
|
1057
|
+
)
|
|
1058
|
+
break
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
port += field.bytes
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// Terminate linked list with the pci config access capability.
|
|
1067
|
+
|
|
1068
|
+
const cap_len = VIRTIO_PCI_CAP_LENGTH + 4
|
|
1069
|
+
dbg_assert(
|
|
1070
|
+
cap_next + cap_len <= 256,
|
|
1071
|
+
'VirtIO device<' +
|
|
1072
|
+
this.name +
|
|
1073
|
+
"> can't fit all capabilities into 256byte configspace",
|
|
1074
|
+
)
|
|
1075
|
+
this.pci_space[cap_next] = VIRTIO_PCI_CAP_VENDOR
|
|
1076
|
+
this.pci_space[cap_next + 1] = 0 // cap next (null terminator)
|
|
1077
|
+
this.pci_space[cap_next + 2] = cap_len
|
|
1078
|
+
this.pci_space[cap_next + 3] = VIRTIO_PCI_CAP_PCI_CFG // cap type
|
|
1079
|
+
this.pci_space[cap_next + 4] = 0 // bar (written by device)
|
|
1080
|
+
this.pci_space[cap_next + 5] = 0 // Padding.
|
|
1081
|
+
this.pci_space[cap_next + 6] = 0 // Padding.
|
|
1082
|
+
this.pci_space[cap_next + 7] = 0 // Padding.
|
|
1083
|
+
|
|
1084
|
+
// Remaining fields are configured by driver when needed.
|
|
1085
|
+
|
|
1086
|
+
// offset
|
|
1087
|
+
this.pci_space[cap_next + 8] = 0
|
|
1088
|
+
this.pci_space[cap_next + 9] = 0
|
|
1089
|
+
this.pci_space[cap_next + 10] = 0
|
|
1090
|
+
this.pci_space[cap_next + 11] = 0
|
|
1091
|
+
|
|
1092
|
+
// bar size
|
|
1093
|
+
this.pci_space[cap_next + 12] = 0
|
|
1094
|
+
this.pci_space[cap_next + 13] = 0
|
|
1095
|
+
this.pci_space[cap_next + 14] = 0
|
|
1096
|
+
this.pci_space[cap_next + 15] = 0
|
|
1097
|
+
|
|
1098
|
+
// cfg_data
|
|
1099
|
+
this.pci_space[cap_next + 16] = 0
|
|
1100
|
+
this.pci_space[cap_next + 17] = 0
|
|
1101
|
+
this.pci_space[cap_next + 18] = 0
|
|
1102
|
+
this.pci_space[cap_next + 19] = 0
|
|
1103
|
+
|
|
1104
|
+
//
|
|
1105
|
+
// TODO
|
|
1106
|
+
// The pci config access capability is required by spec, but so far, devices
|
|
1107
|
+
// seem to work well without it.
|
|
1108
|
+
// This capability provides a cfg_data field (at cap_next + 16 for 4 bytes)
|
|
1109
|
+
// that acts like a window to the previous bars. The driver writes the bar number,
|
|
1110
|
+
// offset, and length values in this capability, and the cfg_data field should
|
|
1111
|
+
// mirror the data referred by the bar, offset and length. Here, length can be
|
|
1112
|
+
// 1, 2, or 4.
|
|
1113
|
+
//
|
|
1114
|
+
// This requires some sort of pci devicespace read and write handlers.
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
get_state(): any[] {
|
|
1118
|
+
let state: any[] = []
|
|
1119
|
+
|
|
1120
|
+
state[0] = this.device_feature_select
|
|
1121
|
+
state[1] = this.driver_feature_select
|
|
1122
|
+
state[2] = this.device_feature
|
|
1123
|
+
state[3] = this.driver_feature
|
|
1124
|
+
state[4] = this.features_ok
|
|
1125
|
+
state[5] = this.device_status
|
|
1126
|
+
state[6] = this.config_has_changed
|
|
1127
|
+
state[7] = this.config_generation
|
|
1128
|
+
state[8] = this.isr_status
|
|
1129
|
+
state[9] = this.queue_select
|
|
1130
|
+
state = state.concat(this.queues)
|
|
1131
|
+
|
|
1132
|
+
return state
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
set_state(state: any[]): void {
|
|
1136
|
+
this.device_feature_select = state[0]
|
|
1137
|
+
this.driver_feature_select = state[1]
|
|
1138
|
+
this.device_feature = state[2]
|
|
1139
|
+
this.driver_feature = state[3]
|
|
1140
|
+
this.features_ok = state[4]
|
|
1141
|
+
this.device_status = state[5]
|
|
1142
|
+
this.config_has_changed = state[6]
|
|
1143
|
+
this.config_generation = state[7]
|
|
1144
|
+
this.isr_status = state[8]
|
|
1145
|
+
this.queue_select = state[9]
|
|
1146
|
+
let i = 0
|
|
1147
|
+
for (const queue of state.slice(10)) {
|
|
1148
|
+
this.queues[i].set_state(queue)
|
|
1149
|
+
i++
|
|
1150
|
+
}
|
|
1151
|
+
this.queue_selected = this.queues[this.queue_select] || null
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
reset(): void {
|
|
1155
|
+
this.device_feature_select = 0
|
|
1156
|
+
this.driver_feature_select = 0
|
|
1157
|
+
this.driver_feature.set(this.device_feature)
|
|
1158
|
+
|
|
1159
|
+
this.features_ok = true
|
|
1160
|
+
this.device_status = 0
|
|
1161
|
+
|
|
1162
|
+
this.queue_select = 0
|
|
1163
|
+
this.queue_selected = this.queues[0]
|
|
1164
|
+
|
|
1165
|
+
for (const queue of this.queues) {
|
|
1166
|
+
queue.reset()
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
this.config_has_changed = false
|
|
1170
|
+
this.config_generation = 0
|
|
1171
|
+
|
|
1172
|
+
this.lower_irq()
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// Call this when device-specific configuration state changes.
|
|
1176
|
+
// Also called when status DEVICE_NEEDS_RESET is set.
|
|
1177
|
+
notify_config_changes(): void {
|
|
1178
|
+
this.config_has_changed = true
|
|
1179
|
+
|
|
1180
|
+
if (this.device_status & VIRTIO_STATUS_DRIVER_OK) {
|
|
1181
|
+
this.raise_irq(VIRTIO_ISR_DEVICE_CFG)
|
|
1182
|
+
} else {
|
|
1183
|
+
dbg_assert(
|
|
1184
|
+
false,
|
|
1185
|
+
'VirtIO device<' +
|
|
1186
|
+
this.name +
|
|
1187
|
+
'> attempted to notify driver before DRIVER_OK',
|
|
1188
|
+
)
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// To be called after reading any field whose write can trigger notify_config_changes().
|
|
1193
|
+
update_config_generation(): void {
|
|
1194
|
+
if (this.config_has_changed) {
|
|
1195
|
+
this.config_generation++
|
|
1196
|
+
this.config_generation &= 0xff
|
|
1197
|
+
this.config_has_changed = false
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
is_feature_negotiated(feature: number): boolean {
|
|
1202
|
+
// Feature bits are grouped in 32 bits.
|
|
1203
|
+
// Note: earlier we chose not to set invalid features into driver_feature.
|
|
1204
|
+
return (
|
|
1205
|
+
(this.driver_feature[feature >>> 5] & (1 << (feature & 0x1f))) > 0
|
|
1206
|
+
)
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// Call this if an irrecoverable error has occurred.
|
|
1210
|
+
// Notifies driver if DRIVER_OK, or when DRIVER_OK gets set.
|
|
1211
|
+
needs_reset(): void {
|
|
1212
|
+
dbg_log(
|
|
1213
|
+
'Device<' + this.name + '> experienced error - requires reset',
|
|
1214
|
+
LOG_VIRTIO,
|
|
1215
|
+
)
|
|
1216
|
+
this.device_status |= VIRTIO_STATUS_DEVICE_NEEDS_RESET
|
|
1217
|
+
|
|
1218
|
+
if (this.device_status & VIRTIO_STATUS_DRIVER_OK) {
|
|
1219
|
+
this.notify_config_changes()
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
raise_irq(type: number): void {
|
|
1224
|
+
dbg_log('Raise irq ' + h(type), LOG_VIRTIO)
|
|
1225
|
+
this.isr_status |= type
|
|
1226
|
+
this.pci.raise_irq(this.pci_id)
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
lower_irq(): void {
|
|
1230
|
+
dbg_log('Lower irq ', LOG_VIRTIO)
|
|
1231
|
+
this.isr_status = 0
|
|
1232
|
+
this.pci.lower_irq(this.pci_id)
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
export class VirtQueue {
|
|
1237
|
+
cpu: VirtioCPU
|
|
1238
|
+
virtio: VirtIO
|
|
1239
|
+
size: number
|
|
1240
|
+
size_supported: number
|
|
1241
|
+
mask: number
|
|
1242
|
+
enabled: boolean
|
|
1243
|
+
notify_offset: number
|
|
1244
|
+
desc_addr: number
|
|
1245
|
+
avail_addr: number
|
|
1246
|
+
avail_last_idx: number
|
|
1247
|
+
used_addr: number
|
|
1248
|
+
num_staged_replies: number
|
|
1249
|
+
fix_wrapping: boolean
|
|
1250
|
+
|
|
1251
|
+
constructor(cpu: VirtioCPU, virtio: VirtIO, options: VirtQueue_Options) {
|
|
1252
|
+
this.cpu = cpu
|
|
1253
|
+
this.virtio = virtio
|
|
1254
|
+
|
|
1255
|
+
// Number of entries.
|
|
1256
|
+
this.size = options.size_supported
|
|
1257
|
+
this.size_supported = options.size_supported
|
|
1258
|
+
this.mask = this.size - 1
|
|
1259
|
+
this.enabled = false
|
|
1260
|
+
this.notify_offset = options.notify_offset
|
|
1261
|
+
|
|
1262
|
+
this.desc_addr = 0
|
|
1263
|
+
|
|
1264
|
+
this.avail_addr = 0
|
|
1265
|
+
this.avail_last_idx = 0
|
|
1266
|
+
|
|
1267
|
+
this.used_addr = 0
|
|
1268
|
+
this.num_staged_replies = 0
|
|
1269
|
+
|
|
1270
|
+
this.fix_wrapping = false
|
|
1271
|
+
|
|
1272
|
+
this.reset()
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
get_state(): any[] {
|
|
1276
|
+
const state: any[] = []
|
|
1277
|
+
|
|
1278
|
+
state[0] = this.size
|
|
1279
|
+
state[1] = this.size_supported
|
|
1280
|
+
state[2] = this.enabled
|
|
1281
|
+
state[3] = this.notify_offset
|
|
1282
|
+
state[4] = this.desc_addr
|
|
1283
|
+
state[5] = this.avail_addr
|
|
1284
|
+
state[6] = this.avail_last_idx
|
|
1285
|
+
state[7] = this.used_addr
|
|
1286
|
+
state[8] = this.num_staged_replies
|
|
1287
|
+
state[9] = 1
|
|
1288
|
+
|
|
1289
|
+
return state
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
set_state(state: any[]): void {
|
|
1293
|
+
this.size = state[0]
|
|
1294
|
+
this.size_supported = state[1]
|
|
1295
|
+
this.enabled = state[2]
|
|
1296
|
+
this.notify_offset = state[3]
|
|
1297
|
+
this.desc_addr = state[4]
|
|
1298
|
+
this.avail_addr = state[5]
|
|
1299
|
+
this.avail_last_idx = state[6]
|
|
1300
|
+
this.used_addr = state[7]
|
|
1301
|
+
this.num_staged_replies = state[8]
|
|
1302
|
+
|
|
1303
|
+
this.mask = this.size - 1
|
|
1304
|
+
this.fix_wrapping = state[9] !== 1
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
reset(): void {
|
|
1308
|
+
this.enabled = false
|
|
1309
|
+
this.desc_addr = 0
|
|
1310
|
+
this.avail_addr = 0
|
|
1311
|
+
this.avail_last_idx = 0
|
|
1312
|
+
this.used_addr = 0
|
|
1313
|
+
this.num_staged_replies = 0
|
|
1314
|
+
this.set_size(this.size_supported)
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
is_configured(): boolean {
|
|
1318
|
+
return !!(this.desc_addr && this.avail_addr && this.used_addr)
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
enable(): void {
|
|
1322
|
+
dbg_assert(
|
|
1323
|
+
this.is_configured(),
|
|
1324
|
+
'VirtQueue must be configured before enabled',
|
|
1325
|
+
)
|
|
1326
|
+
this.enabled = true
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
set_size(size: number): void {
|
|
1330
|
+
dbg_assert(
|
|
1331
|
+
(size & (size - 1)) === 0,
|
|
1332
|
+
'VirtQueue size must be power of 2 or zero',
|
|
1333
|
+
)
|
|
1334
|
+
dbg_assert(
|
|
1335
|
+
size <= this.size_supported,
|
|
1336
|
+
'VirtQueue size must be within supported size',
|
|
1337
|
+
)
|
|
1338
|
+
this.size = size
|
|
1339
|
+
this.mask = size - 1
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
count_requests(): number {
|
|
1343
|
+
dbg_assert(
|
|
1344
|
+
!!this.avail_addr,
|
|
1345
|
+
'VirtQueue addresses must be configured before use',
|
|
1346
|
+
)
|
|
1347
|
+
if (this.fix_wrapping) {
|
|
1348
|
+
this.fix_wrapping = false
|
|
1349
|
+
this.avail_last_idx =
|
|
1350
|
+
(this.avail_get_idx() & ~this.mask) +
|
|
1351
|
+
(this.avail_last_idx & this.mask)
|
|
1352
|
+
}
|
|
1353
|
+
return (this.avail_get_idx() - this.avail_last_idx) & 0xffff
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
has_request(): boolean {
|
|
1357
|
+
dbg_assert(
|
|
1358
|
+
!!this.avail_addr,
|
|
1359
|
+
'VirtQueue addresses must be configured before use',
|
|
1360
|
+
)
|
|
1361
|
+
return this.count_requests() !== 0
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
pop_request(): VirtQueueBufferChain {
|
|
1365
|
+
dbg_assert(
|
|
1366
|
+
!!this.avail_addr,
|
|
1367
|
+
'VirtQueue addresses must be configured before use',
|
|
1368
|
+
)
|
|
1369
|
+
dbg_assert(
|
|
1370
|
+
this.has_request(),
|
|
1371
|
+
'VirtQueue must not pop nonexistent request',
|
|
1372
|
+
)
|
|
1373
|
+
|
|
1374
|
+
const desc_idx = this.avail_get_entry(this.avail_last_idx)
|
|
1375
|
+
dbg_log(
|
|
1376
|
+
'Pop request: avail_last_idx=' +
|
|
1377
|
+
this.avail_last_idx +
|
|
1378
|
+
' desc_idx=' +
|
|
1379
|
+
desc_idx,
|
|
1380
|
+
LOG_VIRTIO,
|
|
1381
|
+
)
|
|
1382
|
+
|
|
1383
|
+
const bufchain = new VirtQueueBufferChain(this, desc_idx)
|
|
1384
|
+
|
|
1385
|
+
this.avail_last_idx = (this.avail_last_idx + 1) & 0xffff
|
|
1386
|
+
|
|
1387
|
+
return bufchain
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// Stage a buffer chain into the used ring.
|
|
1391
|
+
// Can call push_reply many times before flushing to batch replies together.
|
|
1392
|
+
// Note: this reply is not visible to driver until flush_replies is called.
|
|
1393
|
+
push_reply(bufchain: VirtQueueBufferChain): void {
|
|
1394
|
+
dbg_assert(
|
|
1395
|
+
!!this.used_addr,
|
|
1396
|
+
'VirtQueue addresses must be configured before use',
|
|
1397
|
+
)
|
|
1398
|
+
dbg_assert(
|
|
1399
|
+
this.num_staged_replies < this.size,
|
|
1400
|
+
'VirtQueue replies must not exceed queue size',
|
|
1401
|
+
)
|
|
1402
|
+
|
|
1403
|
+
const used_idx =
|
|
1404
|
+
(this.used_get_idx() + this.num_staged_replies) & this.mask
|
|
1405
|
+
dbg_log(
|
|
1406
|
+
'Push reply: used_idx=' +
|
|
1407
|
+
used_idx +
|
|
1408
|
+
' desc_idx=' +
|
|
1409
|
+
bufchain.head_idx,
|
|
1410
|
+
LOG_VIRTIO,
|
|
1411
|
+
)
|
|
1412
|
+
|
|
1413
|
+
this.used_set_entry(
|
|
1414
|
+
used_idx,
|
|
1415
|
+
bufchain.head_idx,
|
|
1416
|
+
bufchain.length_written,
|
|
1417
|
+
)
|
|
1418
|
+
this.num_staged_replies++
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// Makes replies visible to driver by updating the used ring idx and
|
|
1422
|
+
// firing appropriate interrupt if needed.
|
|
1423
|
+
flush_replies(): void {
|
|
1424
|
+
dbg_assert(
|
|
1425
|
+
!!this.used_addr,
|
|
1426
|
+
'VirtQueue addresses must be configured before use',
|
|
1427
|
+
)
|
|
1428
|
+
|
|
1429
|
+
if (this.num_staged_replies === 0) {
|
|
1430
|
+
dbg_log('flush_replies: Nothing to flush', LOG_VIRTIO)
|
|
1431
|
+
return
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
dbg_log('Flushing ' + this.num_staged_replies + ' replies', LOG_VIRTIO)
|
|
1435
|
+
const old_idx = this.used_get_idx()
|
|
1436
|
+
const new_idx = (old_idx + this.num_staged_replies) & VIRTQ_IDX_MASK
|
|
1437
|
+
this.used_set_idx(new_idx)
|
|
1438
|
+
|
|
1439
|
+
this.num_staged_replies = 0
|
|
1440
|
+
|
|
1441
|
+
if (this.virtio.is_feature_negotiated(VIRTIO_F_RING_EVENT_IDX)) {
|
|
1442
|
+
const used_event = this.avail_get_used_event()
|
|
1443
|
+
|
|
1444
|
+
// Fire irq when idx values associated with the pushed reply buffers
|
|
1445
|
+
// has reached or gone past used_event.
|
|
1446
|
+
let _has_passed = old_idx <= used_event && used_event < new_idx
|
|
1447
|
+
|
|
1448
|
+
// Has overflowed? Assumes num_staged_replies > 0.
|
|
1449
|
+
if (new_idx <= old_idx) {
|
|
1450
|
+
_has_passed = used_event < new_idx || old_idx <= used_event
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
// Commented out: Workaround for sometimes loading from the filesystem hangs and the emulator stays idle
|
|
1454
|
+
//if(has_passed)
|
|
1455
|
+
{
|
|
1456
|
+
this.virtio.raise_irq(VIRTIO_ISR_QUEUE)
|
|
1457
|
+
}
|
|
1458
|
+
} else {
|
|
1459
|
+
if (~this.avail_get_flags() & VIRTQ_AVAIL_F_NO_INTERRUPT) {
|
|
1460
|
+
this.virtio.raise_irq(VIRTIO_ISR_QUEUE)
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
// If using VIRTIO_F_RING_EVENT_IDX, device must tell driver when
|
|
1466
|
+
// to get notifications or else driver won't notify regularly.
|
|
1467
|
+
// If not using VIRTIO_F_RING_EVENT_IDX, driver will ignore avail_event
|
|
1468
|
+
// and notify every request regardless unless NO_NOTIFY is set (TODO implement when needed).
|
|
1469
|
+
notify_me_after(num_skipped_requests: number): void {
|
|
1470
|
+
dbg_assert(
|
|
1471
|
+
num_skipped_requests >= 0,
|
|
1472
|
+
'Must skip a non-negative number of requests',
|
|
1473
|
+
)
|
|
1474
|
+
|
|
1475
|
+
// The 16 bit idx field wraps around after 2^16.
|
|
1476
|
+
const avail_event =
|
|
1477
|
+
(this.avail_get_idx() + num_skipped_requests) & 0xffff
|
|
1478
|
+
this.used_set_avail_event(avail_event)
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
get_descriptor(table_address: number, i: number): VirtQueueDescriptor {
|
|
1482
|
+
return {
|
|
1483
|
+
addr_low: this.cpu.read32s(
|
|
1484
|
+
table_address + i * VIRTQ_DESC_ENTRYSIZE,
|
|
1485
|
+
),
|
|
1486
|
+
addr_high: this.cpu.read32s(
|
|
1487
|
+
table_address + i * VIRTQ_DESC_ENTRYSIZE + 4,
|
|
1488
|
+
),
|
|
1489
|
+
len: this.cpu.read32s(table_address + i * VIRTQ_DESC_ENTRYSIZE + 8),
|
|
1490
|
+
flags: this.cpu.read16(
|
|
1491
|
+
table_address + i * VIRTQ_DESC_ENTRYSIZE + 12,
|
|
1492
|
+
),
|
|
1493
|
+
next: this.cpu.read16(
|
|
1494
|
+
table_address + i * VIRTQ_DESC_ENTRYSIZE + 14,
|
|
1495
|
+
),
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// Avail ring fields
|
|
1500
|
+
|
|
1501
|
+
avail_get_flags(): number {
|
|
1502
|
+
return this.cpu.read16(this.avail_addr)
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
avail_get_idx(): number {
|
|
1506
|
+
return this.cpu.read16(this.avail_addr + 2)
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
avail_get_entry(i: number): number {
|
|
1510
|
+
return this.cpu.read16(
|
|
1511
|
+
this.avail_addr + 4 + VIRTQ_AVAIL_ENTRYSIZE * (i & this.mask),
|
|
1512
|
+
)
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
avail_get_used_event(): number {
|
|
1516
|
+
return this.cpu.read16(
|
|
1517
|
+
this.avail_addr + 4 + VIRTQ_AVAIL_ENTRYSIZE * this.size,
|
|
1518
|
+
)
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
// Used ring fields
|
|
1522
|
+
|
|
1523
|
+
used_get_flags(): number {
|
|
1524
|
+
return this.cpu.read16(this.used_addr)
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
used_set_flags(value: number): void {
|
|
1528
|
+
this.cpu.write16(this.used_addr, value)
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
used_get_idx(): number {
|
|
1532
|
+
return this.cpu.read16(this.used_addr + 2)
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
used_set_idx(value: number): void {
|
|
1536
|
+
this.cpu.write16(this.used_addr + 2, value)
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
used_set_entry(i: number, desc_idx: number, length_written: number): void {
|
|
1540
|
+
this.cpu.write32(
|
|
1541
|
+
this.used_addr + 4 + VIRTQ_USED_ENTRYSIZE * i,
|
|
1542
|
+
desc_idx,
|
|
1543
|
+
)
|
|
1544
|
+
this.cpu.write32(
|
|
1545
|
+
this.used_addr + 8 + VIRTQ_USED_ENTRYSIZE * i,
|
|
1546
|
+
length_written,
|
|
1547
|
+
)
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
used_set_avail_event(value: number): void {
|
|
1551
|
+
this.cpu.write16(
|
|
1552
|
+
this.used_addr + 4 + VIRTQ_USED_ENTRYSIZE * this.size,
|
|
1553
|
+
value,
|
|
1554
|
+
)
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
// Traverses through descriptor chain starting at head_id.
|
|
1559
|
+
// Provides means to read/write to buffers represented by the descriptors.
|
|
1560
|
+
export class VirtQueueBufferChain {
|
|
1561
|
+
cpu: VirtioCPU
|
|
1562
|
+
virtio: VirtIO
|
|
1563
|
+
head_idx: number
|
|
1564
|
+
read_buffers: VirtQueueDescriptor[]
|
|
1565
|
+
read_buffer_idx: number
|
|
1566
|
+
read_buffer_offset: number
|
|
1567
|
+
length_readable: number
|
|
1568
|
+
write_buffers: VirtQueueDescriptor[]
|
|
1569
|
+
write_buffer_idx: number
|
|
1570
|
+
write_buffer_offset: number
|
|
1571
|
+
length_written: number
|
|
1572
|
+
length_writable: number
|
|
1573
|
+
|
|
1574
|
+
constructor(virtqueue: VirtQueue, head_idx: number) {
|
|
1575
|
+
this.cpu = virtqueue.cpu
|
|
1576
|
+
this.virtio = virtqueue.virtio
|
|
1577
|
+
|
|
1578
|
+
this.head_idx = head_idx
|
|
1579
|
+
|
|
1580
|
+
this.read_buffers = []
|
|
1581
|
+
// Pointers for sequential consumption via get_next_blob.
|
|
1582
|
+
this.read_buffer_idx = 0
|
|
1583
|
+
this.read_buffer_offset = 0
|
|
1584
|
+
this.length_readable = 0
|
|
1585
|
+
|
|
1586
|
+
this.write_buffers = []
|
|
1587
|
+
// Pointers for sequential write via set_next_blob.
|
|
1588
|
+
this.write_buffer_idx = 0
|
|
1589
|
+
this.write_buffer_offset = 0
|
|
1590
|
+
this.length_written = 0
|
|
1591
|
+
this.length_writable = 0
|
|
1592
|
+
|
|
1593
|
+
// Traverse chain to discover buffers.
|
|
1594
|
+
// - There shouldn't be an excessive amount of descriptor elements.
|
|
1595
|
+
let table_address = virtqueue.desc_addr
|
|
1596
|
+
let desc_idx = head_idx
|
|
1597
|
+
let chain_length = 0
|
|
1598
|
+
let chain_max = virtqueue.size
|
|
1599
|
+
let writable_region = false
|
|
1600
|
+
const has_indirect_feature = this.virtio.is_feature_negotiated(
|
|
1601
|
+
VIRTIO_F_RING_INDIRECT_DESC,
|
|
1602
|
+
)
|
|
1603
|
+
dbg_log('<<< Descriptor chain start', LOG_VIRTIO)
|
|
1604
|
+
do {
|
|
1605
|
+
const desc = virtqueue.get_descriptor(table_address, desc_idx)
|
|
1606
|
+
|
|
1607
|
+
dbg_log(
|
|
1608
|
+
'descriptor: idx=' +
|
|
1609
|
+
desc_idx +
|
|
1610
|
+
' addr=' +
|
|
1611
|
+
h(desc.addr_high, 8) +
|
|
1612
|
+
':' +
|
|
1613
|
+
h(desc.addr_low, 8) +
|
|
1614
|
+
' len=' +
|
|
1615
|
+
h(desc.len, 8) +
|
|
1616
|
+
' flags=' +
|
|
1617
|
+
h(desc.flags, 4) +
|
|
1618
|
+
' next=' +
|
|
1619
|
+
h(desc.next, 4),
|
|
1620
|
+
LOG_VIRTIO,
|
|
1621
|
+
)
|
|
1622
|
+
|
|
1623
|
+
if (has_indirect_feature && desc.flags & VIRTQ_DESC_F_INDIRECT) {
|
|
1624
|
+
if (DEBUG && desc.flags & VIRTQ_DESC_F_NEXT) {
|
|
1625
|
+
dbg_log(
|
|
1626
|
+
'Driver bug: has set VIRTQ_DESC_F_NEXT flag in an indirect table descriptor',
|
|
1627
|
+
LOG_VIRTIO,
|
|
1628
|
+
)
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
// Carry on using indirect table, starting at first entry.
|
|
1632
|
+
table_address = desc.addr_low
|
|
1633
|
+
desc_idx = 0
|
|
1634
|
+
chain_length = 0
|
|
1635
|
+
chain_max = desc.len / VIRTQ_DESC_ENTRYSIZE
|
|
1636
|
+
dbg_log('start indirect', LOG_VIRTIO)
|
|
1637
|
+
continue
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
if (desc.flags & VIRTQ_DESC_F_WRITE) {
|
|
1641
|
+
writable_region = true
|
|
1642
|
+
this.write_buffers.push(desc)
|
|
1643
|
+
this.length_writable += desc.len
|
|
1644
|
+
} else {
|
|
1645
|
+
if (writable_region) {
|
|
1646
|
+
dbg_log(
|
|
1647
|
+
'Driver bug: readonly buffer after writeonly buffer within chain',
|
|
1648
|
+
LOG_VIRTIO,
|
|
1649
|
+
)
|
|
1650
|
+
break
|
|
1651
|
+
}
|
|
1652
|
+
this.read_buffers.push(desc)
|
|
1653
|
+
this.length_readable += desc.len
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
chain_length++
|
|
1657
|
+
if (chain_length > chain_max) {
|
|
1658
|
+
dbg_log(
|
|
1659
|
+
'Driver bug: descriptor chain cycle detected',
|
|
1660
|
+
LOG_VIRTIO,
|
|
1661
|
+
)
|
|
1662
|
+
break
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
if (desc.flags & VIRTQ_DESC_F_NEXT) {
|
|
1666
|
+
desc_idx = desc.next
|
|
1667
|
+
} else {
|
|
1668
|
+
break
|
|
1669
|
+
}
|
|
1670
|
+
// eslint-disable-next-line no-constant-condition
|
|
1671
|
+
} while (true)
|
|
1672
|
+
dbg_log('Descriptor chain end >>>', LOG_VIRTIO)
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
// Reads the next blob of memory represented by the buffer chain into dest_buffer.
|
|
1676
|
+
get_next_blob(dest_buffer: Uint8Array): number {
|
|
1677
|
+
let dest_offset = 0
|
|
1678
|
+
let remaining = dest_buffer.length
|
|
1679
|
+
|
|
1680
|
+
while (remaining) {
|
|
1681
|
+
if (this.read_buffer_idx === this.read_buffers.length) {
|
|
1682
|
+
dbg_log(
|
|
1683
|
+
'Device<' +
|
|
1684
|
+
this.virtio.name +
|
|
1685
|
+
'> Read more than device-readable buffers has',
|
|
1686
|
+
LOG_VIRTIO,
|
|
1687
|
+
)
|
|
1688
|
+
break
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
const buf = this.read_buffers[this.read_buffer_idx]
|
|
1692
|
+
const read_address = buf.addr_low + this.read_buffer_offset
|
|
1693
|
+
let read_length = buf.len - this.read_buffer_offset
|
|
1694
|
+
|
|
1695
|
+
if (read_length > remaining) {
|
|
1696
|
+
read_length = remaining
|
|
1697
|
+
this.read_buffer_offset += remaining
|
|
1698
|
+
} else {
|
|
1699
|
+
this.read_buffer_idx++
|
|
1700
|
+
this.read_buffer_offset = 0
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
dest_buffer.set(
|
|
1704
|
+
this.cpu.read_blob(read_address, read_length),
|
|
1705
|
+
dest_offset,
|
|
1706
|
+
)
|
|
1707
|
+
|
|
1708
|
+
dest_offset += read_length
|
|
1709
|
+
remaining -= read_length
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
return dest_offset
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
// Appends contents of src_buffer into the memory represented by the buffer chain.
|
|
1716
|
+
set_next_blob(src_buffer: Uint8Array): number {
|
|
1717
|
+
let src_offset = 0
|
|
1718
|
+
let remaining = src_buffer.length
|
|
1719
|
+
|
|
1720
|
+
while (remaining) {
|
|
1721
|
+
if (this.write_buffer_idx === this.write_buffers.length) {
|
|
1722
|
+
dbg_log(
|
|
1723
|
+
'Device<' +
|
|
1724
|
+
this.virtio.name +
|
|
1725
|
+
'> Write more than device-writable capacity',
|
|
1726
|
+
LOG_VIRTIO,
|
|
1727
|
+
)
|
|
1728
|
+
break
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
const buf = this.write_buffers[this.write_buffer_idx]
|
|
1732
|
+
const write_address = buf.addr_low + this.write_buffer_offset
|
|
1733
|
+
let write_length = buf.len - this.write_buffer_offset
|
|
1734
|
+
|
|
1735
|
+
if (write_length > remaining) {
|
|
1736
|
+
write_length = remaining
|
|
1737
|
+
this.write_buffer_offset += remaining
|
|
1738
|
+
} else {
|
|
1739
|
+
this.write_buffer_idx++
|
|
1740
|
+
this.write_buffer_offset = 0
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
const src_end = src_offset + write_length
|
|
1744
|
+
this.cpu.write_blob(
|
|
1745
|
+
src_buffer.subarray(src_offset, src_end),
|
|
1746
|
+
write_address,
|
|
1747
|
+
)
|
|
1748
|
+
|
|
1749
|
+
src_offset += write_length
|
|
1750
|
+
remaining -= write_length
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
this.length_written += src_offset
|
|
1754
|
+
return src_offset
|
|
1755
|
+
}
|
|
1756
|
+
}
|