1#![forbid(unsafe_code)]
10
11use arrayvec::ArrayVec;
12use bitfield_struct::bitfield;
13use chipset_device::ChipsetDevice;
14use chipset_device::io::IoError;
15use chipset_device::io::IoResult;
16use chipset_device::pio::ControlPortIoIntercept;
17use chipset_device::pio::PortIoIntercept;
18use chipset_device::pio::RegisterPortIoIntercept;
19use chipset_device::poll_device::PollDevice;
20use inspect::Inspect;
21use inspect::InspectMut;
22use open_enum::open_enum;
23use vmcore::device_state::ChangeDeviceState;
24use vmcore::line_interrupt::LineInterrupt;
25
26const FIFO_SIZE: usize = 16;
27const INVALID_COMMAND_STATUS: u8 = 0x80;
28const FLOPPY_DSR_DISK_RESET_MASK: u8 = 0x80;
29const ENHANCED_CONTROLLER_VERSION: u8 = 0x90;
30const FLOPPY_STATUS0_MASK: u8 = 0xC0;
31const FLOPPY_STATUS0_SEEK_END: u8 = 0x20;
32const NO_TAPE_DRIVES_PRESENT: u8 = 0xFC;
33
34open_enum! {
35 #[derive(Default)]
36 enum RegisterOffset: u16 {
37 STATUS_A = 0, STATUS_B = 1, DIGITAL_OUTPUT = 2,
40 TAPE_DRIVE = 3, MAIN_STATUS = 4, DATA_RATE = 4, DATA = 5,
44 DIGITAL_INPUT = 7,CONFIG_CONTROL = 7, }
47}
48
49#[derive(Inspect)]
51#[bitfield(u8)]
52pub struct DigitalOutput {
53 #[bits(2)]
54 _drive_select: u8,
55 controller_enabled: bool,
56 dma_enabled: bool,
57 #[bits(4)]
60 motors_active: u8,
61}
62
63#[derive(Inspect)]
65#[bitfield(u8)]
66pub struct MainStatus {
67 #[bits(4)]
70 active_drives: u8,
71 busy: bool,
73 _non_dma_mode: bool,
74 data_direction: bool,
77 main_request: bool,
80}
81
82open_enum! {
83 #[derive(Inspect)]
84 #[inspect(debug)]
85 enum FloppyCommand: u8 {
86 SPECIFY = 0x3,
87 SENSE_DRIVE_STATUS = 0x4,
88 RECALIBRATE = 0x7,
89 SENSE_INTERRUPT_STATUS = 0x8,
90 DUMP_REGISTERS = 0xE,
91 SEEK = 0xF,
92 VERSION = 0x10,
93 PERP288_MODE = 0x12,
94 CONFIGURE = 0x13,
95 UNLOCK_FIFO_FUNCTIONS = 0x14,
96 PART_ID = 0x18,
97 LOCK_FIFO_FUNCTIONS = 0x94,
98 }
99}
100
101impl FloppyCommand {
102 fn input_bytes_needed(&self) -> usize {
106 1 + match *self {
108 Self::SPECIFY => 2,
109 Self::SENSE_DRIVE_STATUS => 1,
110 Self::RECALIBRATE => 1,
111 Self::SENSE_INTERRUPT_STATUS => 0,
112 Self::DUMP_REGISTERS => 0,
113 Self::SEEK => 2,
114 Self::VERSION => 0,
115 Self::PERP288_MODE => 1,
116 Self::CONFIGURE => 3,
117 Self::UNLOCK_FIFO_FUNCTIONS => 0,
118 Self::PART_ID => 0,
119 Self::LOCK_FIFO_FUNCTIONS => 0,
120 _ => 0,
121 }
122 }
123}
124
125impl ChangeDeviceState for StubFloppyDiskController {
126 fn start(&mut self) {}
127
128 async fn stop(&mut self) {}
129
130 async fn reset(&mut self) {
131 self.reset(false);
132 }
133}
134
135impl ChipsetDevice for StubFloppyDiskController {
136 fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
137 Some(self)
138 }
139
140 fn supports_poll_device(&mut self) -> Option<&mut dyn PollDevice> {
141 Some(self)
142 }
143}
144
145impl PollDevice for StubFloppyDiskController {
148 fn poll_device(&mut self, _cx: &mut std::task::Context<'_>) {}
149}
150
151impl PortIoIntercept for StubFloppyDiskController {
152 fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
153 if data.len() != 1 {
154 return IoResult::Err(IoError::InvalidAccessSize);
155 }
156
157 let mut io_result = IoResult::Ok;
158 let offset = RegisterOffset(io_port % 0x10);
159
160 data[0] = match offset {
161 RegisterOffset::STATUS_A => 0xFF,
163 RegisterOffset::STATUS_B => NO_TAPE_DRIVES_PRESENT,
165 RegisterOffset::TAPE_DRIVE => 0xFF,
167 RegisterOffset::DIGITAL_OUTPUT => self.state.digital_output.0,
169 RegisterOffset::MAIN_STATUS => {
170 if self.state.digital_output.controller_enabled() {
172 self.state.main_status.0
173 } else {
174 0
175 }
176 }
177 RegisterOffset::DATA => {
178 if let Some(result) = self.state.output_bytes.pop() {
180 self.state.main_status.set_active_drives(0);
181 if self.state.output_bytes.is_empty() {
182 self.state.main_status.set_data_direction(false);
184 self.state.main_status.set_busy(false);
185 }
186 result
187 } else {
188 INVALID_COMMAND_STATUS
189 }
190 }
191 RegisterOffset::DIGITAL_INPUT => {
192 if self.state.digital_output.motors_active() != 0 {
195 0xff
196 } else {
197 0x7f
198 }
199 }
200 _ => {
201 io_result = IoResult::Err(IoError::InvalidRegister);
202 0
203 }
204 };
205
206 tracing::trace!(?io_port, ?offset, ?data, "io port read");
207 io_result
208 }
209
210 fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
211 if data.len() != 1 {
212 return IoResult::Err(IoError::InvalidAccessSize);
213 }
214
215 let data = data[0];
216 let offset = RegisterOffset(io_port % 0x10);
217 tracing::trace!(?io_port, ?offset, ?data, "io port write");
218
219 match offset {
220 RegisterOffset::STATUS_A | RegisterOffset::STATUS_B => {
221 tracelimit::warn_ratelimited!(?offset, "write to read-only floppy status register");
222 }
223 RegisterOffset::TAPE_DRIVE => {} RegisterOffset::CONFIG_CONTROL => {} RegisterOffset::DATA_RATE => {
226 if self.state.digital_output.controller_enabled()
227 && (data & FLOPPY_DSR_DISK_RESET_MASK) != 0
228 {
229 self.reset(true);
230 self.state.sense_output = Some(SenseOutput::ResetCounter { count: 4 });
231 self.raise_interrupt(true);
233 tracing::trace!("Un-resetting - asserting floppy interrupt");
234 }
235 }
236 RegisterOffset::DIGITAL_OUTPUT => {
237 let new_digital_output = DigitalOutput::from(data);
238 let was_reset = !self.state.digital_output.controller_enabled();
239 let is_reset = !new_digital_output.controller_enabled();
240 let interrupts_were_enabled = self.state.digital_output.dma_enabled();
241 let interrupts_enabled = new_digital_output.dma_enabled();
242 self.state.digital_output = new_digital_output;
243
244 if was_reset && !is_reset {
245 tracing::trace!("un-resetting - asserting floppy interrupt");
246 self.state.sense_output = Some(SenseOutput::ResetCounter { count: 4 });
247 self.raise_interrupt(true);
249 } else if is_reset {
250 self.reset(true);
251 } else {
252 if !interrupts_were_enabled && interrupts_enabled {
253 tracing::trace!("Re-enabling floppy interrupts");
254 self.raise_interrupt(false);
255 } else if interrupts_were_enabled && !interrupts_enabled {
256 tracing::trace!("Disabling floppy interrupts");
257 self.lower_interrupt();
258 }
259 }
260 }
261 RegisterOffset::DATA => {
262 if !self.state.digital_output.controller_enabled() {
263 return IoResult::Ok;
265 }
266
267 tracing::trace!(
268 ?data,
269 ?self.state.input_bytes,
270 "floppy command byte"
271 );
272
273 self.state.output_bytes.clear();
274 self.state.input_bytes.push(data);
275 self.state.main_status.set_busy(true);
276 let command = FloppyCommand(self.state.input_bytes[0]);
277 if self.state.input_bytes.len() < command.input_bytes_needed() {
278 return IoResult::Ok;
279 }
280
281 tracing::trace!(
282 ?command,
283 input_bytes = ?self.state.input_bytes,
284 "executing floppy command"
285 );
286
287 match command {
288 FloppyCommand::SPECIFY => {
289 self.state.scd = [self.state.input_bytes[1], self.state.input_bytes[2]];
293 }
294 FloppyCommand::SENSE_DRIVE_STATUS => {
295 let input_info = self.state.input_bytes[1] & 0b111;
299 let mut result = 0x28 | input_info;
300 if self.state.cur_cylinder == 0 {
301 result |= 0x10;
302 }
303 self.state.output_bytes.push(result);
304
305 if let Some(SenseOutput::Value { ref mut value }) = self.state.sense_output
306 {
307 *value |= FLOPPY_STATUS0_SEEK_END;
308 }
309 }
310 FloppyCommand::RECALIBRATE | FloppyCommand::SEEK => {
311 self.state.cur_cylinder = if matches!(command, FloppyCommand::SEEK) {
312 self.state.input_bytes[2]
313 } else {
314 0
315 };
316 match self.state.sense_output {
320 Some(SenseOutput::Value { ref mut value }) => {
321 *value |= FLOPPY_STATUS0_SEEK_END
322 }
323 _ => {
324 self.state.sense_output = Some(SenseOutput::Value {
325 value: FLOPPY_STATUS0_SEEK_END,
326 })
327 }
328 }
329 self.state.main_status.set_active_drives(
331 self.state.main_status.active_drives()
332 | (1 << (self.state.input_bytes[1] & 0x3)),
333 );
334
335 self.raise_interrupt(false);
336 }
337 FloppyCommand::SENSE_INTERRUPT_STATUS => {
338 self.state.output_bytes.push(self.state.cur_cylinder);
339 match self.state.sense_output {
340 Some(SenseOutput::ResetCounter { ref mut count }) => {
341 self.state
342 .output_bytes
343 .push(FLOPPY_STATUS0_MASK | (4 - *count));
344 *count -= 1;
345 if *count == 0 {
346 self.state.sense_output = None;
347 }
348 }
349 Some(SenseOutput::Value { value }) => {
350 self.state.output_bytes.push(value);
351 self.state.sense_output = None;
352 }
353 None => {
354 self.state.output_bytes.push(INVALID_COMMAND_STATUS);
355 }
356 }
357
358 tracing::trace!(
359 "sense interrupt status cmd - deasserting floppy interrupt"
360 );
361 self.lower_interrupt();
362 }
363 FloppyCommand::DUMP_REGISTERS => {
364 self.state.output_bytes.push(self.state.cur_cylinder);
365 self.state.output_bytes.push(0); self.state.output_bytes.push(0); self.state.output_bytes.push(0); self.state.output_bytes.push(self.state.scd[0]);
369 self.state.output_bytes.push(self.state.scd[1]);
370 self.state.output_bytes.push(0); self.state.output_bytes.push(0); self.state.output_bytes.push(0); self.state.output_bytes.push(0); }
375 FloppyCommand::VERSION => {
376 self.state.output_bytes.push(ENHANCED_CONTROLLER_VERSION);
377 }
378 FloppyCommand::PERP288_MODE => {} FloppyCommand::CONFIGURE => {} FloppyCommand::PART_ID => {
381 self.state.output_bytes.push(0x01);
382 }
383 FloppyCommand::UNLOCK_FIFO_FUNCTIONS => {
386 self.state.output_bytes.push(0);
387 }
388 FloppyCommand::LOCK_FIFO_FUNCTIONS => {
389 self.state.output_bytes.push(0x10);
390 }
391 _ => {
392 tracing::debug!(?command, "unimplemented/unsupported command");
393 self.state.output_bytes.push(INVALID_COMMAND_STATUS);
394 }
395 }
396
397 self.state.input_bytes.clear();
398
399 if self.state.output_bytes.is_empty() {
400 self.state.main_status.set_busy(false);
401 } else {
402 self.state.main_status.set_data_direction(true);
404 }
405
406 }
409 _ => return IoResult::Err(IoError::InvalidRegister),
410 }
411
412 IoResult::Ok
413 }
414}
415
416#[derive(Clone, Inspect)]
417struct FloppyState {
418 digital_output: DigitalOutput,
419 main_status: MainStatus,
420
421 #[inspect(bytes)]
423 input_bytes: ArrayVec<u8, FIFO_SIZE>,
424
425 #[inspect(bytes)]
427 output_bytes: ArrayVec<u8, FIFO_SIZE>,
428
429 scd: [u8; 2],
430
431 sense_output: Option<SenseOutput>,
432
433 cur_cylinder: u8,
437
438 interrupt_level: bool,
440}
441
442#[derive(Clone, Inspect)]
443#[inspect(external_tag)]
444enum SenseOutput {
445 ResetCounter { count: u8 },
446 Value { value: u8 },
447}
448
449impl FloppyState {
450 fn new() -> Self {
451 Self {
452 digital_output: DigitalOutput::new(),
453 main_status: MainStatus::new(),
454 cur_cylinder: 0,
455 input_bytes: ArrayVec::new(),
456 output_bytes: ArrayVec::new(),
457 scd: [0; 2],
458 sense_output: None,
459 interrupt_level: false,
460 }
461 }
462}
463
464#[derive(Inspect)]
465struct FloppyRt {
466 interrupt: LineInterrupt,
467 pio_base: Box<dyn ControlPortIoIntercept>,
468 pio_control: Box<dyn ControlPortIoIntercept>,
469}
470
471#[derive(InspectMut)]
473pub struct StubFloppyDiskController {
474 rt: FloppyRt,
476
477 state: FloppyState,
479}
480
481impl StubFloppyDiskController {
482 pub fn new(
484 interrupt: LineInterrupt,
485 register_pio: &mut dyn RegisterPortIoIntercept,
486 pio_base_addr: u16,
487 ) -> Self {
488 let mut pio_base = register_pio.new_io_region("floppy base", 6);
489 let mut pio_control = register_pio.new_io_region("floppy control", 1);
490
491 pio_base.map(pio_base_addr);
492 pio_control.map(pio_base_addr + RegisterOffset::DIGITAL_INPUT.0);
495
496 Self {
497 rt: FloppyRt {
498 interrupt,
499 pio_base,
500 pio_control,
501 },
502 state: FloppyState::new(),
503 }
504 }
505
506 pub fn offset_of(&self, addr: u16) -> Option<u16> {
511 self.rt.pio_base.offset_of(addr).or_else(|| {
512 self.rt
513 .pio_control
514 .offset_of(addr)
515 .map(|_| RegisterOffset::DIGITAL_INPUT.0)
516 })
517 }
518
519 fn raise_interrupt(&mut self, is_reset: bool) {
520 if self.state.digital_output.dma_enabled() || is_reset {
521 self.rt.interrupt.set_level(true);
522 self.state.interrupt_level = true;
523 }
524 }
525
526 fn lower_interrupt(&mut self) {
527 self.rt.interrupt.set_level(false);
528 self.state.interrupt_level = false;
529 }
530
531 fn reset(&mut self, preserve_digital_output: bool) {
532 self.lower_interrupt();
533 self.state = FloppyState {
534 digital_output: if preserve_digital_output {
535 self.state.digital_output
536 } else {
537 DigitalOutput::new()
538 },
539 ..FloppyState::new()
540 };
541
542 self.state.main_status.set_main_request(true);
546
547 tracing::trace!(
548 preserve_digital_output,
549 "controller reset - deasserting floppy interrupt"
550 );
551 }
552}
553
554mod save_restore {
555 use super::*;
556 use vmcore::save_restore::RestoreError;
557 use vmcore::save_restore::SaveError;
558 use vmcore::save_restore::SaveRestore;
559
560 mod state {
561 use mesh::payload::Protobuf;
562 use vmcore::save_restore::SavedStateRoot;
563
564 #[derive(Protobuf, SavedStateRoot)]
565 #[mesh(package = "chipset.floppy")]
566 pub struct SavedState {
567 #[mesh(1)]
568 pub digital_output: u8,
569 #[mesh(2)]
570 pub main_status: u8,
571 #[mesh(3)]
572 pub input_bytes: Vec<u8>,
573 #[mesh(4)]
574 pub output_bytes: Vec<u8>,
575 #[mesh(5)]
576 pub scd: [u8; 2],
577 #[mesh(6)]
578 pub interrupt_output: Option<SavedInterruptOutput>,
579 #[mesh(7)]
580 pub interrupt_level: bool,
581 #[mesh(8)]
584 pub cur_drive: u8,
585 #[mesh(9)]
587 pub floppies: [SavedFloppyState; 4],
588 }
589
590 #[derive(Protobuf, Default)]
591 #[mesh(package = "chipset.floppy")]
592 pub struct SavedFloppyState {
593 #[mesh(1)]
594 pub cur_cylinder: u8,
595 #[mesh(2)]
596 pub cur_head: u8,
597 #[mesh(3)]
598 pub cur_sector: u8,
599 }
600
601 #[derive(Protobuf)]
602 #[mesh(package = "chipset.floppy")]
603 pub enum SavedInterruptOutput {
604 #[mesh(1)]
605 ResetCounter {
606 #[mesh(1)]
607 count: u8,
608 },
609 #[mesh(2)]
610 Value {
611 #[mesh(1)]
612 value: u8,
613 },
614 }
615
616 impl From<SavedInterruptOutput> for super::SenseOutput {
617 fn from(value: SavedInterruptOutput) -> Self {
618 match value {
619 SavedInterruptOutput::ResetCounter { count } => {
620 super::SenseOutput::ResetCounter { count }
621 }
622 SavedInterruptOutput::Value { value } => super::SenseOutput::Value { value },
623 }
624 }
625 }
626
627 impl From<super::SenseOutput> for SavedInterruptOutput {
628 fn from(value: super::SenseOutput) -> Self {
629 match value {
630 super::SenseOutput::ResetCounter { count } => {
631 SavedInterruptOutput::ResetCounter { count }
632 }
633 super::SenseOutput::Value { value } => SavedInterruptOutput::Value { value },
634 }
635 }
636 }
637 }
638
639 impl SaveRestore for StubFloppyDiskController {
640 type SavedState = state::SavedState;
641
642 fn save(&mut self) -> Result<Self::SavedState, SaveError> {
643 let FloppyState {
644 digital_output,
645 main_status,
646 ref input_bytes,
647 ref output_bytes,
648 scd,
649 sense_output: ref interrupt_output,
650 interrupt_level,
651 cur_cylinder,
652 } = self.state;
653
654 let saved_state = state::SavedState {
655 digital_output: digital_output.into(),
656 main_status: main_status.into(),
657 input_bytes: input_bytes.to_vec(),
658 output_bytes: output_bytes.to_vec(),
659 scd,
660 interrupt_output: interrupt_output.clone().map(|x| x.into()),
661 interrupt_level,
662 cur_drive: 0,
663 floppies: [
664 state::SavedFloppyState {
665 cur_cylinder,
666 ..state::SavedFloppyState::default()
667 },
668 state::SavedFloppyState::default(),
669 state::SavedFloppyState::default(),
670 state::SavedFloppyState::default(),
671 ],
672 };
673
674 Ok(saved_state)
675 }
676
677 fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
678 let state::SavedState {
679 digital_output,
680 main_status,
681 input_bytes,
682 output_bytes,
683 scd,
684 interrupt_output,
685 interrupt_level,
686 cur_drive: _,
687 floppies,
688 } = state;
689
690 self.state = FloppyState {
691 digital_output: digital_output.into(),
692 main_status: main_status.into(),
693 input_bytes: input_bytes.as_slice().try_into().map_err(
694 |e: arrayvec::CapacityError| RestoreError::InvalidSavedState(e.into()),
695 )?,
696 output_bytes: output_bytes.as_slice().try_into().map_err(
697 |e: arrayvec::CapacityError| RestoreError::InvalidSavedState(e.into()),
698 )?,
699 scd,
700 sense_output: interrupt_output.map(|x| x.into()),
701 interrupt_level,
702 cur_cylinder: floppies[0].cur_cylinder,
703 };
704
705 self.rt.interrupt.set_level(interrupt_level);
706
707 Ok(())
708 }
709 }
710}