@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.
Files changed (111) hide show
  1. package/LICENSE +22 -0
  2. package/LICENSE.MIT +22 -0
  3. package/Readme.md +237 -0
  4. package/dist/v86.browser.js +26666 -0
  5. package/dist/v86.browser.js.map +7 -0
  6. package/dist/v86.js +26632 -0
  7. package/dist/v86.js.map +7 -0
  8. package/gen/generate_analyzer.ts +512 -0
  9. package/gen/generate_interpreter.ts +522 -0
  10. package/gen/generate_jit.ts +624 -0
  11. package/gen/rust_ast.ts +107 -0
  12. package/gen/util.ts +35 -0
  13. package/gen/x86_table.ts +1836 -0
  14. package/lib/9p.ts +1547 -0
  15. package/lib/filesystem.ts +1879 -0
  16. package/lib/marshall.ts +168 -0
  17. package/lib/softfloat/softfloat.c +32501 -0
  18. package/lib/zstd/zstddeclib.c +13520 -0
  19. package/package.json +75 -0
  20. package/src/acpi.ts +267 -0
  21. package/src/browser/dummy_screen.ts +106 -0
  22. package/src/browser/fake_network.ts +1771 -0
  23. package/src/browser/fetch_network.ts +361 -0
  24. package/src/browser/filestorage.ts +124 -0
  25. package/src/browser/inbrowser_network.ts +57 -0
  26. package/src/browser/keyboard.ts +564 -0
  27. package/src/browser/main.ts +3415 -0
  28. package/src/browser/mouse.ts +255 -0
  29. package/src/browser/network.ts +142 -0
  30. package/src/browser/print_stats.ts +336 -0
  31. package/src/browser/screen.ts +978 -0
  32. package/src/browser/serial.ts +316 -0
  33. package/src/browser/speaker.ts +1223 -0
  34. package/src/browser/starter.ts +1688 -0
  35. package/src/browser/wisp_network.ts +332 -0
  36. package/src/browser/worker_bus.ts +64 -0
  37. package/src/buffer.ts +652 -0
  38. package/src/bus.ts +78 -0
  39. package/src/const.ts +128 -0
  40. package/src/cpu.ts +2891 -0
  41. package/src/dma.ts +474 -0
  42. package/src/elf.ts +251 -0
  43. package/src/floppy.ts +1778 -0
  44. package/src/ide.ts +3455 -0
  45. package/src/io.ts +504 -0
  46. package/src/iso9660.ts +317 -0
  47. package/src/kernel.ts +250 -0
  48. package/src/lib.ts +645 -0
  49. package/src/log.ts +149 -0
  50. package/src/main.ts +199 -0
  51. package/src/ne2k.ts +1589 -0
  52. package/src/pci.ts +815 -0
  53. package/src/pit.ts +406 -0
  54. package/src/ps2.ts +820 -0
  55. package/src/rtc.ts +537 -0
  56. package/src/rust/analysis.rs +101 -0
  57. package/src/rust/codegen.rs +2660 -0
  58. package/src/rust/config.rs +3 -0
  59. package/src/rust/control_flow.rs +425 -0
  60. package/src/rust/cpu/apic.rs +658 -0
  61. package/src/rust/cpu/arith.rs +1207 -0
  62. package/src/rust/cpu/call_indirect.rs +2 -0
  63. package/src/rust/cpu/cpu.rs +4501 -0
  64. package/src/rust/cpu/fpu.rs +923 -0
  65. package/src/rust/cpu/global_pointers.rs +112 -0
  66. package/src/rust/cpu/instructions.rs +2486 -0
  67. package/src/rust/cpu/instructions_0f.rs +5261 -0
  68. package/src/rust/cpu/ioapic.rs +316 -0
  69. package/src/rust/cpu/memory.rs +351 -0
  70. package/src/rust/cpu/misc_instr.rs +613 -0
  71. package/src/rust/cpu/mod.rs +16 -0
  72. package/src/rust/cpu/modrm.rs +133 -0
  73. package/src/rust/cpu/pic.rs +402 -0
  74. package/src/rust/cpu/sse_instr.rs +361 -0
  75. package/src/rust/cpu/string.rs +701 -0
  76. package/src/rust/cpu/vga.rs +175 -0
  77. package/src/rust/cpu_context.rs +69 -0
  78. package/src/rust/dbg.rs +98 -0
  79. package/src/rust/gen/analyzer.rs +3807 -0
  80. package/src/rust/gen/analyzer0f.rs +3992 -0
  81. package/src/rust/gen/interpreter.rs +4447 -0
  82. package/src/rust/gen/interpreter0f.rs +5404 -0
  83. package/src/rust/gen/jit.rs +5080 -0
  84. package/src/rust/gen/jit0f.rs +5547 -0
  85. package/src/rust/gen/mod.rs +14 -0
  86. package/src/rust/jit.rs +2443 -0
  87. package/src/rust/jit_instructions.rs +7881 -0
  88. package/src/rust/js_api.rs +6 -0
  89. package/src/rust/leb.rs +46 -0
  90. package/src/rust/lib.rs +29 -0
  91. package/src/rust/modrm.rs +330 -0
  92. package/src/rust/opstats.rs +249 -0
  93. package/src/rust/page.rs +15 -0
  94. package/src/rust/paging.rs +25 -0
  95. package/src/rust/prefix.rs +15 -0
  96. package/src/rust/profiler.rs +155 -0
  97. package/src/rust/regs.rs +38 -0
  98. package/src/rust/softfloat.rs +286 -0
  99. package/src/rust/state_flags.rs +27 -0
  100. package/src/rust/wasmgen/mod.rs +2 -0
  101. package/src/rust/wasmgen/wasm_builder.rs +1047 -0
  102. package/src/rust/wasmgen/wasm_opcodes.rs +221 -0
  103. package/src/rust/zstd.rs +105 -0
  104. package/src/sb16.ts +1928 -0
  105. package/src/state.ts +359 -0
  106. package/src/uart.ts +472 -0
  107. package/src/vga.ts +2791 -0
  108. package/src/virtio.ts +1756 -0
  109. package/src/virtio_balloon.ts +273 -0
  110. package/src/virtio_console.ts +372 -0
  111. 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
+ }