@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/floppy.ts ADDED
@@ -0,0 +1,1778 @@
1
+ // v86 Floppy Disk Controller emulation
2
+ //
3
+ // This file is licensed under both BSD and MIT, see LICENSE and LICENSE.MIT.
4
+ //
5
+ // Links
6
+ // - Intel 82078 44 Pin CHMOS Single-Chip Floppy Disk Controller
7
+ // https://wiki.qemu.org/images/f/f0/29047403.pdf
8
+ // - qemu: fdc.c
9
+ // https://github.com/qemu/qemu/blob/master/hw/block/fdc.c
10
+ // - Programming Floppy Disk Controllers
11
+ // https://www.isdaman.com/alsos/hardware/fdc/floppy.htm
12
+ // - OSDev: Floppy Disk Controller
13
+ // https://wiki.osdev.org/Floppy_Disk_Controller
14
+
15
+ declare let DEBUG: boolean
16
+
17
+ import { LOG_FLOPPY } from './const.js'
18
+ import { h } from './lib.js'
19
+ import { dbg_assert, dbg_log } from './log.js'
20
+ import { CMOS_FLOPPY_DRIVE_TYPE } from './rtc.js'
21
+ import { SyncBuffer } from './buffer.js'
22
+ import { DMA } from './dma.js'
23
+ import { IO } from './io.js'
24
+
25
+ // Minimal interface for the CPU fields FloppyController uses.
26
+ interface FloppyCpu {
27
+ io: IO
28
+ devices: {
29
+ dma: DMA
30
+ rtc: { cmos_write(register: number, value: number): void }
31
+ }
32
+ device_raise_irq(irq: number): void
33
+ device_lower_irq(irq: number): void
34
+ }
35
+
36
+ interface FloppyDriveConfig {
37
+ drive_type?: number
38
+ read_only?: boolean
39
+ }
40
+
41
+ interface FloppyControllerConfig {
42
+ fda?: FloppyDriveConfig
43
+ fdb?: FloppyDriveConfig
44
+ }
45
+
46
+ // System resources
47
+ const FDC_IRQ_CHANNEL = 6
48
+ const FDC_DMA_CHANNEL = 2
49
+
50
+ /**
51
+ * Floppy drive types
52
+ * CMOS register 0x10 bits: upper nibble: fda, lower nibble: fdb
53
+ * @see {@link https://wiki.osdev.org/CMOS#Register_0x10}
54
+ */
55
+ const CMOS_FDD_TYPE_NO_DRIVE = 0x0 // no floppy drive
56
+ const CMOS_FDD_TYPE_360 = 0x1 // 360 KB 5"1/4 drive
57
+ const CMOS_FDD_TYPE_1200 = 0x2 // 1.2 MB 5"1/4 drive
58
+ const CMOS_FDD_TYPE_720 = 0x3 // 720 KB 3"1/2 drive
59
+ const CMOS_FDD_TYPE_1440 = 0x4 // 1.44 MB 3"1/2 drive
60
+ const CMOS_FDD_TYPE_2880 = 0x5 // 2.88 MB 3"1/2 drive
61
+
62
+ // Physical floppy disk size identifier of each drive type
63
+ const CMOS_FDD_TYPE_MEDIUM: Record<number, number> = {
64
+ [CMOS_FDD_TYPE_NO_DRIVE]: 0, // no disk
65
+ [CMOS_FDD_TYPE_360]: 525, // 5"1/4 disk
66
+ [CMOS_FDD_TYPE_1200]: 525, // 5"1/4 disk
67
+ [CMOS_FDD_TYPE_720]: 350, // 3"1/2 disk
68
+ [CMOS_FDD_TYPE_1440]: 350, // 3"1/2 disk
69
+ [CMOS_FDD_TYPE_2880]: 350, // 3"1/2 disk
70
+ }
71
+
72
+ // Floppy Controller PIO Register offsets (base: 0x3F0/0x370, offset 0x6 is reserved for ATA IDE)
73
+ const REG_SRA = 0x0 // R, Status Register A (SRA)
74
+ const REG_SRB = 0x1 // R, Status Register B (SRB)
75
+ const REG_DOR = 0x2 // RW, Digital Output Register (DOR)
76
+ const REG_TDR = 0x3 // RW, Tape Drive Register (TDR)
77
+ const REG_MSR = 0x4 // R, Main Status Register (MSR)
78
+ const REG_DSR = 0x4 // W, Datarate Select Register (DSR)
79
+ const REG_FIFO = 0x5 // RW, W: command bytes, R: response bytes (FIFO)
80
+ const REG_DIR = 0x7 // R, Digital Input Register (DIR)
81
+ const REG_CCR = 0x7 // W, Configuration Control Register (CCR)
82
+
83
+ // Status Register A (SRA) bits
84
+ const _SRA_NDRV2 = 0x40 // true: second drive is not connected
85
+ const SRA_INTPEND = 0x80 // true: interrupt pending
86
+
87
+ // Status Register B (SRB) bits
88
+ const SRB_MTR0 = 0x1 // follows DOR.DOR_MOT0
89
+ const SRB_MTR1 = 0x2 // follows DOR.DOR_MOT1
90
+ const SRB_DR0 = 0x20 // follows DOR.DOR_SEL_LO
91
+ const SRB_RESET = 0xc0 // magic value after reset
92
+
93
+ // Digital Output Register (DOR) bits
94
+ const DOR_SEL_LO = 0x1 // lower bit of selected FDD number
95
+ const DOR_SEL_HI = 0x2 // upper bit of selected FDD number
96
+ const DOR_NRESET = 0x4 // true: normal controller mode, false: reset mode ("not RESET")
97
+ const DOR_DMAEN = 0x8 // true: use DMA
98
+ const DOR_MOTEN0 = 0x10 // true: enable motor of FDD0
99
+ const DOR_MOTEN1 = 0x20 // true: enable motor of FDD1
100
+ const _DOR_MOTEN2 = 0x40 // true: enable motor of FDD2
101
+ const _DOR_MOTEN3 = 0x80 // true: enable motor of FDD3
102
+ const DOR_SELMASK = 0x01
103
+
104
+ // Tape Drive Register (TDR) bits
105
+ const TDR_BOOTSEL = 0x4
106
+
107
+ // Main Status Register (MSR) bits
108
+ const _MSR_FDD0 = 0x1 // true: FDD0 is busy in seek mode
109
+ const _MSR_FDD1 = 0x2 // true: FDD1 is busy in seek mode
110
+ const _MSR_FDD2 = 0x4 // true: FDD2 is busy in seek mode
111
+ const _MSR_FDD3 = 0x8 // true: FDD3 is busy in seek mode
112
+ const MSR_CMDBUSY = 0x10 // true: FDC busy, Read/Write command in progress, cleared at end of Result phase
113
+ const MSR_NDMA = 0x20 // Non-DMA mode, set in Execution phase of PIO mode read/write commands only.
114
+ const MSR_DIO = 0x40 // Data Input/Output, true: FDC has data for CPU, false: FDC expects data from CPU
115
+ const MSR_RQM = 0x80 // true: DATA register is ready for I/O
116
+
117
+ // Datarate Select Register (DSR) bits
118
+ const DSR_DRATEMASK = 0x3
119
+ const DSR_PWRDOWN = 0x40
120
+ const DSR_SWRESET = 0x80
121
+
122
+ // Digital Input Register (DIR) bits
123
+ const DIR_DOOR = 0x80 // true: No disk or disk changed since last command
124
+
125
+ // Status Register 0 (SR0) bits
126
+ const SR0_DS0 = 0x1 // Drive select 0..3 lower bit
127
+ const SR0_DS1 = 0x2 // Drive select 0..3 upper bit
128
+ const SR0_HEAD = 0x4 // true: Use 2nd head
129
+ const _SR0_EQPMT = 0x10 // (?)
130
+ const SR0_SEEK = 0x20 // (?)
131
+ const SR0_ABNTERM = 0x40 // true: Command failed
132
+ const SR0_INVCMD = 0x80 // true: Unknown/unimplemented command code
133
+ const SR0_RDYCHG = SR0_ABNTERM | SR0_INVCMD // 0xC0 (?)
134
+
135
+ // Status Register 1 (SR1) bits
136
+ const SR1_MA = 0x1 // true: Missing address mark error
137
+ const SR1_NW = 0x2 // true: Not writable error
138
+ const SR1_EC = 0x80 // true: End of cylinder error
139
+
140
+ // Status Register 2 (SR2) bits
141
+ const _SR2_SNS = 0x4 // true: Scan not satisfied (?)
142
+ const _SR2_SEH = 0x8 // true: Scan equal hit (?)
143
+
144
+ /**
145
+ * FDC command codes
146
+ * We declare all known floppy commands but implement only the subset that
147
+ * we actually observe in the field. See also build_cmd_lookup_table().
148
+ * @see {@link https://github.com/qemu/qemu/blob/6e1571533fd92bec67e5ab9b1dd1e15032925757/hw/block/fdc.c#L619}
149
+ */
150
+ const CMD_READ_TRACK = 0x2 // unimplemented
151
+ const CMD_SPECIFY = 0x3
152
+ const CMD_SENSE_DRIVE_STATUS = 0x4
153
+ const CMD_WRITE = 0x5
154
+ const CMD_READ = 0x6
155
+ const CMD_RECALIBRATE = 0x7
156
+ const CMD_SENSE_INTERRUPT_STATUS = 0x8
157
+ const CMD_WRITE_DELETED_DATA = 0x9 // unimplemented
158
+ const CMD_READ_ID = 0xa
159
+ const CMD_READ_DELETED_DATA = 0xc // unimplemented
160
+ const CMD_FORMAT_TRACK = 0xd
161
+ const CMD_DUMP_REGS = 0xe
162
+ const CMD_SEEK = 0xf
163
+ const CMD_VERSION = 0x10
164
+ const CMD_SCAN_EQUAL = 0x11 // unimplemented
165
+ const CMD_PERPENDICULAR_MODE = 0x12
166
+ const CMD_CONFIGURE = 0x13
167
+ const CMD_LOCK = 0x14
168
+ const CMD_VERIFY = 0x16 // unimplemented
169
+ const CMD_POWERDOWN_MODE = 0x17 // unimplemented
170
+ const CMD_PART_ID = 0x18
171
+ const CMD_SCAN_LOW_OR_EQUAL = 0x19 // unimplemented
172
+ const CMD_SCAN_HIGH_OR_EQUAL = 0x1d // unimplemented
173
+ const CMD_SAVE = 0x2e // unimplemented
174
+ const CMD_OPTION = 0x33 // unimplemented
175
+ const CMD_RESTORE = 0x4e // unimplemented
176
+ const CMD_DRIVE_SPECIFICATION = 0x8e // unimplemented
177
+ const CMD_RELATIVE_SEEK_OUT = 0x8f // unimplemented
178
+ const CMD_FORMAT_AND_WRITE = 0xcd // unimplemented
179
+ const CMD_RELATIVE_SEEK_IN = 0xcf // unimplemented
180
+
181
+ // FDC command flags
182
+ const CMD_FLAG_MULTI_TRACK = 0x1 // MT: multi-track selector (use both heads) in READ/WRITE
183
+
184
+ // FDC command execution phases
185
+ const CMD_PHASE_COMMAND = 1
186
+ const CMD_PHASE_EXECUTION = 2
187
+ const CMD_PHASE_RESULT = 3
188
+
189
+ // FDC config bits
190
+ const _CONFIG_PRETRK = 0xff // Pre-compensation set to track 0
191
+ const _CONFIG_FIFOTHR = 0x0f // FIFO threshold set to 1 byte
192
+ const _CONFIG_POLL = 0x10 // Poll enabled
193
+ const CONFIG_EFIFO = 0x20 // FIFO disabled
194
+ const CONFIG_EIS = 0x40 // No implied seeks
195
+
196
+ // Number of CMD_SENSE_INTERRUPT_STATUS expected after reset
197
+ const RESET_SENSE_INT_MAX = 4
198
+
199
+ // Sector size
200
+ const SECTOR_SIZE = 512 // fixed size of 512 bytes/sector
201
+ const SECTOR_SIZE_CODE = 2 // sector size code 2: 512 bytes/sector
202
+
203
+ interface CmdDescriptor {
204
+ code: number
205
+ mask: number
206
+ argc: number
207
+ name: string
208
+ handler: (args: Uint8Array) => void
209
+ }
210
+
211
+ // class FloppyController ----------------------------------------------------
212
+
213
+ export class FloppyController {
214
+ name: string = 'fdc'
215
+ io: IO
216
+ cpu: FloppyCpu
217
+ dma: DMA
218
+
219
+ cmd_table: CmdDescriptor[]
220
+
221
+ sra: number
222
+ srb: number
223
+ dor: number
224
+ tdr: number
225
+ msr: number
226
+ dsr: number
227
+
228
+ cmd_phase: number
229
+ cmd_code: number
230
+ cmd_flags: number
231
+ cmd_buffer: Uint8Array
232
+ cmd_cursor: number
233
+ cmd_remaining: number
234
+
235
+ response_data: Uint8Array
236
+ response_cursor: number
237
+ response_length: number
238
+ status0: number
239
+ status1: number
240
+
241
+ curr_drive_no: number
242
+ reset_sense_int_count: number
243
+ locked: boolean
244
+ step_rate_interval: number
245
+ head_load_time: number
246
+ fdc_config: number
247
+ precomp_trk: number
248
+ eot: number
249
+
250
+ drives: [FloppyDrive, FloppyDrive]
251
+
252
+ constructor(
253
+ cpu: FloppyCpu,
254
+ fda_image: SyncBuffer | Uint8Array | null | undefined,
255
+ fdb_image: SyncBuffer | Uint8Array | null | undefined,
256
+ fdc_config?: FloppyControllerConfig,
257
+ ) {
258
+ this.io = cpu.io
259
+ this.cpu = cpu
260
+ this.dma = cpu.devices.dma
261
+
262
+ this.cmd_table = this.build_cmd_lookup_table()
263
+
264
+ this.sra = 0
265
+ this.srb = SRB_RESET
266
+ this.dor = DOR_NRESET | DOR_DMAEN
267
+ this.tdr = 0
268
+ this.msr = MSR_RQM
269
+ this.dsr = 0
270
+
271
+ this.cmd_phase = CMD_PHASE_COMMAND
272
+ this.cmd_code = 0
273
+ this.cmd_flags = 0
274
+ this.cmd_buffer = new Uint8Array(17) // CMD_RESTORE has 17 argument bytes
275
+ this.cmd_cursor = 0
276
+ this.cmd_remaining = 0
277
+
278
+ this.response_data = new Uint8Array(15) // CMD_SAVE response size is 15 bytes
279
+ this.response_cursor = 0
280
+ this.response_length = 0
281
+ this.status0 = 0
282
+ this.status1 = 0
283
+
284
+ this.curr_drive_no = 0 // was: this.drive; qemu: fdctrl->cur_drv
285
+ this.reset_sense_int_count = 0 // see SENSE INTERRUPT
286
+ this.locked = false // see LOCK
287
+ this.step_rate_interval = 0 // see SPECIFY, qemu: timer0
288
+ this.head_load_time = 0 // see SPECIFY, qemu: timer1
289
+ this.fdc_config = CONFIG_EIS | CONFIG_EFIFO // see CONFIGURE, qemu: config
290
+ this.precomp_trk = 0 // see CONFIGURE
291
+ this.eot = 0 // see READ/WRITE
292
+
293
+ this.drives = [
294
+ new FloppyDrive(
295
+ 'fda',
296
+ fdc_config?.fda,
297
+ fda_image,
298
+ CMOS_FDD_TYPE_1440,
299
+ ),
300
+ new FloppyDrive(
301
+ 'fdb',
302
+ fdc_config?.fdb,
303
+ fdb_image,
304
+ CMOS_FDD_TYPE_1440,
305
+ ),
306
+ ]
307
+
308
+ // To make the floppy drives visible to the guest OS we MUST write a
309
+ // drive type other than 0 (CMOS_FDD_TYPE_NO_DRIVE) to either nibble of
310
+ // CMOS register 0x10 before the guest is started (CMOS registers are
311
+ // usually read only once at startup).
312
+ this.cpu.devices.rtc.cmos_write(
313
+ CMOS_FLOPPY_DRIVE_TYPE,
314
+ (this.drives[0].drive_type << 4) | this.drives[1].drive_type,
315
+ )
316
+
317
+ const fdc_io_base = 0x3f0 // alt: 0x370
318
+
319
+ this.io.register_read(fdc_io_base | REG_SRA, this, this.read_reg_sra)
320
+ this.io.register_read(fdc_io_base | REG_SRB, this, this.read_reg_srb)
321
+ this.io.register_read(fdc_io_base | REG_DOR, this, this.read_reg_dor)
322
+ this.io.register_read(fdc_io_base | REG_TDR, this, this.read_reg_tdr)
323
+ this.io.register_read(fdc_io_base | REG_MSR, this, this.read_reg_msr)
324
+ this.io.register_read(fdc_io_base | REG_FIFO, this, this.read_reg_fifo)
325
+ this.io.register_read(fdc_io_base | REG_DIR, this, this.read_reg_dir)
326
+
327
+ this.io.register_write(fdc_io_base | REG_DOR, this, this.write_reg_dor)
328
+ this.io.register_write(fdc_io_base | REG_TDR, this, this.write_reg_tdr)
329
+ this.io.register_write(fdc_io_base | REG_DSR, this, this.write_reg_dsr)
330
+ this.io.register_write(
331
+ fdc_io_base | REG_FIFO,
332
+ this,
333
+ this.write_reg_fifo,
334
+ )
335
+ this.io.register_write(fdc_io_base | REG_CCR, this, this.write_reg_ccr)
336
+
337
+ dbg_log('floppy controller ready', LOG_FLOPPY)
338
+ }
339
+
340
+ build_cmd_lookup_table(): CmdDescriptor[] {
341
+ /**
342
+ * NOTE: The order of items in the table below is significant.
343
+ * @see {@link https://github.com/qemu/qemu/blob/aec6836c73403cffa56b9a4c5556451ee16071fe/hw/block/fdc.c#L2160}
344
+ */
345
+ const CMD_DESCRIPTOR: CmdDescriptor[] = [
346
+ {
347
+ code: CMD_READ,
348
+ mask: 0x1f,
349
+ argc: 8,
350
+ name: 'READ',
351
+ handler: this.exec_read,
352
+ },
353
+ {
354
+ code: CMD_WRITE,
355
+ mask: 0x3f,
356
+ argc: 8,
357
+ name: 'WRITE',
358
+ handler: this.exec_write,
359
+ },
360
+ {
361
+ code: CMD_SEEK,
362
+ mask: 0xff,
363
+ argc: 2,
364
+ name: 'SEEK',
365
+ handler: this.exec_seek,
366
+ },
367
+ {
368
+ code: CMD_SENSE_INTERRUPT_STATUS,
369
+ mask: 0xff,
370
+ argc: 0,
371
+ name: 'SENSE INTERRUPT STATUS',
372
+ handler: this.exec_sense_interrupt_status,
373
+ },
374
+ {
375
+ code: CMD_RECALIBRATE,
376
+ mask: 0xff,
377
+ argc: 1,
378
+ name: 'RECALIBRATE',
379
+ handler: this.exec_recalibrate,
380
+ },
381
+ {
382
+ code: CMD_FORMAT_TRACK,
383
+ mask: 0xbf,
384
+ argc: 5,
385
+ name: 'FORMAT TRACK',
386
+ handler: this.exec_format_track,
387
+ },
388
+ {
389
+ code: CMD_READ_TRACK,
390
+ mask: 0xbf,
391
+ argc: 8,
392
+ name: 'READ TRACK',
393
+ handler: this.exec_unimplemented,
394
+ },
395
+ {
396
+ code: CMD_RESTORE,
397
+ mask: 0xff,
398
+ argc: 17,
399
+ name: 'RESTORE',
400
+ handler: this.exec_unimplemented,
401
+ },
402
+ {
403
+ code: CMD_SAVE,
404
+ mask: 0xff,
405
+ argc: 0,
406
+ name: 'SAVE',
407
+ handler: this.exec_unimplemented,
408
+ },
409
+ {
410
+ code: CMD_READ_DELETED_DATA,
411
+ mask: 0x1f,
412
+ argc: 8,
413
+ name: 'READ DELETED DATA',
414
+ handler: this.exec_unimplemented,
415
+ },
416
+ {
417
+ code: CMD_SCAN_EQUAL,
418
+ mask: 0x1f,
419
+ argc: 8,
420
+ name: 'SCAN EQUAL',
421
+ handler: this.exec_unimplemented,
422
+ },
423
+ {
424
+ code: CMD_VERIFY,
425
+ mask: 0x1f,
426
+ argc: 8,
427
+ name: 'VERIFY',
428
+ handler: this.exec_unimplemented,
429
+ },
430
+ {
431
+ code: CMD_SCAN_LOW_OR_EQUAL,
432
+ mask: 0x1f,
433
+ argc: 8,
434
+ name: 'SCAN LOW OR EQUAL',
435
+ handler: this.exec_unimplemented,
436
+ },
437
+ {
438
+ code: CMD_SCAN_HIGH_OR_EQUAL,
439
+ mask: 0x1f,
440
+ argc: 8,
441
+ name: 'SCAN HIGH OR EQUAL',
442
+ handler: this.exec_unimplemented,
443
+ },
444
+ {
445
+ code: CMD_WRITE_DELETED_DATA,
446
+ mask: 0x3f,
447
+ argc: 8,
448
+ name: 'WRITE DELETED DATA',
449
+ handler: this.exec_unimplemented,
450
+ },
451
+ {
452
+ code: CMD_READ_ID,
453
+ mask: 0xbf,
454
+ argc: 1,
455
+ name: 'READ ID',
456
+ handler: this.exec_read_id,
457
+ },
458
+ {
459
+ code: CMD_SPECIFY,
460
+ mask: 0xff,
461
+ argc: 2,
462
+ name: 'SPECIFY',
463
+ handler: this.exec_specify,
464
+ },
465
+ {
466
+ code: CMD_SENSE_DRIVE_STATUS,
467
+ mask: 0xff,
468
+ argc: 1,
469
+ name: 'SENSE DRIVE STATUS',
470
+ handler: this.exec_sense_drive_status,
471
+ },
472
+ {
473
+ code: CMD_PERPENDICULAR_MODE,
474
+ mask: 0xff,
475
+ argc: 1,
476
+ name: 'PERPENDICULAR MODE',
477
+ handler: this.exec_perpendicular_mode,
478
+ },
479
+ {
480
+ code: CMD_CONFIGURE,
481
+ mask: 0xff,
482
+ argc: 3,
483
+ name: 'CONFIGURE',
484
+ handler: this.exec_configure,
485
+ },
486
+ {
487
+ code: CMD_POWERDOWN_MODE,
488
+ mask: 0xff,
489
+ argc: 2,
490
+ name: 'POWERDOWN MODE',
491
+ handler: this.exec_unimplemented,
492
+ },
493
+ {
494
+ code: CMD_OPTION,
495
+ mask: 0xff,
496
+ argc: 1,
497
+ name: 'OPTION',
498
+ handler: this.exec_unimplemented,
499
+ },
500
+ {
501
+ code: CMD_DRIVE_SPECIFICATION,
502
+ mask: 0xff,
503
+ argc: 5,
504
+ name: 'DRIVE SPECIFICATION',
505
+ handler: this.exec_unimplemented,
506
+ },
507
+ {
508
+ code: CMD_RELATIVE_SEEK_OUT,
509
+ mask: 0xff,
510
+ argc: 2,
511
+ name: 'RELATIVE SEEK OUT',
512
+ handler: this.exec_unimplemented,
513
+ },
514
+ {
515
+ code: CMD_FORMAT_AND_WRITE,
516
+ mask: 0xff,
517
+ argc: 10,
518
+ name: 'FORMAT AND WRITE',
519
+ handler: this.exec_unimplemented,
520
+ },
521
+ {
522
+ code: CMD_RELATIVE_SEEK_IN,
523
+ mask: 0xff,
524
+ argc: 2,
525
+ name: 'RELATIVE SEEK IN',
526
+ handler: this.exec_unimplemented,
527
+ },
528
+ {
529
+ code: CMD_LOCK,
530
+ mask: 0x7f,
531
+ argc: 0,
532
+ name: 'LOCK',
533
+ handler: this.exec_lock,
534
+ },
535
+ {
536
+ code: CMD_DUMP_REGS,
537
+ mask: 0xff,
538
+ argc: 0,
539
+ name: 'DUMP REGISTERS',
540
+ handler: this.exec_dump_regs,
541
+ },
542
+ {
543
+ code: CMD_VERSION,
544
+ mask: 0xff,
545
+ argc: 0,
546
+ name: 'VERSION',
547
+ handler: this.exec_version,
548
+ },
549
+ {
550
+ code: CMD_PART_ID,
551
+ mask: 0xff,
552
+ argc: 0,
553
+ name: 'PART ID',
554
+ handler: this.exec_part_id,
555
+ },
556
+
557
+ {
558
+ code: 0,
559
+ mask: 0x00,
560
+ argc: 0,
561
+ name: 'UNKNOWN COMMAND',
562
+ handler: this.exec_unimplemented,
563
+ }, // default handler
564
+ ]
565
+
566
+ const cmd_table: CmdDescriptor[] = new Array(256)
567
+ for (let i = CMD_DESCRIPTOR.length - 1; i >= 0; i--) {
568
+ const cmd_desc = CMD_DESCRIPTOR[i]
569
+ if (cmd_desc.mask === 0xff) {
570
+ cmd_table[cmd_desc.code] = cmd_desc
571
+ } else {
572
+ for (let j = 0; j < 256; j++) {
573
+ if ((j & cmd_desc.mask) === cmd_desc.code) {
574
+ cmd_table[j] = cmd_desc
575
+ }
576
+ }
577
+ }
578
+ }
579
+ return cmd_table
580
+ }
581
+
582
+ raise_irq(reason: string): void {
583
+ if (!(this.sra & SRA_INTPEND)) {
584
+ this.cpu.device_raise_irq(FDC_IRQ_CHANNEL)
585
+ this.sra |= SRA_INTPEND
586
+ dbg_log('IRQ raised, reason: ' + reason, LOG_FLOPPY)
587
+ }
588
+ this.reset_sense_int_count = 0
589
+ }
590
+
591
+ lower_irq(reason: string): void {
592
+ this.status0 = 0
593
+ if (this.sra & SRA_INTPEND) {
594
+ this.cpu.device_lower_irq(FDC_IRQ_CHANNEL)
595
+ this.sra &= ~SRA_INTPEND
596
+ dbg_log('IRQ lowered, reason: ' + reason, LOG_FLOPPY)
597
+ }
598
+ }
599
+
600
+ set_curr_drive_no(curr_drive_no: number): FloppyDrive {
601
+ this.curr_drive_no = curr_drive_no & 1
602
+ return this.drives[this.curr_drive_no]
603
+ }
604
+
605
+ enter_command_phase(): void {
606
+ this.cmd_phase = CMD_PHASE_COMMAND
607
+ this.cmd_cursor = 0
608
+ this.cmd_remaining = 0
609
+ this.msr &= ~(MSR_CMDBUSY | MSR_DIO)
610
+ this.msr |= MSR_RQM
611
+ }
612
+
613
+ enter_result_phase(fifo_len: number): void {
614
+ this.cmd_phase = CMD_PHASE_RESULT
615
+ this.response_cursor = 0
616
+ this.response_length = fifo_len
617
+ this.msr |= MSR_CMDBUSY | MSR_RQM | MSR_DIO
618
+ }
619
+
620
+ reset_fdc(): void {
621
+ dbg_log('resetting controller', LOG_FLOPPY)
622
+ this.lower_irq('controller reset')
623
+
624
+ this.sra = 0 // NOTE: set SRA to SRA_NDRV2 if fdb does not exist
625
+ this.srb = SRB_RESET
626
+ this.dor = DOR_NRESET | DOR_DMAEN
627
+ this.msr = MSR_RQM
628
+ this.curr_drive_no = 0
629
+ this.status0 |= SR0_RDYCHG
630
+
631
+ this.response_cursor = 0
632
+ this.response_length = 0
633
+
634
+ this.drives[0].seek(0, 0, 1)
635
+ this.drives[1].seek(0, 0, 1)
636
+
637
+ // raise interrupt
638
+ this.enter_command_phase()
639
+ this.raise_irq('controller reset')
640
+ this.reset_sense_int_count = RESET_SENSE_INT_MAX
641
+ }
642
+
643
+ // Register I/O callbacks ----------------------------------------------------
644
+
645
+ read_reg_sra(): number {
646
+ dbg_log('SRA read: ' + h(this.sra), LOG_FLOPPY)
647
+ return this.sra
648
+ }
649
+
650
+ read_reg_srb(): number {
651
+ dbg_log('SRB read: ' + h(this.srb), LOG_FLOPPY)
652
+ return this.srb
653
+ }
654
+
655
+ read_reg_dor(): number {
656
+ const dor_byte =
657
+ (this.dor & ~(DOR_SEL_LO | DOR_SEL_HI)) | this.curr_drive_no
658
+ dbg_log('DOR read: ' + h(dor_byte), LOG_FLOPPY)
659
+ return dor_byte
660
+ }
661
+
662
+ read_reg_tdr(): number {
663
+ dbg_log('TDR read: ' + h(this.tdr), LOG_FLOPPY)
664
+ return this.tdr
665
+ }
666
+
667
+ read_reg_msr(): number {
668
+ dbg_log('MSR read: ' + h(this.msr), LOG_FLOPPY)
669
+ this.dsr &= ~DSR_PWRDOWN
670
+ this.dor |= DOR_NRESET
671
+ return this.msr
672
+ }
673
+
674
+ read_reg_fifo(): number {
675
+ this.dsr &= ~DSR_PWRDOWN
676
+ if (!(this.msr & MSR_RQM) || !(this.msr & MSR_DIO)) {
677
+ dbg_log(
678
+ 'FIFO read rejected: controller not ready for reading',
679
+ LOG_FLOPPY,
680
+ )
681
+ return 0
682
+ } else if (this.cmd_phase !== CMD_PHASE_RESULT) {
683
+ dbg_log(
684
+ 'FIFO read rejected: floppy controller not in RESULT phase, phase: ' +
685
+ this.cmd_phase,
686
+ LOG_FLOPPY,
687
+ )
688
+ return 0
689
+ }
690
+
691
+ if (this.response_cursor < this.response_length) {
692
+ const fifo_byte = this.response_data[this.response_cursor++]
693
+ if (this.response_cursor === this.response_length) {
694
+ const lower_irq_reason = DEBUG
695
+ ? 'end of ' +
696
+ this.cmd_table[this.cmd_code].name +
697
+ ' response'
698
+ : ''
699
+ this.msr &= ~MSR_RQM
700
+ this.enter_command_phase()
701
+ this.lower_irq(lower_irq_reason)
702
+ }
703
+ return fifo_byte
704
+ } else {
705
+ dbg_log('FIFO read: empty', LOG_FLOPPY)
706
+ return 0
707
+ }
708
+ }
709
+
710
+ read_reg_dir(): number {
711
+ const curr_drive = this.drives[this.curr_drive_no]
712
+ const dir_byte = curr_drive.media_changed ? DIR_DOOR : 0
713
+ dbg_log('DIR read: ' + h(dir_byte), LOG_FLOPPY)
714
+ return dir_byte
715
+ }
716
+
717
+ write_reg_dor(dor_byte: number): void {
718
+ // update motor and drive bits in Status Register B
719
+ this.srb =
720
+ (this.srb & ~(SRB_MTR0 | SRB_MTR1 | SRB_DR0)) |
721
+ (dor_byte & DOR_MOTEN0 ? SRB_MTR0 : 0) |
722
+ (dor_byte & DOR_MOTEN1 ? SRB_MTR1 : 0) |
723
+ (dor_byte & DOR_SEL_LO ? SRB_DR0 : 0)
724
+
725
+ // RESET-state transitions
726
+ if (this.dor & DOR_NRESET) {
727
+ if (!(dor_byte & DOR_NRESET)) {
728
+ dbg_log('enter RESET state', LOG_FLOPPY)
729
+ }
730
+ } else {
731
+ if (dor_byte & DOR_NRESET) {
732
+ this.reset_fdc()
733
+ this.dsr &= ~DSR_PWRDOWN
734
+ dbg_log('exit RESET state', LOG_FLOPPY)
735
+ }
736
+ }
737
+
738
+ // select current drive
739
+ const new_drive_no = dor_byte & (DOR_SEL_LO | DOR_SEL_HI)
740
+ dbg_log(
741
+ 'DOR write: ' +
742
+ h(dor_byte) +
743
+ ', motors: ' +
744
+ h(dor_byte >> 4) +
745
+ ', dma: ' +
746
+ !!(dor_byte & DOR_DMAEN) +
747
+ ', reset: ' +
748
+ !(dor_byte & DOR_NRESET) +
749
+ ', drive: ' +
750
+ new_drive_no,
751
+ LOG_FLOPPY,
752
+ )
753
+ if (new_drive_no > 1) {
754
+ dbg_log(
755
+ '*** WARNING: floppy drive number ' +
756
+ new_drive_no +
757
+ ' not implemented!',
758
+ LOG_FLOPPY,
759
+ )
760
+ }
761
+ this.curr_drive_no = new_drive_no & DOR_SEL_LO
762
+
763
+ this.dor = dor_byte
764
+ }
765
+
766
+ write_reg_tdr(tdr_byte: number): void {
767
+ if (!(this.dor & DOR_NRESET)) {
768
+ dbg_log(
769
+ 'TDR write ' +
770
+ h(tdr_byte) +
771
+ ' rejected: Floppy controller in RESET mode!',
772
+ LOG_FLOPPY,
773
+ )
774
+ return
775
+ }
776
+
777
+ dbg_log('TDR write: ' + h(tdr_byte), LOG_FLOPPY)
778
+ this.tdr = tdr_byte & TDR_BOOTSEL // Disk boot selection indicator
779
+ }
780
+
781
+ write_reg_dsr(dsr_byte: number): void {
782
+ if (!(this.dor & DOR_NRESET)) {
783
+ dbg_log(
784
+ 'DSR write: ' +
785
+ h(dsr_byte) +
786
+ ' rejected: Floppy controller in RESET mode!',
787
+ LOG_FLOPPY,
788
+ )
789
+ return
790
+ }
791
+
792
+ dbg_log('DSR write: ' + h(dsr_byte), LOG_FLOPPY)
793
+ if (dsr_byte & DSR_SWRESET) {
794
+ this.dor &= ~DOR_NRESET
795
+ this.reset_fdc()
796
+ this.dor |= DOR_NRESET
797
+ }
798
+ if (dsr_byte & DSR_PWRDOWN) {
799
+ this.reset_fdc()
800
+ }
801
+ this.dsr = dsr_byte
802
+ }
803
+
804
+ write_reg_fifo(fifo_byte: number): void {
805
+ this.dsr &= ~DSR_PWRDOWN
806
+ if (!(this.dor & DOR_NRESET)) {
807
+ dbg_log(
808
+ 'FIFO write ' +
809
+ h(fifo_byte) +
810
+ ' rejected: floppy controller in RESET mode!',
811
+ LOG_FLOPPY,
812
+ )
813
+ return
814
+ } else if (!(this.msr & MSR_RQM) || this.msr & MSR_DIO) {
815
+ dbg_log(
816
+ 'FIFO write ' +
817
+ h(fifo_byte) +
818
+ ' rejected: controller not ready for writing',
819
+ LOG_FLOPPY,
820
+ )
821
+ return
822
+ } else if (this.cmd_phase !== CMD_PHASE_COMMAND) {
823
+ dbg_log(
824
+ 'FIFO write ' +
825
+ h(fifo_byte) +
826
+ ' rejected: floppy controller not in COMMAND phase, phase: ' +
827
+ this.cmd_phase,
828
+ LOG_FLOPPY,
829
+ )
830
+ return
831
+ }
832
+
833
+ if (this.cmd_remaining === 0) {
834
+ // start reading command, fifo_byte contains the command code
835
+ const cmd_desc = this.cmd_table[fifo_byte]
836
+ this.cmd_code = fifo_byte
837
+ this.cmd_remaining = cmd_desc.argc
838
+ this.cmd_cursor = 0
839
+ this.cmd_flags = 0
840
+ if (
841
+ (cmd_desc.code === CMD_READ || cmd_desc.code === CMD_WRITE) &&
842
+ this.cmd_code & 0x80
843
+ ) // 0x80: Multi-track (MT)
844
+ {
845
+ this.cmd_flags |= CMD_FLAG_MULTI_TRACK
846
+ }
847
+ if (this.cmd_remaining) {
848
+ this.msr |= MSR_RQM
849
+ }
850
+ this.msr |= MSR_CMDBUSY
851
+ } else {
852
+ // continue reading command, fifo_byte contains an argument value
853
+ this.cmd_buffer[this.cmd_cursor++] = fifo_byte
854
+ this.cmd_remaining--
855
+ }
856
+
857
+ if (this.cmd_remaining === 0) {
858
+ // done reading command: execute
859
+ this.cmd_phase = CMD_PHASE_EXECUTION
860
+ const cmd_desc = this.cmd_table[this.cmd_code]
861
+ const args = this.cmd_buffer.slice(0, this.cmd_cursor)
862
+ if (DEBUG) {
863
+ const args_hex: string[] = []
864
+ for (const arg of args) {
865
+ args_hex.push(h(arg, 2))
866
+ }
867
+ dbg_log(
868
+ 'FD command ' +
869
+ h(this.cmd_code) +
870
+ ': ' +
871
+ cmd_desc.name +
872
+ '(' +
873
+ args_hex.join(', ') +
874
+ ')',
875
+ LOG_FLOPPY,
876
+ )
877
+ }
878
+ cmd_desc.handler.call(this, args)
879
+ }
880
+ }
881
+
882
+ write_reg_ccr(ccr_byte: number): void {
883
+ if (!(this.dor & DOR_NRESET)) {
884
+ dbg_log(
885
+ 'CCR write: ' +
886
+ h(ccr_byte) +
887
+ ' rejected: Floppy controller in RESET mode!',
888
+ LOG_FLOPPY,
889
+ )
890
+ return
891
+ }
892
+
893
+ dbg_log('CCR write: ' + h(ccr_byte), LOG_FLOPPY)
894
+ // only the rate selection bits used in AT mode, and we store those in the DSR
895
+ this.dsr = (this.dsr & ~DSR_DRATEMASK) | (ccr_byte & DSR_DRATEMASK)
896
+ }
897
+
898
+ // Floppy command handler ----------------------------------------------------
899
+
900
+ exec_unimplemented(_args: Uint8Array): void {
901
+ dbg_assert(
902
+ false,
903
+ 'Unimplemented floppy command code ' + h(this.cmd_code) + '!',
904
+ )
905
+ this.status0 = SR0_INVCMD
906
+ this.response_data[0] = this.status0
907
+
908
+ // no interrupt
909
+ this.enter_result_phase(1)
910
+ }
911
+
912
+ exec_read(args: Uint8Array): void {
913
+ this.start_read_write(args, false)
914
+ }
915
+
916
+ exec_write(args: Uint8Array): void {
917
+ this.start_read_write(args, true)
918
+ }
919
+
920
+ exec_seek(args: Uint8Array): void {
921
+ const curr_drive = this.set_curr_drive_no(args[0] & DOR_SELMASK)
922
+ const track = args[1]
923
+
924
+ this.enter_command_phase()
925
+ curr_drive.seek(curr_drive.curr_head, track, curr_drive.curr_sect)
926
+
927
+ // raise interrupt without response
928
+ this.status0 |= SR0_SEEK
929
+ this.raise_irq('SEEK command')
930
+ }
931
+
932
+ exec_sense_interrupt_status(_args: Uint8Array): void {
933
+ const curr_drive = this.drives[this.curr_drive_no]
934
+
935
+ let status0: number
936
+ if (this.reset_sense_int_count > 0) {
937
+ const drv_nr = RESET_SENSE_INT_MAX - this.reset_sense_int_count--
938
+ status0 = SR0_RDYCHG | drv_nr
939
+ } else if (this.sra & SRA_INTPEND) {
940
+ status0 =
941
+ (this.status0 & ~(SR0_HEAD | SR0_DS1 | SR0_DS0)) |
942
+ this.curr_drive_no
943
+ } else {
944
+ dbg_log(
945
+ 'No interrupt pending, aborting SENSE INTERRUPT command!',
946
+ LOG_FLOPPY,
947
+ )
948
+ this.response_data[0] = SR0_INVCMD
949
+ this.enter_result_phase(1)
950
+ return
951
+ }
952
+
953
+ this.response_data[0] = status0
954
+ this.response_data[1] = curr_drive.curr_track
955
+
956
+ // lower interrupt
957
+ this.enter_result_phase(2)
958
+ this.lower_irq('SENSE INTERRUPT command')
959
+ this.status0 = SR0_RDYCHG
960
+ }
961
+
962
+ exec_recalibrate(args: Uint8Array): void {
963
+ const curr_drive = this.set_curr_drive_no(args[0] & DOR_SELMASK)
964
+
965
+ curr_drive.seek(0, 0, 1)
966
+
967
+ // raise interrupt without response
968
+ this.enter_command_phase()
969
+ this.status0 |= SR0_SEEK
970
+ this.raise_irq('RECALIBRATE command')
971
+ }
972
+
973
+ exec_format_track(args: Uint8Array): void {
974
+ const curr_drive = this.set_curr_drive_no(args[0] & DOR_SELMASK)
975
+
976
+ let status0 = 0,
977
+ status1 = 0
978
+ if (curr_drive.read_only) {
979
+ status0 = SR0_ABNTERM | SR0_SEEK
980
+ status1 = SR1_NW
981
+ }
982
+
983
+ // raise interrupt
984
+ this.end_read_write(status0, status1, 0)
985
+ }
986
+
987
+ exec_read_id(args: Uint8Array): void {
988
+ const head_sel = args[0]
989
+ const curr_drive = this.drives[this.curr_drive_no]
990
+
991
+ curr_drive.curr_head = (head_sel >> 2) & 1
992
+ if (curr_drive.max_sect !== 0) {
993
+ curr_drive.curr_sect =
994
+ (curr_drive.curr_sect % curr_drive.max_sect) + 1
995
+ }
996
+
997
+ // raise interrupt
998
+ this.end_read_write(0, 0, 0)
999
+ }
1000
+
1001
+ exec_specify(args: Uint8Array): void {
1002
+ const hut_srt = args[0] // 0..3: Head Unload Time (HUT), 4..7: Step Rate Interval (SRT)
1003
+ const nd_hlt = args[1] // 0: Non-DMA mode flag (ND), 1..7: Head Load Time (HLT)
1004
+
1005
+ this.step_rate_interval = hut_srt >> 4
1006
+ this.head_load_time = nd_hlt >> 1
1007
+ if (nd_hlt & 0x1) {
1008
+ this.dor &= ~DOR_DMAEN
1009
+ } else {
1010
+ this.dor |= DOR_DMAEN
1011
+ }
1012
+
1013
+ // no interrupt or response
1014
+ this.enter_command_phase()
1015
+ }
1016
+
1017
+ exec_sense_drive_status(args: Uint8Array): void {
1018
+ const drv_sel = args[0]
1019
+ const curr_drive = this.set_curr_drive_no(drv_sel & DOR_SELMASK)
1020
+ curr_drive.curr_head = (drv_sel >> 2) & 1
1021
+
1022
+ this.response_data[0] =
1023
+ (curr_drive.read_only ? 0x40 : 0) | // response byte is Status Register 3
1024
+ (curr_drive.curr_track === 0 ? 0x10 : 0) |
1025
+ (curr_drive.curr_head << 2) |
1026
+ this.curr_drive_no |
1027
+ 0x28 // bits 3 and 5 unused, always "1"
1028
+
1029
+ // no interrupt
1030
+ this.enter_result_phase(1)
1031
+ }
1032
+
1033
+ exec_perpendicular_mode(args: Uint8Array): void {
1034
+ const perp_mode = args[0]
1035
+
1036
+ if (perp_mode & 0x80) // 0x80: OW, bits D0 and D1 can be overwritten
1037
+ {
1038
+ const curr_drive = this.drives[this.curr_drive_no]
1039
+ curr_drive.perpendicular = perp_mode & 0x7
1040
+ }
1041
+
1042
+ // no interrupt or response
1043
+ this.enter_command_phase()
1044
+ }
1045
+
1046
+ exec_configure(args: Uint8Array): void {
1047
+ // args[0] is always 0
1048
+ this.fdc_config = args[1]
1049
+ this.precomp_trk = args[2]
1050
+
1051
+ // no interrupt or response
1052
+ this.enter_command_phase()
1053
+ }
1054
+
1055
+ exec_lock(_args: Uint8Array): void {
1056
+ if (this.cmd_code & 0x80) {
1057
+ // LOCK command
1058
+ this.locked = true
1059
+ this.response_data[0] = 0x10
1060
+ } else {
1061
+ // UNLOCK command
1062
+ this.locked = false
1063
+ this.response_data[0] = 0
1064
+ }
1065
+
1066
+ // no interrupt
1067
+ this.enter_result_phase(1)
1068
+ }
1069
+
1070
+ exec_dump_regs(_args: Uint8Array): void {
1071
+ const curr_drive = this.drives[this.curr_drive_no]
1072
+
1073
+ // drive positions
1074
+ this.response_data[0] = this.drives[0].curr_track
1075
+ this.response_data[1] = this.drives[1].curr_track
1076
+ this.response_data[2] = 0
1077
+ this.response_data[3] = 0
1078
+ // timers
1079
+ this.response_data[4] = this.step_rate_interval
1080
+ this.response_data[5] =
1081
+ (this.head_load_time << 1) | (this.dor & DOR_DMAEN ? 1 : 0)
1082
+ this.response_data[6] = curr_drive.max_sect
1083
+ this.response_data[7] =
1084
+ (this.locked ? 0x80 : 0) | (curr_drive.perpendicular << 2)
1085
+ this.response_data[8] = this.fdc_config
1086
+ this.response_data[9] = this.precomp_trk
1087
+
1088
+ // no interrupt
1089
+ this.enter_result_phase(10)
1090
+ }
1091
+
1092
+ exec_version(_args: Uint8Array): void {
1093
+ this.response_data[0] = 0x90 // 0x80: Standard controller, 0x81: Intel 82077, 0x90: Intel 82078
1094
+
1095
+ // no interrupt
1096
+ this.enter_result_phase(1)
1097
+ }
1098
+
1099
+ exec_part_id(_args: Uint8Array): void {
1100
+ this.response_data[0] = 0x41 // Stepping 1 (PS/2 mode)
1101
+
1102
+ // no interrupt
1103
+ this.enter_result_phase(1)
1104
+ }
1105
+
1106
+ // ---------------------------------------------------------------------------
1107
+
1108
+ start_read_write(args: Uint8Array, do_write: boolean): void {
1109
+ const curr_drive = this.set_curr_drive_no(args[0] & DOR_SELMASK)
1110
+ const track = args[1]
1111
+ const head = args[2]
1112
+ const sect = args[3]
1113
+ const ssc = args[4] // sector size code 0..7 (0:128, 1:256, 2:512, ..., 7:16384 bytes/sect)
1114
+ const eot = args[5] // last sector number of current track
1115
+ const dtl = args[7] < 128 ? args[7] : 128 // data length in bytes if ssc is 0, else unused
1116
+
1117
+ switch (curr_drive.seek(head, track, sect)) {
1118
+ case 2: // error: sect too big
1119
+ this.end_read_write(SR0_ABNTERM, 0, 0)
1120
+ this.response_data[3] = track
1121
+ this.response_data[4] = head
1122
+ this.response_data[5] = sect
1123
+ return
1124
+ case 3: // error: track too big
1125
+ this.end_read_write(SR0_ABNTERM, SR1_EC, 0)
1126
+ this.response_data[3] = track
1127
+ this.response_data[4] = head
1128
+ this.response_data[5] = sect
1129
+ return
1130
+ case 1: // track changed
1131
+ this.status0 |= SR0_SEEK
1132
+ break
1133
+ }
1134
+
1135
+ const sect_size = 128 << (ssc > 7 ? 7 : ssc) // sector size in bytes
1136
+ const sect_start = curr_drive.chs2lba(track, head, sect) // linear start sector
1137
+ const data_offset = sect_start * sect_size // data offset in bytes
1138
+ let data_length: number // data length in bytes
1139
+ if (sect_size === 128) {
1140
+ // if requested data length (dtl) is < 128:
1141
+ // - READ: return only dtl bytes, skipping the sector's remaining bytes (OK)
1142
+ // - WRITE: we must fill the sector's remaining bytes with 0 (TODO!)
1143
+ if (do_write && dtl < 128) {
1144
+ dbg_assert(
1145
+ false,
1146
+ 'dtl=' +
1147
+ dtl +
1148
+ ' is less than 128, zero-padding is still unimplemented!',
1149
+ )
1150
+ }
1151
+ data_length = dtl
1152
+ } else {
1153
+ if (this.cmd_flags & CMD_FLAG_MULTI_TRACK) {
1154
+ data_length = (2 * eot - sect + 1) * sect_size
1155
+ } else {
1156
+ data_length = (eot - sect + 1) * sect_size
1157
+ }
1158
+ if (data_length <= 0) {
1159
+ dbg_log(
1160
+ 'invalid data_length: ' +
1161
+ data_length +
1162
+ ' sect=' +
1163
+ sect +
1164
+ ' eot=' +
1165
+ eot,
1166
+ LOG_FLOPPY,
1167
+ )
1168
+ this.end_read_write(SR0_ABNTERM, SR1_MA, 0)
1169
+ this.response_data[3] = track
1170
+ this.response_data[4] = head
1171
+ this.response_data[5] = sect
1172
+ return
1173
+ }
1174
+ }
1175
+ this.eot = eot
1176
+
1177
+ if (DEBUG) {
1178
+ dbg_log(
1179
+ 'Floppy ' +
1180
+ this.cmd_table[this.cmd_code].name +
1181
+ ' from: ' +
1182
+ h(data_offset) +
1183
+ ', length: ' +
1184
+ h(data_length) +
1185
+ ', C/H/S: ' +
1186
+ track +
1187
+ '/' +
1188
+ head +
1189
+ '/' +
1190
+ sect +
1191
+ ', ro: ' +
1192
+ curr_drive.read_only +
1193
+ ', #S: ' +
1194
+ curr_drive.max_sect +
1195
+ ', #H: ' +
1196
+ curr_drive.max_head,
1197
+ LOG_FLOPPY,
1198
+ )
1199
+ }
1200
+
1201
+ if (do_write && curr_drive.read_only) {
1202
+ this.end_read_write(SR0_ABNTERM | SR0_SEEK, SR1_NW, 0)
1203
+ this.response_data[3] = track
1204
+ this.response_data[4] = head
1205
+ this.response_data[5] = sect
1206
+ return
1207
+ }
1208
+
1209
+ if (this.dor & DOR_DMAEN) {
1210
+ // start DMA transfer
1211
+ this.msr &= ~MSR_RQM
1212
+ const do_dma = do_write ? this.dma.do_write : this.dma.do_read
1213
+ do_dma.call(
1214
+ this.dma,
1215
+ curr_drive.buffer!,
1216
+ data_offset,
1217
+ data_length,
1218
+ FDC_DMA_CHANNEL,
1219
+ (dma_error: boolean) => {
1220
+ if (dma_error) {
1221
+ dbg_log('DMA floppy error', LOG_FLOPPY)
1222
+ this.end_read_write(SR0_ABNTERM, 0, 0)
1223
+ } else {
1224
+ this.seek_next_sect()
1225
+ this.end_read_write(0, 0, 0)
1226
+ }
1227
+ },
1228
+ )
1229
+ } else {
1230
+ // start PIO transfer
1231
+ dbg_assert(
1232
+ false,
1233
+ this.cmd_table[this.cmd_code].name +
1234
+ ' in PIO mode not supported!',
1235
+ )
1236
+ }
1237
+ }
1238
+
1239
+ end_read_write(status0: number, status1: number, status2: number): void {
1240
+ const curr_drive = this.drives[this.curr_drive_no]
1241
+
1242
+ this.status0 &= ~(SR0_DS0 | SR0_DS1 | SR0_HEAD)
1243
+ this.status0 |= this.curr_drive_no
1244
+ if (curr_drive.curr_head) {
1245
+ this.status0 |= SR0_HEAD
1246
+ }
1247
+ this.status0 |= status0
1248
+
1249
+ this.msr |= MSR_RQM | MSR_DIO
1250
+ this.msr &= ~MSR_NDMA
1251
+
1252
+ this.response_data[0] = this.status0
1253
+ this.response_data[1] = status1
1254
+ this.response_data[2] = status2
1255
+ this.response_data[3] = curr_drive.curr_track
1256
+ this.response_data[4] = curr_drive.curr_head
1257
+ this.response_data[5] = curr_drive.curr_sect
1258
+ this.response_data[6] = SECTOR_SIZE_CODE
1259
+
1260
+ // raise interrupt
1261
+ this.enter_result_phase(7)
1262
+ this.raise_irq(this.cmd_table[this.cmd_code].name + ' command')
1263
+ }
1264
+
1265
+ seek_next_sect(): number {
1266
+ // Seek to next sector
1267
+ // returns 0 when end of track reached (for DBL_SIDES on head 1). otherwise returns 1
1268
+ const curr_drive = this.drives[this.curr_drive_no]
1269
+
1270
+ // XXX: curr_sect >= max_sect should be an error in fact (TODO: this comment comes from qemu, not sure what to make of it)
1271
+ let new_track = curr_drive.curr_track
1272
+ let new_head = curr_drive.curr_head
1273
+ let new_sect = curr_drive.curr_sect
1274
+ let ret = 1
1275
+
1276
+ if (new_sect >= curr_drive.max_sect || new_sect === this.eot) {
1277
+ new_sect = 1
1278
+ if (this.cmd_flags & CMD_FLAG_MULTI_TRACK) {
1279
+ if (new_head === 0 && curr_drive.max_head === 2) {
1280
+ new_head = 1
1281
+ } else {
1282
+ new_head = 0
1283
+ new_track++
1284
+ this.status0 |= SR0_SEEK
1285
+ if (curr_drive.max_head === 1) {
1286
+ ret = 0
1287
+ }
1288
+ }
1289
+ } else {
1290
+ this.status0 |= SR0_SEEK
1291
+ new_track++
1292
+ ret = 0
1293
+ }
1294
+ } else {
1295
+ new_sect++
1296
+ }
1297
+
1298
+ curr_drive.seek(new_head, new_track, new_sect)
1299
+ return ret
1300
+ }
1301
+
1302
+ get_state(): (number | boolean | Uint8Array | unknown[])[] {
1303
+ // NOTE: Old-style state snapshots (state indices 0..18) did not include
1304
+ // the disk image buffer, only a few register states, so a floppy drive
1305
+ // remained essentially unchangd when a state snapshot was applied.
1306
+ // The snapshotted registers can be safely ignored when restoring state,
1307
+ // hence the entire old-style state is now ignored and deprecated.
1308
+ const state: (number | boolean | Uint8Array | unknown[])[] = []
1309
+ state[19] = this.sra
1310
+ state[20] = this.srb
1311
+ state[21] = this.dor
1312
+ state[22] = this.tdr
1313
+ state[23] = this.msr
1314
+ state[24] = this.dsr
1315
+ state[25] = this.cmd_phase
1316
+ state[26] = this.cmd_code
1317
+ state[27] = this.cmd_flags
1318
+ state[28] = this.cmd_buffer // Uint8Array
1319
+ state[29] = this.cmd_cursor
1320
+ state[30] = this.cmd_remaining
1321
+ state[31] = this.response_data // Uint8Array
1322
+ state[32] = this.response_cursor
1323
+ state[33] = this.response_length
1324
+ state[34] = this.status0
1325
+ state[35] = this.status1
1326
+ state[36] = this.curr_drive_no
1327
+ state[37] = this.reset_sense_int_count
1328
+ state[38] = this.locked
1329
+ state[39] = this.step_rate_interval
1330
+ state[40] = this.head_load_time
1331
+ state[41] = this.fdc_config
1332
+ state[42] = this.precomp_trk
1333
+ state[43] = this.eot
1334
+ state[44] = this.drives[0].get_state()
1335
+ state[45] = this.drives[1].get_state()
1336
+ return state
1337
+ }
1338
+
1339
+ set_state(state: any[]): void {
1340
+ if (typeof state[19] === 'undefined') {
1341
+ // see comment above in get_state()
1342
+ return
1343
+ }
1344
+ this.sra = state[19]
1345
+ this.srb = state[20]
1346
+ this.dor = state[21]
1347
+ this.tdr = state[22]
1348
+ this.msr = state[23]
1349
+ this.dsr = state[24]
1350
+ this.cmd_phase = state[25]
1351
+ this.cmd_code = state[26]
1352
+ this.cmd_flags = state[27]
1353
+ this.cmd_buffer.set(state[28]) // Uint8Array
1354
+ this.cmd_cursor = state[29]
1355
+ this.cmd_remaining = state[30]
1356
+ this.response_data.set(state[31]) // Uint8Array
1357
+ this.response_cursor = state[32]
1358
+ this.response_length = state[33]
1359
+ this.status0 = state[34]
1360
+ this.status1 = state[35]
1361
+ this.curr_drive_no = state[36]
1362
+ this.reset_sense_int_count = state[37]
1363
+ this.locked = state[38]
1364
+ this.step_rate_interval = state[39]
1365
+ this.head_load_time = state[40]
1366
+ this.fdc_config = state[41]
1367
+ this.precomp_trk = state[42]
1368
+ this.eot = state[43]
1369
+ this.drives[0].set_state(state[44])
1370
+ this.drives[1].set_state(state[45])
1371
+ }
1372
+ }
1373
+
1374
+ // class FloppyDrive ---------------------------------------------------------
1375
+
1376
+ /**
1377
+ * Floppy disk formats
1378
+ *
1379
+ * In many cases, the total sector count (sectors*tracks*heads) of a format
1380
+ * is enough to uniquely identify it. However, there are some total sector
1381
+ * collisions between formats of different physical size (drive_type), these
1382
+ * are highlighed below in the "collides" column.
1383
+ *
1384
+ * Matches that are higher up in the table take precedence over later matches.
1385
+ *
1386
+ * @see {@link https://github.com/qemu/qemu/blob/e240f6cc25917f3138d9e95e0343ae23b63a3f8c/hw/block/fdc.c#L99}
1387
+ * @see {@link https://en.wikipedia.org/wiki/List_of_floppy_disk_formats}
1388
+ */
1389
+
1390
+ interface DiskFormat {
1391
+ drive_type: number
1392
+ sectors: number
1393
+ tracks: number
1394
+ heads: number
1395
+ }
1396
+
1397
+ const DISK_FORMATS: DiskFormat[] = [
1398
+ // ttl_sect size collides
1399
+ // 1.44 MB 3"1/2 floppy disks | | |
1400
+ { drive_type: CMOS_FDD_TYPE_1440, sectors: 18, tracks: 80, heads: 2 }, // 2880 1.44 MB 3.5"
1401
+ { drive_type: CMOS_FDD_TYPE_1440, sectors: 20, tracks: 80, heads: 2 }, // 3200 1.6 MB 3.5"
1402
+ { drive_type: CMOS_FDD_TYPE_1440, sectors: 21, tracks: 80, heads: 2 }, // 3360 1.68 MB
1403
+ { drive_type: CMOS_FDD_TYPE_1440, sectors: 21, tracks: 82, heads: 2 }, // 3444 1.72 MB
1404
+ { drive_type: CMOS_FDD_TYPE_1440, sectors: 21, tracks: 83, heads: 2 }, // 3486 1.74 MB
1405
+ { drive_type: CMOS_FDD_TYPE_1440, sectors: 22, tracks: 80, heads: 2 }, // 3520 1.76 MB
1406
+ { drive_type: CMOS_FDD_TYPE_1440, sectors: 23, tracks: 80, heads: 2 }, // 3680 1.84 MB
1407
+ { drive_type: CMOS_FDD_TYPE_1440, sectors: 24, tracks: 80, heads: 2 }, // 3840 1.92 MB
1408
+ // 2.88 MB 3"1/2 floppy disks
1409
+ { drive_type: CMOS_FDD_TYPE_2880, sectors: 36, tracks: 80, heads: 2 }, // 5760 2.88 MB
1410
+ { drive_type: CMOS_FDD_TYPE_2880, sectors: 39, tracks: 80, heads: 2 }, // 6240 3.12 MB
1411
+ { drive_type: CMOS_FDD_TYPE_2880, sectors: 40, tracks: 80, heads: 2 }, // 6400 3.2 MB
1412
+ { drive_type: CMOS_FDD_TYPE_2880, sectors: 44, tracks: 80, heads: 2 }, // 7040 3.52 MB
1413
+ { drive_type: CMOS_FDD_TYPE_2880, sectors: 48, tracks: 80, heads: 2 }, // 7680 3.84 MB
1414
+ // 720 kB 3"1/2 floppy disks
1415
+ { drive_type: CMOS_FDD_TYPE_1440, sectors: 8, tracks: 80, heads: 2 }, // 1280 640 kB
1416
+ { drive_type: CMOS_FDD_TYPE_1440, sectors: 9, tracks: 80, heads: 2 }, // 1440 720 kB 3.5"
1417
+ { drive_type: CMOS_FDD_TYPE_1440, sectors: 10, tracks: 80, heads: 2 }, // 1600 800 kB
1418
+ { drive_type: CMOS_FDD_TYPE_1440, sectors: 10, tracks: 82, heads: 2 }, // 1640 820 kB
1419
+ { drive_type: CMOS_FDD_TYPE_1440, sectors: 10, tracks: 83, heads: 2 }, // 1660 830 kB
1420
+ { drive_type: CMOS_FDD_TYPE_1440, sectors: 13, tracks: 80, heads: 2 }, // 2080 1.04 MB
1421
+ { drive_type: CMOS_FDD_TYPE_1440, sectors: 14, tracks: 80, heads: 2 }, // 2240 1.12 MB
1422
+ // 1.2 MB 5"1/4 floppy disks
1423
+ { drive_type: CMOS_FDD_TYPE_1200, sectors: 15, tracks: 80, heads: 2 }, // 2400 1.2 MB
1424
+ { drive_type: CMOS_FDD_TYPE_1200, sectors: 18, tracks: 80, heads: 2 }, // 2880 1.44 MB 5.25"
1425
+ { drive_type: CMOS_FDD_TYPE_1200, sectors: 18, tracks: 82, heads: 2 }, // 2952 1.48 MB
1426
+ { drive_type: CMOS_FDD_TYPE_1200, sectors: 18, tracks: 83, heads: 2 }, // 2988 1.49 MB
1427
+ { drive_type: CMOS_FDD_TYPE_1200, sectors: 20, tracks: 80, heads: 2 }, // 3200 1.6 MB 5.25"
1428
+ // 720 kB 5"1/4 floppy disks
1429
+ { drive_type: CMOS_FDD_TYPE_1200, sectors: 9, tracks: 80, heads: 2 }, // 1440 720 kB 5.25"
1430
+ { drive_type: CMOS_FDD_TYPE_1200, sectors: 11, tracks: 80, heads: 2 }, // 1760 880 kB
1431
+ // 360 kB 5"1/4 floppy disks
1432
+ { drive_type: CMOS_FDD_TYPE_1200, sectors: 9, tracks: 40, heads: 2 }, // 720 360 kB 5.25"
1433
+ { drive_type: CMOS_FDD_TYPE_1200, sectors: 9, tracks: 40, heads: 1 }, // 360 180 kB
1434
+ { drive_type: CMOS_FDD_TYPE_1200, sectors: 10, tracks: 41, heads: 2 }, // 820 410 kB
1435
+ { drive_type: CMOS_FDD_TYPE_1200, sectors: 10, tracks: 42, heads: 2 }, // 840 420 kB
1436
+ // 320 kB 5"1/4 floppy disks
1437
+ { drive_type: CMOS_FDD_TYPE_1200, sectors: 8, tracks: 40, heads: 2 }, // 640 320 kB
1438
+ { drive_type: CMOS_FDD_TYPE_1200, sectors: 8, tracks: 40, heads: 1 }, // 320 160 kB
1439
+ // 360 kB must match 5"1/4 better than 3"1/2...
1440
+ { drive_type: CMOS_FDD_TYPE_1440, sectors: 9, tracks: 80, heads: 1 }, // 720 360 kB 3.5"
1441
+ // types defined in earlier v86 releases (not defined in qemu)
1442
+ { drive_type: CMOS_FDD_TYPE_360, sectors: 10, tracks: 40, heads: 1 }, // 400 200 kB
1443
+ { drive_type: CMOS_FDD_TYPE_360, sectors: 10, tracks: 40, heads: 2 }, // 800 400 kB
1444
+ ]
1445
+
1446
+ class FloppyDrive {
1447
+ name: string
1448
+
1449
+ // drive state
1450
+ drive_type: number
1451
+
1452
+ // disk state
1453
+ max_track: number // disk's max. track
1454
+ max_head: number // disk's max. head (1 or 2)
1455
+ max_sect: number // disk's max. sect
1456
+ curr_track: number // >= 0
1457
+ curr_head: number // 0 or 1
1458
+ curr_sect: number // > 0
1459
+ perpendicular: number
1460
+ read_only: boolean
1461
+ media_changed: boolean
1462
+ buffer: SyncBuffer | null
1463
+
1464
+ constructor(
1465
+ name: string,
1466
+ fdd_config: FloppyDriveConfig | undefined,
1467
+ buffer: SyncBuffer | Uint8Array | null | undefined,
1468
+ fallback_drive_type: number,
1469
+ ) {
1470
+ this.name = name
1471
+
1472
+ // drive state
1473
+ this.drive_type = CMOS_FDD_TYPE_NO_DRIVE
1474
+
1475
+ // disk state
1476
+ this.max_track = 0
1477
+ this.max_head = 0
1478
+ this.max_sect = 0
1479
+ this.curr_track = 0
1480
+ this.curr_head = 0
1481
+ this.curr_sect = 1
1482
+ this.perpendicular = 0
1483
+ this.read_only = false
1484
+ this.media_changed = true
1485
+ this.buffer = null
1486
+
1487
+ // Drive type this.drive_type is either (in this order):
1488
+ // - specified in fdd_config.drive_type (if defined),
1489
+ // - derived from given image buffer (if provided), or
1490
+ // - specfied in fallback_drive_type.
1491
+ // If buffer is undefined and fdd_config.drive_type is
1492
+ // CMOS_FDD_TYPE_NO_DRIVE then the drive will be invisible to the guest.
1493
+ const cfg_drive_type = fdd_config?.drive_type
1494
+ if (
1495
+ cfg_drive_type !== undefined &&
1496
+ cfg_drive_type >= 0 &&
1497
+ cfg_drive_type <= 5
1498
+ ) {
1499
+ this.drive_type = cfg_drive_type
1500
+ }
1501
+
1502
+ this.insert_disk(buffer, !!fdd_config?.read_only)
1503
+
1504
+ if (
1505
+ this.drive_type === CMOS_FDD_TYPE_NO_DRIVE &&
1506
+ cfg_drive_type === undefined
1507
+ ) {
1508
+ this.drive_type = fallback_drive_type
1509
+ }
1510
+
1511
+ dbg_log(
1512
+ 'floppy drive ' +
1513
+ this.name +
1514
+ ' ready, drive type: ' +
1515
+ this.drive_type,
1516
+ LOG_FLOPPY,
1517
+ )
1518
+ }
1519
+
1520
+ /**
1521
+ * Insert disk image into floppy drive.
1522
+ */
1523
+ insert_disk(
1524
+ buffer: SyncBuffer | Uint8Array | null | undefined,
1525
+ read_only?: boolean,
1526
+ ): boolean {
1527
+ if (!buffer) {
1528
+ return false
1529
+ }
1530
+
1531
+ if (buffer instanceof Uint8Array) {
1532
+ const ab = new ArrayBuffer(buffer.byteLength)
1533
+ new Uint8Array(ab).set(buffer)
1534
+ buffer = new SyncBuffer(ab)
1535
+ }
1536
+
1537
+ const [new_buffer, disk_format] = this.find_disk_format(
1538
+ buffer,
1539
+ this.drive_type,
1540
+ )
1541
+ if (!new_buffer) {
1542
+ dbg_log(
1543
+ 'WARNING: disk rejected, no suitable disk format found for image of size ' +
1544
+ buffer.byteLength +
1545
+ ' bytes',
1546
+ LOG_FLOPPY,
1547
+ )
1548
+ return false
1549
+ }
1550
+
1551
+ this.max_track = disk_format!.tracks
1552
+ this.max_head = disk_format!.heads
1553
+ this.max_sect = disk_format!.sectors
1554
+ this.read_only = !!read_only
1555
+ this.media_changed = true
1556
+ this.buffer = new_buffer
1557
+
1558
+ if (this.drive_type === CMOS_FDD_TYPE_NO_DRIVE) {
1559
+ // auto-select drive type once during construction
1560
+ this.drive_type = disk_format!.drive_type
1561
+ }
1562
+
1563
+ if (DEBUG) {
1564
+ dbg_log(
1565
+ 'disk inserted into ' +
1566
+ this.name +
1567
+ ': type: ' +
1568
+ disk_format!.drive_type +
1569
+ ', C/H/S: ' +
1570
+ disk_format!.tracks +
1571
+ '/' +
1572
+ disk_format!.heads +
1573
+ '/' +
1574
+ disk_format!.sectors +
1575
+ ', size: ' +
1576
+ new_buffer.byteLength,
1577
+ LOG_FLOPPY,
1578
+ )
1579
+ }
1580
+ return true
1581
+ }
1582
+
1583
+ /**
1584
+ * Eject disk from this floppy drive.
1585
+ */
1586
+ eject_disk(): void {
1587
+ this.max_track = 0
1588
+ this.max_head = 0
1589
+ this.max_sect = 0
1590
+ this.media_changed = true
1591
+ this.buffer = null
1592
+ }
1593
+
1594
+ /**
1595
+ * Returns this drive's current image buffer or null if no disk is inserted.
1596
+ */
1597
+ get_buffer(): Uint8Array | null {
1598
+ return this.buffer ? new Uint8Array(this.buffer.buffer) : null
1599
+ }
1600
+
1601
+ /**
1602
+ * Map structured C/H/S address to linear block address (LBA).
1603
+ */
1604
+ chs2lba(track: number, head: number, sect: number): number {
1605
+ return (track * this.max_head + head) * this.max_sect + sect - 1
1606
+ }
1607
+
1608
+ /**
1609
+ * Find best-matching disk format for the given image buffer.
1610
+ *
1611
+ * If the given drive_type is CMOS_FDD_TYPE_NO_DRIVE then drive types are
1612
+ * ignored and the first matching disk format is selected (auto-detect).
1613
+ *
1614
+ * If the size of the given buffer does not match any known floppy disk
1615
+ * format then the smallest format larger than the image buffer is
1616
+ * selected, and buffer is copied into a new, format-sized buffer with
1617
+ * trailing zeroes.
1618
+ *
1619
+ * Returns [null, null] if the given buffer is larger than any known
1620
+ * floppy disk format.
1621
+ */
1622
+ find_disk_format(
1623
+ buffer: SyncBuffer,
1624
+ drive_type: number,
1625
+ ): [SyncBuffer | null, DiskFormat | null] {
1626
+ const autodetect = drive_type === CMOS_FDD_TYPE_NO_DRIVE
1627
+ const buffer_size = buffer.byteLength
1628
+
1629
+ let preferred_match = -1,
1630
+ medium_match = -1,
1631
+ size_match = -1,
1632
+ nearest_match = -1,
1633
+ nearest_size = -1
1634
+ for (let i = 0; i < DISK_FORMATS.length; i++) {
1635
+ const disk_format = DISK_FORMATS[i]
1636
+ const disk_size =
1637
+ disk_format.sectors *
1638
+ disk_format.tracks *
1639
+ disk_format.heads *
1640
+ SECTOR_SIZE
1641
+ if (buffer_size === disk_size) {
1642
+ if (autodetect || disk_format.drive_type === drive_type) {
1643
+ // (1) same size and CMOS drive type
1644
+ preferred_match = i
1645
+ break
1646
+ } else if (
1647
+ !autodetect &&
1648
+ CMOS_FDD_TYPE_MEDIUM[disk_format.drive_type] ===
1649
+ CMOS_FDD_TYPE_MEDIUM[drive_type]
1650
+ ) {
1651
+ // (2) same size and physical medium size (5"1/4 or 3"1/2)
1652
+ medium_match = medium_match === -1 ? i : medium_match
1653
+ } else {
1654
+ // (3) same size
1655
+ size_match = size_match === -1 ? i : size_match
1656
+ }
1657
+ } else if (buffer_size < disk_size) {
1658
+ if (nearest_size === -1 || disk_size < nearest_size) {
1659
+ // (4) nearest matching size
1660
+ nearest_match = i
1661
+ nearest_size = disk_size
1662
+ }
1663
+ }
1664
+ }
1665
+
1666
+ if (preferred_match !== -1) {
1667
+ return [buffer, DISK_FORMATS[preferred_match]]
1668
+ } else if (medium_match !== -1) {
1669
+ return [buffer, DISK_FORMATS[medium_match]]
1670
+ } else if (size_match !== -1) {
1671
+ return [buffer, DISK_FORMATS[size_match]]
1672
+ } else if (nearest_match !== -1) {
1673
+ const tmp_buffer = new Uint8Array(nearest_size)
1674
+ tmp_buffer.set(new Uint8Array(buffer.buffer))
1675
+ return [
1676
+ new SyncBuffer(tmp_buffer.buffer),
1677
+ DISK_FORMATS[nearest_match],
1678
+ ]
1679
+ } else {
1680
+ return [null, null]
1681
+ }
1682
+ }
1683
+
1684
+ /**
1685
+ * Seek to a new position, returns:
1686
+ * 0 if already on right track
1687
+ * 1 if track changed
1688
+ * 2 if track is invalid
1689
+ * 3 if sector is invalid
1690
+ */
1691
+ seek(head: number, track: number, sect: number): number {
1692
+ if (track > this.max_track || (head !== 0 && this.max_head === 1)) {
1693
+ dbg_log(
1694
+ 'WARNING: attempt to seek to invalid track: head: ' +
1695
+ head +
1696
+ ', track: ' +
1697
+ track +
1698
+ ', sect: ' +
1699
+ sect,
1700
+ LOG_FLOPPY,
1701
+ )
1702
+ return 2
1703
+ }
1704
+ if (sect > this.max_sect) {
1705
+ dbg_log(
1706
+ 'WARNING: attempt to seek beyond last sector: ' +
1707
+ sect +
1708
+ ' (max: ' +
1709
+ this.max_sect +
1710
+ ')',
1711
+ LOG_FLOPPY,
1712
+ )
1713
+ return 3
1714
+ }
1715
+
1716
+ let result = 0
1717
+ const curr_lba = this.chs2lba(
1718
+ this.curr_track,
1719
+ this.curr_head,
1720
+ this.curr_sect,
1721
+ )
1722
+ const new_lba = this.chs2lba(track, head, sect)
1723
+ if (curr_lba !== new_lba) {
1724
+ if (this.curr_track !== track) {
1725
+ if (this.buffer) {
1726
+ this.media_changed = false
1727
+ }
1728
+ result = 1
1729
+ }
1730
+ this.curr_head = head
1731
+ this.curr_track = track
1732
+ this.curr_sect = sect
1733
+ }
1734
+
1735
+ if (!this.buffer) {
1736
+ result = 2
1737
+ }
1738
+
1739
+ return result
1740
+ }
1741
+
1742
+ get_state(): (number | boolean | [number, Uint8Array] | null)[] {
1743
+ const state: (number | boolean | [number, Uint8Array] | null)[] = []
1744
+ state[0] = this.drive_type
1745
+ state[1] = this.max_track
1746
+ state[2] = this.max_head
1747
+ state[3] = this.max_sect
1748
+ state[4] = this.curr_track
1749
+ state[5] = this.curr_head
1750
+ state[6] = this.curr_sect
1751
+ state[7] = this.perpendicular
1752
+ state[8] = this.read_only
1753
+ state[9] = this.media_changed
1754
+ state[10] = this.buffer ? this.buffer.get_state() : null // SyncBuffer
1755
+ return state
1756
+ }
1757
+
1758
+ set_state(state: any[]): void {
1759
+ this.drive_type = state[0]
1760
+ this.max_track = state[1]
1761
+ this.max_head = state[2]
1762
+ this.max_sect = state[3]
1763
+ this.curr_track = state[4]
1764
+ this.curr_head = state[5]
1765
+ this.curr_sect = state[6]
1766
+ this.perpendicular = state[7]
1767
+ this.read_only = state[8]
1768
+ this.media_changed = state[9]
1769
+ if (state[10]) {
1770
+ if (!this.buffer) {
1771
+ this.buffer = new SyncBuffer(new ArrayBuffer(0))
1772
+ }
1773
+ this.buffer.set_state(state[10])
1774
+ } else {
1775
+ this.buffer = null
1776
+ }
1777
+ }
1778
+ }