@aptre/v86 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/LICENSE.MIT +22 -0
- package/Readme.md +237 -0
- package/dist/v86.browser.js +26666 -0
- package/dist/v86.browser.js.map +7 -0
- package/dist/v86.js +26632 -0
- package/dist/v86.js.map +7 -0
- package/gen/generate_analyzer.ts +512 -0
- package/gen/generate_interpreter.ts +522 -0
- package/gen/generate_jit.ts +624 -0
- package/gen/rust_ast.ts +107 -0
- package/gen/util.ts +35 -0
- package/gen/x86_table.ts +1836 -0
- package/lib/9p.ts +1547 -0
- package/lib/filesystem.ts +1879 -0
- package/lib/marshall.ts +168 -0
- package/lib/softfloat/softfloat.c +32501 -0
- package/lib/zstd/zstddeclib.c +13520 -0
- package/package.json +75 -0
- package/src/acpi.ts +267 -0
- package/src/browser/dummy_screen.ts +106 -0
- package/src/browser/fake_network.ts +1771 -0
- package/src/browser/fetch_network.ts +361 -0
- package/src/browser/filestorage.ts +124 -0
- package/src/browser/inbrowser_network.ts +57 -0
- package/src/browser/keyboard.ts +564 -0
- package/src/browser/main.ts +3415 -0
- package/src/browser/mouse.ts +255 -0
- package/src/browser/network.ts +142 -0
- package/src/browser/print_stats.ts +336 -0
- package/src/browser/screen.ts +978 -0
- package/src/browser/serial.ts +316 -0
- package/src/browser/speaker.ts +1223 -0
- package/src/browser/starter.ts +1688 -0
- package/src/browser/wisp_network.ts +332 -0
- package/src/browser/worker_bus.ts +64 -0
- package/src/buffer.ts +652 -0
- package/src/bus.ts +78 -0
- package/src/const.ts +128 -0
- package/src/cpu.ts +2891 -0
- package/src/dma.ts +474 -0
- package/src/elf.ts +251 -0
- package/src/floppy.ts +1778 -0
- package/src/ide.ts +3455 -0
- package/src/io.ts +504 -0
- package/src/iso9660.ts +317 -0
- package/src/kernel.ts +250 -0
- package/src/lib.ts +645 -0
- package/src/log.ts +149 -0
- package/src/main.ts +199 -0
- package/src/ne2k.ts +1589 -0
- package/src/pci.ts +815 -0
- package/src/pit.ts +406 -0
- package/src/ps2.ts +820 -0
- package/src/rtc.ts +537 -0
- package/src/rust/analysis.rs +101 -0
- package/src/rust/codegen.rs +2660 -0
- package/src/rust/config.rs +3 -0
- package/src/rust/control_flow.rs +425 -0
- package/src/rust/cpu/apic.rs +658 -0
- package/src/rust/cpu/arith.rs +1207 -0
- package/src/rust/cpu/call_indirect.rs +2 -0
- package/src/rust/cpu/cpu.rs +4501 -0
- package/src/rust/cpu/fpu.rs +923 -0
- package/src/rust/cpu/global_pointers.rs +112 -0
- package/src/rust/cpu/instructions.rs +2486 -0
- package/src/rust/cpu/instructions_0f.rs +5261 -0
- package/src/rust/cpu/ioapic.rs +316 -0
- package/src/rust/cpu/memory.rs +351 -0
- package/src/rust/cpu/misc_instr.rs +613 -0
- package/src/rust/cpu/mod.rs +16 -0
- package/src/rust/cpu/modrm.rs +133 -0
- package/src/rust/cpu/pic.rs +402 -0
- package/src/rust/cpu/sse_instr.rs +361 -0
- package/src/rust/cpu/string.rs +701 -0
- package/src/rust/cpu/vga.rs +175 -0
- package/src/rust/cpu_context.rs +69 -0
- package/src/rust/dbg.rs +98 -0
- package/src/rust/gen/analyzer.rs +3807 -0
- package/src/rust/gen/analyzer0f.rs +3992 -0
- package/src/rust/gen/interpreter.rs +4447 -0
- package/src/rust/gen/interpreter0f.rs +5404 -0
- package/src/rust/gen/jit.rs +5080 -0
- package/src/rust/gen/jit0f.rs +5547 -0
- package/src/rust/gen/mod.rs +14 -0
- package/src/rust/jit.rs +2443 -0
- package/src/rust/jit_instructions.rs +7881 -0
- package/src/rust/js_api.rs +6 -0
- package/src/rust/leb.rs +46 -0
- package/src/rust/lib.rs +29 -0
- package/src/rust/modrm.rs +330 -0
- package/src/rust/opstats.rs +249 -0
- package/src/rust/page.rs +15 -0
- package/src/rust/paging.rs +25 -0
- package/src/rust/prefix.rs +15 -0
- package/src/rust/profiler.rs +155 -0
- package/src/rust/regs.rs +38 -0
- package/src/rust/softfloat.rs +286 -0
- package/src/rust/state_flags.rs +27 -0
- package/src/rust/wasmgen/mod.rs +2 -0
- package/src/rust/wasmgen/wasm_builder.rs +1047 -0
- package/src/rust/wasmgen/wasm_opcodes.rs +221 -0
- package/src/rust/zstd.rs +105 -0
- package/src/sb16.ts +1928 -0
- package/src/state.ts +359 -0
- package/src/uart.ts +472 -0
- package/src/vga.ts +2791 -0
- package/src/virtio.ts +1756 -0
- package/src/virtio_balloon.ts +273 -0
- package/src/virtio_console.ts +372 -0
- package/src/virtio_net.ts +326 -0
package/src/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
|
+
}
|