floppy/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Emulator for the Intel 82077AA CHMOS Single-Chip Floppy Disk Controller.
5//!
6//! Some notable limitations of the current implementation:
7//!
8//! - no support for more than one attached floppy drive
9//! - no support for hot-add/remove of floppy disks
10//!
11//! While there's no _pressing_ need to address these limitations, it would
12//! certainly be _cool_ if we could implement that functionality at some point.
13//!
14//! # Accuracy
15//!
16//! This emulator is not 100% accurate, and does not implement all documented
17//! features of the 82077AA floppy disk controller. Rather, it implements a
18//! "pragmatic subset" of features that allow it to have "good-enough"
19//! compatibility with both modern and legacy operating systems.
20//!
21//! New features are only added on a case-by-case basis whenever a particular
22//! bit of software happens to require it.
23//
24// DEVNOTE: this implementation began life as a straight port of the existing
25// C++ code from Hyper-V, and while there has been some effort put into
26// reorganizing and refactoring the code to be more Rust-y, there's still quite
27// a ways to go.
28
29#![forbid(unsafe_code)]
30
31use self::floppy_sizes::FloppyImageType;
32use self::protocol::FLOPPY_TOTAL_CYLINDERS;
33use self::protocol::FloppyCommand;
34use self::protocol::INVALID_COMMAND_STATUS;
35use self::protocol::RegisterOffset;
36use self::protocol::STANDARD_FLOPPY_SECTOR_SIZE;
37use arrayvec::ArrayVec;
38use chipset_device::ChipsetDevice;
39use chipset_device::io::IoError;
40use chipset_device::io::IoResult;
41use chipset_device::pio::ControlPortIoIntercept;
42use chipset_device::pio::PortIoIntercept;
43use chipset_device::pio::RegisterPortIoIntercept;
44use chipset_device::poll_device::PollDevice;
45use core::sync::atomic::Ordering;
46use disk_backend::Disk;
47use guestmem::AlignedHeapMemory;
48use guestmem::GuestMemory;
49use guestmem::ranges::PagedRange;
50use inspect::Inspect;
51use inspect::InspectMut;
52use scsi_buffers::RequestBuffers;
53use std::future::Future;
54use std::pin::Pin;
55use std::sync::Arc;
56use std::task::Context;
57use std::task::Poll;
58use std::task::Waker;
59use thiserror::Error;
60use vmcore::device_state::ChangeDeviceState;
61use vmcore::isa_dma_channel::IsaDmaChannel;
62use vmcore::isa_dma_channel::IsaDmaDirection;
63use vmcore::line_interrupt::LineInterrupt;
64
65mod floppy_sizes {
66    use super::protocol::FLOPPY_TOTAL_CYLINDERS;
67    use super::protocol::STANDARD_FLOPPY_SECTOR_SIZE;
68
69    const HDMSS_SECTORS_PER_TRACK: u8 = 23;
70    const DMF_SECTORS_PER_TRACK: u8 = 21;
71    const HD_SECTORS_PER_TRACK: u8 = 18;
72    const MD_SECTORS_PER_TRACK: u8 = 15;
73    const LD_SECTORS_PER_TRACK: u8 = 9;
74
75    const fn calculate_image_size(sectors_per_track: u8) -> u64 {
76        sectors_per_track as u64
77            * STANDARD_FLOPPY_SECTOR_SIZE as u64
78            * FLOPPY_TOTAL_CYLINDERS as u64
79            * 2
80    }
81
82    const HDMSS_FLOPY_IMAGE_SIZE: u64 = calculate_image_size(HDMSS_SECTORS_PER_TRACK);
83    const DMF_FLOPPY_IMAGE_SIZE: u64 = calculate_image_size(DMF_SECTORS_PER_TRACK);
84    const HD_FLOPPY_IMAGE_SIZE: u64 = calculate_image_size(HD_SECTORS_PER_TRACK);
85    const MD_FLOPPY_IMAGE_SIZE: u64 = calculate_image_size(MD_SECTORS_PER_TRACK);
86    const LD_FLOPPY_IMAGE_SIZE: u64 = calculate_image_size(LD_SECTORS_PER_TRACK);
87    const LDSS_FLOPPY_IMAGE_SIZE: u64 = calculate_image_size(LD_SECTORS_PER_TRACK) / 2;
88
89    pub enum FloppyImageType {
90        /// Low-density disks, single sided (360Kb)
91        LowDensitySingleSided,
92        /// Low-density disks (720Kb)
93        LowDensity,
94        /// Medium-density disks (1.2Mb)
95        MediumDensity,
96        /// High-density disks (1.44MB)
97        HighDensity,
98        /// DMF (distribution media format) disks (1.68Mb)
99        Dmf,
100        /// High-density Multiple Sector Size (MSS) used by eXtended
101        /// Distribution Format (XDF) (1.72Mb)
102        HighDensityMss,
103    }
104
105    impl FloppyImageType {
106        pub fn sectors(&self) -> u8 {
107            match self {
108                FloppyImageType::LowDensity => LD_SECTORS_PER_TRACK,
109                FloppyImageType::HighDensity => HD_SECTORS_PER_TRACK,
110                FloppyImageType::Dmf => DMF_SECTORS_PER_TRACK,
111                FloppyImageType::LowDensitySingleSided => LD_SECTORS_PER_TRACK,
112                FloppyImageType::MediumDensity => MD_SECTORS_PER_TRACK,
113                FloppyImageType::HighDensityMss => HDMSS_SECTORS_PER_TRACK,
114            }
115        }
116
117        pub fn from_file_size(file_size: u64) -> Option<Self> {
118            let res = match file_size {
119                HD_FLOPPY_IMAGE_SIZE => FloppyImageType::HighDensity,
120                DMF_FLOPPY_IMAGE_SIZE => FloppyImageType::Dmf,
121                LD_FLOPPY_IMAGE_SIZE => FloppyImageType::LowDensity,
122                MD_FLOPPY_IMAGE_SIZE => FloppyImageType::MediumDensity,
123                LDSS_FLOPPY_IMAGE_SIZE => FloppyImageType::LowDensitySingleSided,
124                HDMSS_FLOPY_IMAGE_SIZE => FloppyImageType::HighDensityMss,
125                _ => return None,
126            };
127            Some(res)
128        }
129    }
130}
131
132mod protocol {
133    use bitfield_struct::bitfield;
134    use inspect::Inspect;
135    use open_enum::open_enum;
136
137    pub const FIFO_SIZE: usize = 16;
138
139    pub const INVALID_COMMAND_STATUS: u8 = 0x80; // returned by e.g., SENSE_INTERRUPT_STATUS on err
140
141    pub const STANDARD_FLOPPY_SECTOR_SIZE: usize = 512;
142    pub const FLOPPY_TOTAL_CYLINDERS: u8 = 80;
143
144    #[derive(Inspect)]
145    #[bitfield(u8)]
146    pub struct InputRegister {
147        #[bits(2)]
148        pub drive_select: u8,
149        #[bits(1)]
150        pub head: u8,
151        #[bits(5)]
152        unused2: u8,
153    }
154
155    #[derive(Inspect)]
156    #[bitfield(u8)]
157    pub struct StatusRegister0 {
158        #[bits(2)]
159        pub drive_select: u8,
160        #[bits(1)]
161        pub head: u8,
162        #[bits(2)]
163        unused: u8,
164        pub seek_end: bool,
165        pub abnormal_termination: bool,
166        pub invalid_command: bool,
167    }
168
169    #[derive(Inspect)]
170    #[bitfield(u8)]
171    pub struct StatusRegister1 {
172        pub missing_address: bool,
173        pub write_protected: bool,
174        pub no_data: bool,
175        #[bits(5)]
176        unused: u8,
177    }
178
179    #[derive(Inspect)]
180    #[bitfield(u8)]
181    pub struct StatusRegister2 {
182        pub missing_address: bool,
183        pub bad_cylinder: bool,
184        #[bits(6)]
185        unused: u8,
186    }
187
188    #[derive(Inspect)]
189    #[bitfield(u8)]
190    pub struct StatusRegister3 {
191        #[bits(2)]
192        pub drive_select: u8,
193        #[bits(1)]
194        pub head: u8,
195        pub unused1: bool, // This bit is always 1
196        pub track0: bool,
197        pub unused2: bool, // This bit is always 1
198        pub write_protected: bool,
199        pub unused3: bool, // This bit is always 0
200    }
201
202    open_enum! {
203        #[derive(Default)]
204        pub enum RegisterOffset: u16 {
205            STATUS_A = 0, // Read-only
206            STATUS_B = 1, // Read-only
207            DIGITAL_OUTPUT = 2,
208            TAPE_DRIVE = 3, // Obsolete
209            MAIN_STATUS = 4, // Read-only
210            DATA_RATE = 4, // Write-only
211            DATA = 5,
212            DIGITAL_INPUT = 7,// Read-only
213            CONFIG_CONTROL = 7, // Write-only
214        }
215    }
216
217    /// Floppy DOR - digital output register (read/write)
218    // Drive Select bits is [1:0]
219    // Reset bits is        [2]
220    // Not DMA Gate bit is  [3]
221    // Motor enable bits is [7:4]. Each bit for EN0, EN1, ..., EN4
222    #[derive(Inspect)]
223    #[bitfield(u8)]
224    pub struct DigitalOutputRegister {
225        // A good item to note are the drive activation (drive select and motor enable) values:
226        // DOR value= 0x1C for drive= 0,
227        // DOR value= 0x2D for drive= 1,
228        // DOR value= 0x4E for drive= 2,
229        // DOR value= 0x8F for drive= 3,
230        #[bits(2)]
231        pub _drive_select: u8,
232
233        // effectively, `not reset` 1 is true, 0 is false (meaning resetting)
234        pub controller_enabled: bool,
235
236        // bit high only in PC-AT and Model 30 modes
237        pub dma_enabled: bool,
238
239        // This is really 4 separate bools, but for our convenience we treat
240        // it as a large number (one-hot encoding).
241        #[bits(4)]
242        pub motors_active: u8,
243    }
244
245    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
246    pub enum DataDirection {
247        /// Write to guest memory. Also to indicate the
248        /// direction of a data transfer (0 indicates a
249        /// write is required -- an inward FIFO
250        /// direction).
251        Write = 0,
252        /// Read from guest memory.  Also to indicate the
253        /// direction of a data transfer (1 indicates a
254        /// read is required -- an outward FIFO
255        /// direction).
256        Read = 1,
257    }
258
259    impl DataDirection {
260        pub fn as_bool(self) -> bool {
261            match self {
262                Self::Write => false,
263                Self::Read => true,
264            }
265        }
266    }
267
268    /// Floppy MSR - main status register (read-only)
269    #[derive(Inspect)]
270    #[bitfield(u8)]
271    pub struct MainStatusRegister {
272        // This is really 4 separate bools, but for our convenience we treat
273        // it as a large number. E.g. one-hot encoded for DRV0, ..., DRV3
274        #[bits(4)]
275        pub active_drives: u8,
276        /// Indicates if the controller is currently executing a command
277        pub busy: bool,
278        /// Non DMA mode is not supported
279        pub non_dma_mode: bool,
280        /// Data input/output (1 - output data to CPU (read), 0 - receive data from CPU (write)).
281        /// Holds no meaning if main_request is not set.
282        pub data_direction: bool, // DataDirection
283        /// Indicates whether controller is ready to receive or send
284        /// data or commands via the data registers
285        pub main_request: bool,
286    }
287
288    /// Floppy DIR - digital input register (read-only)
289    // e.g., return current data-rate set via ConfigControl
290    #[derive(Inspect)]
291    #[bitfield(u8)]
292    pub struct DigitalInputRegister {
293        // in PC-AT, all bits except for msb always tristated
294        #[bits(7)]
295        pub tristated: u8,
296
297        #[bits(1)]
298        pub disk_change: bool,
299    }
300
301    open_enum! {
302        #[derive(Default)]
303        // #[inspect(debug)]
304        /// RECALIBRATE, SEEK, RELATIVE SEEK generate interrupts but do not clear
305        /// the signal themselves. The rest don't forget to clear if applicable.
306        pub enum FloppyCommand: u8 {
307            // high nibble may be 6, C, or E, based on bit values
308            // for MT, MFM, and SK
309            // READ_DATA = 0x06,
310            // READ_DEL_DATA = 0x0C,
311            // WRITE_DATA = 0x05,
312            // WRITE_DEL_DATA = 0x09,
313            // READ_TRACK = 0x02,
314            VERIFY = 0x16,
315            VERIFY2 = 0xF6,
316            /// Just checks if controller is newer/enhanced type, or old type.
317            /// Return value of 0x90 indicates enhanced type.
318            VERSION = 0x10,
319            FORMAT_TRACK = 0x4D,
320            FORMAT_DOUBLE_DENSITY_MODE = 0xCD,
321            // SCAN_EQUAL = 0x11,
322            SCAN_EQUAL_ALL = 0xD1,
323            SCAN_EQUAL = 0xF1,
324            SCAN_LOW_OR_EQUAL = 0x19,
325            SCAN_HIGH_OR_EQUAL = 0x1D,
326            /// Recalibrate command moves the read/write head back to position on
327            /// track 0. On physical floppy disk, there is a TRACK0 pin that goes
328            /// high when head reaches track 0. If disk has more than something
329            /// like 80 tracks, recalibrate would be needed to be called multiple
330            /// times (command simply does e.g., 79 steps via stepper motor pulse,
331            /// checking if each track is track 0)
332            ///
333            /// SENSE_INTERRUPT_STATUS must immediately follow, due to RECALIBRATE
334            /// not having result phase of its own in original design to lower
335            /// interrupt signal.
336            RECALIBRATE = 0x07,
337            /// Will clear interrupt signal, and determine what raised the
338            /// interrupt. Returns 0x80 if command issued when there are no
339            /// active interrupts.
340            ///
341            /// Must be called directly after RECALIBRATE and either type of SEEK.
342            SENSE_INTERRUPT_STATUS = 0x08,
343            /// Provide the Stepping Rate Time (SRT) to be used to set the rate at
344            /// which step pulses are issued to move between tracks during a SEEK
345            /// or RECALIBRATE. Also sets initial values for Head Unload Timer
346            /// (HUT), and Head Load Time (HLT). HUT defines time from end of
347            /// execution to head unload state, and HLT defines time between signal
348            /// for R/W operation raised, and operation begin.
349            SPECIFY = 0x03,
350            /// Simply returns drive state information. Directly proceeds to result
351            /// phase (e.g., no execution phase).
352            SENSE_DRIVE_STATUS = 0x04,
353            DRIVE_SPECIFICATION_COMMAND = 0x8E,
354            /// SEEK command moves the read/write head from track to track. Using
355            /// correct technicalities, the terms `track` and `cylinder` are some-
356            /// what synonymous. Consider a physical floppy disk -- it is a disk
357            /// with two sides. Each side is called a head. The concentric rings
358            /// that are on each head are called tracks. Each head has e.g., a
359            /// track 18. Together these two track 18s form a cylinder. But, if
360            /// we are to only use one head of the disk, then cylinder and track
361            /// are the same thing. SEEK effectively moves the read/write head
362            /// from the PCN (present cylinder number) to the NCN (new / desired
363            /// cylinder number). Here, the words cylinder and track mean the same.
364            ///
365            /// SENSE_INTERRUPT_STATUS must immediately follow, due to SEEK not
366            /// having result phase of its own in original design to lower interrupt
367            /// signal.
368            SEEK = 0x0F,
369            /// Enables various special features. Don't need by default, probably :)
370            /// E.g., Disable FIFO, disable polling
371            CONFIGURE = 0x13,
372            /// Similar to SEEK, except instead of providing NCN to move R/W head
373            /// to, provide an RCN (relative cylinder number), to move n tracks
374            /// out/in (specified by a direction bit 0/1 DIR) from PCN.
375            RELATIVE_SEEK_IN = 0xCF,
376            RELATIVE_SEEK_OUT = 0x8F,
377            /// Debug reasons
378            DUMP_REGISTERS = 0x0E,
379            READ_ID = 0x4A, // 4 for double density mode
380            /// Perpendicular Recording Mode classically is support for orienting
381            /// the the magnetic bits vertically instead of horizontally, thereby
382            /// being able to pack more data bits for the same area. Toggling
383            /// this mode in theory determines whether or not to interface with a
384            /// perpendicular recoding floppy drive. A 1 Mbps datarate is needed,
385            /// and all other commands here will function the same regardless.
386            PERP288_MODE = 0x12,
387            /// Set LOCK bit to 0.
388            ///
389            /// If LOCK bit is 1, then software resets by DOR/DSR will have no
390            /// effect any parameter values set by CONFIGURE. Hardware reset will
391            /// override and reset parameters.
392            UNLOCK_FIFO_FUNCTIONS = 0x14,
393            /// Set LOCK bit to 1.
394            LOCK_FIFO_FUNCTIONS = 0x94,
395            /// Only purpose is really for problem reporting.
396            PART_ID = 0x18,
397            POWERDOWN_MODE = 0x17,
398            OPTION = 0x33,
399            SAVE = 0x2E,
400            RESTORE = 0x4E,
401            FORMAT_AND_WRITE = 0xAD,
402
403            EXIT_STANDBY_MODE = 0x34,
404            GOTO_STANDBY_MODE = 0x35,
405            HARD_RESET = 0x36,
406            READ_TRACK = 0x42,
407            SEEK_AND_WRITE = 0x45,
408            SEEK_AND_READ = 0x46,
409            ALT_SEEK_AND_READ = 0x66,
410            WRITE_DATA = 0xC5,
411            READ_NORMAL_DEL_DATA = 0xC6,
412            WRITE_DEL_DATA = 0xC9,
413            READ_DEL_DATA = 0xCC,
414            WRITE_NORMAL_DATA = 0xE5, // Nonstandard command used by BeOS
415            READ_NORMAL_DATA = 0xE6,
416
417            INVALID = 0x00,
418        }
419    }
420
421    impl FloppyCommand {
422        // Floppy commands are written one byte at a time to the DATA register. The
423        // first byte specifies the issued command. The remaining bytes are used as
424        // inputs for the command. AKA, number of parameters for particular command
425        pub fn input_bytes_needed(&self) -> usize {
426            // Add one to account for the command byte itself
427            1 + match *self {
428                Self::READ_DEL_DATA => 8,
429                Self::WRITE_DATA => 8,
430                Self::WRITE_DEL_DATA => 8,
431                Self::READ_TRACK => 8,
432                Self::VERIFY => 8,
433                Self::VERSION => 0,
434                Self::FORMAT_TRACK => 5,
435                Self::FORMAT_DOUBLE_DENSITY_MODE => 5,
436                Self::SCAN_EQUAL_ALL => 8,
437                Self::SCAN_EQUAL => 8,
438                Self::SCAN_LOW_OR_EQUAL => 8,
439                Self::SCAN_HIGH_OR_EQUAL => 8,
440                Self::RECALIBRATE => 1,
441                Self::SENSE_INTERRUPT_STATUS => 0,
442                Self::SPECIFY => 2,
443                Self::SENSE_DRIVE_STATUS => 1,
444                Self::DRIVE_SPECIFICATION_COMMAND => 6,
445                Self::SEEK => 2,
446                Self::CONFIGURE => 3,
447                Self::RELATIVE_SEEK_IN => 2,
448                Self::RELATIVE_SEEK_OUT => 2,
449                Self::DUMP_REGISTERS => 0,
450                Self::READ_ID => 1,
451                Self::PERP288_MODE => 1,
452                Self::UNLOCK_FIFO_FUNCTIONS => 0,
453                Self::LOCK_FIFO_FUNCTIONS => 0,
454                Self::PART_ID => 0,
455                Self::POWERDOWN_MODE => 1,
456                Self::OPTION => 1,
457                Self::SAVE => 0,
458                Self::RESTORE => 16,
459                Self::FORMAT_AND_WRITE => 5,
460
461                // Self::EXIT_STANDBY_MODE =>,
462                // Self::GOTO_STANDBY_MODE =>,
463                // Self::HARD_RESET =>,
464                // Self::READ_TRACK =>,
465                Self::SEEK_AND_WRITE => 8,
466                Self::SEEK_AND_READ => 8,
467                Self::ALT_SEEK_AND_READ => 8,
468                Self::READ_NORMAL_DEL_DATA => 8,
469                Self::WRITE_NORMAL_DATA => 8,
470                Self::READ_NORMAL_DATA => 8,
471
472                // Self::INVALID => ..,
473                _ => 0,
474            }
475        }
476
477        pub fn result_bytes_expected(&self) -> usize {
478            match *self {
479                Self::READ_DEL_DATA => 7,
480                Self::WRITE_DATA => 7,
481                Self::WRITE_DEL_DATA => 7,
482                Self::READ_TRACK => 7,
483                Self::VERIFY => 7,
484                Self::VERSION => 1,
485                Self::FORMAT_TRACK => 7,
486                Self::FORMAT_DOUBLE_DENSITY_MODE => 7,
487                Self::SCAN_EQUAL_ALL => 7,
488                Self::SCAN_EQUAL => 7,
489                Self::SCAN_LOW_OR_EQUAL => 7,
490                Self::SCAN_HIGH_OR_EQUAL => 7,
491                Self::RECALIBRATE => 2,
492                Self::SENSE_INTERRUPT_STATUS => 2,
493                Self::SPECIFY => 0,
494                Self::SENSE_DRIVE_STATUS => 1,
495                Self::DRIVE_SPECIFICATION_COMMAND => 0,
496                Self::SEEK => 2, // TODO: 0?
497                Self::CONFIGURE => 0,
498                Self::RELATIVE_SEEK_IN => 2,  // TODO: 0?
499                Self::RELATIVE_SEEK_OUT => 2, // TODO: 0?
500                Self::DUMP_REGISTERS => 10,
501                Self::READ_ID => 7,
502                Self::PERP288_MODE => 0,
503                Self::UNLOCK_FIFO_FUNCTIONS => 1,
504                Self::LOCK_FIFO_FUNCTIONS => 1,
505                Self::PART_ID => 1,
506                Self::POWERDOWN_MODE => 1,
507                Self::OPTION => 1,
508                Self::SAVE => 16,
509                Self::RESTORE => 0,
510                Self::FORMAT_AND_WRITE => 7,
511
512                // Self::EXIT_STANDBY_MODE =>,
513                // Self::GOTO_STANDBY_MODE =>,
514                // Self::HARD_RESET =>,
515                Self::SEEK_AND_WRITE => 7,
516                Self::SEEK_AND_READ => 7,
517                Self::ALT_SEEK_AND_READ => 7,
518                Self::READ_NORMAL_DEL_DATA => 7,
519                Self::WRITE_NORMAL_DATA => 7,
520                Self::READ_NORMAL_DATA => 7,
521
522                Self::INVALID => 1,
523                _ => 0,
524            }
525        }
526    }
527
528    #[derive(Inspect)]
529    #[bitfield(u8)]
530    pub struct SpecifyParam1 {
531        #[bits(4)]
532        pub head_unload_timer: u8,
533        #[bits(4)]
534        pub step_rate_time: u8,
535    }
536
537    #[derive(Inspect)]
538    #[bitfield(u8)]
539    pub struct SpecifyParam2 {
540        #[bits(7)]
541        pub head_load_timer: u8,
542        pub dma_disabled: bool,
543    }
544}
545
546const MAX_CMD_BUFFER_BYTES: usize = 64 * 1024;
547
548#[derive(Debug)]
549struct CommandBuffer {
550    buffer: Arc<AlignedHeapMemory>,
551}
552
553#[derive(Debug)]
554struct CommandBufferAccess {
555    memory: GuestMemory,
556}
557
558impl CommandBuffer {
559    fn new() -> Self {
560        Self {
561            buffer: Arc::new(AlignedHeapMemory::new(MAX_CMD_BUFFER_BYTES)),
562        }
563    }
564
565    fn access(&self) -> CommandBufferAccess {
566        CommandBufferAccess {
567            memory: GuestMemory::new("floppy_buffer", self.buffer.clone()),
568        }
569    }
570}
571
572impl CommandBufferAccess {
573    fn buffers(&self, offset: usize, len: usize, is_write: bool) -> RequestBuffers<'_> {
574        // The buffer is 16 4KB pages long.
575        static BUFFER_RANGE: Option<PagedRange<'_>> = PagedRange::new(
576            0,
577            MAX_CMD_BUFFER_BYTES,
578            &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
579        );
580
581        RequestBuffers::new(
582            &self.memory,
583            BUFFER_RANGE.unwrap().subrange(offset, len),
584            is_write,
585        )
586    }
587}
588
589struct Io(Pin<Box<dyn Send + Future<Output = Result<(), disk_backend::DiskError>>>>);
590
591impl ChangeDeviceState for FloppyDiskController {
592    fn start(&mut self) {}
593
594    async fn stop(&mut self) {}
595
596    async fn reset(&mut self) {
597        self.reset(false);
598    }
599}
600
601impl ChipsetDevice for FloppyDiskController {
602    fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
603        Some(self)
604    }
605
606    fn supports_poll_device(&mut self) -> Option<&mut dyn PollDevice> {
607        Some(self)
608    }
609}
610
611impl PollDevice for FloppyDiskController {
612    fn poll_device(&mut self, cx: &mut Context<'_>) {
613        if let Some(io) = self.io.as_mut() {
614            if let Poll::Ready(result) = io.0.as_mut().poll(cx) {
615                self.io = None;
616                self.handle_io_completion(result);
617            }
618        }
619        self.waker = Some(cx.waker().clone());
620    }
621}
622
623impl PortIoIntercept for FloppyDiskController {
624    fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
625        if data.len() != 1 {
626            return IoResult::Err(IoError::InvalidAccessSize);
627        }
628
629        let offset = RegisterOffset(io_port % 0x10);
630        data[0] = match offset {
631            // This port is completely unsupported by latest floppy controllers.
632            RegisterOffset::STATUS_A => 0xFF,
633            // Also unsupported but return 0xFC to indicate no tape drives present.
634            RegisterOffset::STATUS_B => 0xFC,
635            // Do nothing. This port is obsolete.
636            RegisterOffset::TAPE_DRIVE => 0xFF,
637            RegisterOffset::DIGITAL_OUTPUT => self.state.digital_output.into(),
638            RegisterOffset::MAIN_STATUS => {
639                // Indicate data register is ready for reading/writing.
640                // manifests as 0x80 (or something else with msb high)
641                if self.state.digital_output.controller_enabled() {
642                    self.state.main_status.into()
643                } else {
644                    0
645                }
646            }
647            RegisterOffset::DATA => {
648                // If there are more bytes left to read then read them out now.
649                let active_drive = self.state.main_status.active_drives();
650                let io_direction = self.state.main_status.data_direction();
651                tracing::trace!(?active_drive, ?io_direction, "DATA io read state");
652
653                if let Some(result) = self.state.output_bytes.pop() {
654                    self.state.main_status.set_active_drives(0);
655                    if self.state.output_bytes.is_empty() {
656                        // Reverse direction, now ready to receive a new command
657                        self.state.main_status = (self.state.main_status)
658                            .with_non_dma_mode(false)
659                            .with_busy(false)
660                            .with_main_request(true)
661                            .with_data_direction(protocol::DataDirection::Write.as_bool());
662                    }
663                    result
664                } else {
665                    INVALID_COMMAND_STATUS
666                }
667            }
668
669            // This port returns a value in the high bit if a floppy is
670            // missing or has changed since the last command.
671            RegisterOffset::DIGITAL_INPUT => {
672                // The bottom seven bits are tristated, and always read as
673                // ones on a real floppy controller (in PC-AT mode).
674                let val = protocol::DigitalInputRegister::new()
675                    .with_tristated(0x7f)
676                    .with_disk_change(
677                        self.state.digital_output.motors_active() != 0
678                            && (!self.state.internals.floppy_present
679                                || self.state.internals.floppy_changed),
680                    );
681
682                val.into()
683            }
684            _ => return IoResult::Err(IoError::InvalidRegister),
685        };
686
687        tracing::trace!(?offset, ?data, "floppy pio read");
688
689        IoResult::Ok
690    }
691
692    fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
693        if data.len() != 1 {
694            return IoResult::Err(IoError::InvalidAccessSize);
695        }
696
697        let data = data[0];
698        let offset = RegisterOffset(io_port % 0x10);
699        tracing::trace!(?offset, ?data, "floppy pio write");
700        match offset {
701            RegisterOffset::STATUS_A | RegisterOffset::STATUS_B => {
702                tracelimit::warn_ratelimited!(
703                    ?data,
704                    ?offset,
705                    "write to read-only floppy status register"
706                );
707            }
708            RegisterOffset::TAPE_DRIVE => {
709                tracing::debug!(?data, "write to obsolete tape drive register");
710            } // Do nothing. This port is obsolete.
711            RegisterOffset::CONFIG_CONTROL => {
712                // This controls the data transfer rate which is not
713                // interesting to us. We will just ignore it.
714                tracing::debug!(?data, "write to control register");
715            }
716            RegisterOffset::DATA_RATE => {
717                const FLOPPY_DSR_DISK_RESET_MASK: u8 = 0x80; // DSR = Data-rate Select Register ("software" reset)
718
719                if self.state.digital_output.controller_enabled()
720                    && (data & FLOPPY_DSR_DISK_RESET_MASK) != 0
721                {
722                    self.reset(true);
723                    self.state.sense_output = Some(SenseOutput::ResetCounter { count: 4 });
724                    // Always trigger a reset interrupt, even though DMA will be disabled
725                    self.raise_interrupt(true);
726                    tracing::trace!("DSR wr: Un-resetting - asserting floppy interrupt");
727                }
728            }
729            RegisterOffset::DIGITAL_OUTPUT => {
730                let new_digital_output = protocol::DigitalOutputRegister::from(data);
731                // state written to DOR contains NOT RESET, and controller enabled is
732                // the positive logic relation. Negating controller enabled then gives
733                // a high reset signal (which was originally transmitted as active low).
734                // This means that a 0x00 byte disabled all motors and initiates a reset.
735                // And then 0x04 byte turns off the reset flag. 0x08 will enable
736                // interrupts, etc.
737                let was_reset = !self.state.digital_output.controller_enabled();
738                let is_reset = !new_digital_output.controller_enabled();
739                let interrupts_were_enabled = self.state.digital_output.dma_enabled();
740                let interrupts_enabled = new_digital_output.dma_enabled();
741                self.state.digital_output = new_digital_output;
742
743                if was_reset && !is_reset {
744                    tracing::trace!("DOR wr: Un-resetting - asserting floppy interrupt");
745                    self.state.sense_output = Some(SenseOutput::ResetCounter { count: 4 });
746                    // Always trigger a reset interrupt, regardless of DMA configuration
747                    self.raise_interrupt(true);
748                } else if is_reset {
749                    tracing::debug!("DOR wr: Software reset on fdc");
750                    self.reset(true);
751                } else {
752                    if !interrupts_were_enabled && interrupts_enabled {
753                        tracing::trace!("Re-enabling floppy interrupts");
754                        self.raise_interrupt(false);
755                    } else if interrupts_were_enabled && !interrupts_enabled {
756                        tracing::trace!("Disabling floppy interrupts");
757                        self.lower_interrupt();
758                    }
759                }
760            }
761            RegisterOffset::DATA => self.handle_data_write(data),
762            _ => return IoResult::Err(IoError::InvalidRegister),
763        }
764
765        IoResult::Ok
766    }
767}
768
769#[derive(Clone, Inspect)]
770struct FloppyState {
771    digital_output: protocol::DigitalOutputRegister,
772    main_status: protocol::MainStatusRegister,
773
774    // Used for command input
775    #[inspect(bytes)]
776    input_bytes: ArrayVec<u8, { protocol::FIFO_SIZE }>,
777
778    // Used for output status/results
779    #[inspect(bytes)]
780    output_bytes: ArrayVec<u8, { protocol::FIFO_SIZE }>,
781
782    // Needed for async Read/Write/Format
783    #[inspect(skip)]
784    pending_command: FloppyCommand,
785
786    // scd: [u8; 2],
787    head_unload_timer: u8,
788    step_rate_time: u8,
789    head_load_timer: u8,
790    dma_disabled: bool,
791
792    sense_output: Option<SenseOutput>,
793
794    internals: FloppyStateInternals,
795
796    // HACK: Our DSDT always reports that only 1 drive is available.
797    // If this changes in the future proper drive selection and indexing will
798    // need to be implemented here.
799    position: ReadWriteHeadLocation,
800    end_of_track: u8,
801
802    // Needed for save/restore
803    interrupt_level: bool,
804}
805
806#[derive(Clone, Inspect, Debug, Default, Copy)]
807struct FloppyStateInternals {
808    floppy_changed: bool,
809    floppy_present: bool,
810    media_write_protected: bool,
811    io_pending: bool,
812
813    num_bytes_rd: u32,
814    num_bytes_wr: u32,
815    sectors_per_track: u8,
816    start_sector_pos: u32,
817    sector_cache_start_logical: u32,
818    sector_cache_end_logical: u32,
819}
820
821#[derive(Inspect, Debug, Clone, Copy)]
822struct ReadWriteHeadLocation {
823    cylinder: u8,
824    head: u8,
825    sector: u8,
826}
827
828impl ReadWriteHeadLocation {
829    fn new() -> Self {
830        Self {
831            cylinder: 0,
832            head: 0,
833            sector: 0,
834        }
835    }
836
837    /// Convert from a logical block address lba to a cylinder, head, sector chs indexing scheme
838    /// Typical cylinder is 0-79, head is 0-1, sector is 1-18 all inclusive
839    /// See https://wiki.osdev.org/Floppy_Disk_Controller#CHS
840    fn chs_to_lba(&self, sectors_per_track: u8) -> u32 {
841        (self.cylinder as u32 * 2 + self.head as u32) * sectors_per_track as u32
842            + (self.sector as u32 - 1)
843    }
844}
845
846#[derive(Clone, Inspect, Debug)]
847#[inspect(external_tag)]
848enum SenseOutput {
849    /// BIOS expects the controller to interrupt four times, one for
850    /// each possible drive connected to controller. Even though
851    /// right now per DSDT, we only can have one drive
852    ResetCounter { count: u8 },
853    /// Effectively denotes interrupt cause, as part of drive state
854    Value { value: protocol::StatusRegister0 },
855}
856
857impl FloppyState {
858    fn new(sectors_per_track: u8, read_only: bool) -> Self {
859        Self {
860            digital_output: protocol::DigitalOutputRegister::new(),
861            main_status: protocol::MainStatusRegister::new(),
862            position: ReadWriteHeadLocation::new(),
863            end_of_track: 0,
864
865            input_bytes: ArrayVec::new(),
866            output_bytes: ArrayVec::new(),
867
868            head_unload_timer: 0,
869            step_rate_time: 0,
870            head_load_timer: 0,
871            dma_disabled: false,
872            sense_output: None,
873            interrupt_level: false,
874
875            internals: FloppyStateInternals::new(sectors_per_track, read_only),
876
877            pending_command: FloppyCommand::INVALID,
878        }
879    }
880}
881
882impl FloppyStateInternals {
883    fn new(sectors_per_track: u8, read_only: bool) -> Self {
884        // TODO: this is bogus, but works fine given that we
885        // don't support multi disks / hot add/remove
886        let floppy_present = sectors_per_track != 0;
887
888        Self {
889            floppy_changed: false,
890            floppy_present,
891            media_write_protected: read_only,
892            io_pending: false,
893
894            num_bytes_rd: 0,
895            num_bytes_wr: 0,
896            sectors_per_track,
897            start_sector_pos: 0,
898            sector_cache_start_logical: 0,
899            sector_cache_end_logical: 0,
900        }
901    }
902}
903
904#[derive(Inspect)]
905struct FloppyRt {
906    interrupt: LineInterrupt,
907    pio_base: Box<dyn ControlPortIoIntercept>,
908    pio_control: Box<dyn ControlPortIoIntercept>,
909}
910
911/// 82077AA Floppy disk controller
912#[derive(InspectMut)]
913pub struct FloppyDiskController {
914    guest_memory: GuestMemory,
915
916    // Runtime glue
917    rt: FloppyRt,
918
919    // Volatile state
920    state: FloppyState,
921
922    // backend
923    disk_drive: DriveRibbon,
924
925    #[inspect(skip)]
926    dma: Box<dyn IsaDmaChannel>,
927
928    #[inspect(skip)]
929    command_buffer: CommandBuffer,
930
931    #[inspect(with = "Option::is_some")]
932    io: Option<Io>,
933    #[inspect(skip)]
934    waker: Option<Waker>,
935}
936
937/// Floppy disk drive configuration
938#[derive(Inspect)]
939#[inspect(external_tag)]
940pub enum DriveRibbon {
941    /// No drives connected
942    None,
943    /// Single drive connected
944    Single(#[inspect(rename = "media")] Disk),
945    // TODO: consider supporting multiple disks per controller?
946    // real hardware can support up to 4 per controller...
947}
948
949/// Error returned by `DriveRibbon::from_vec` when too many drives are provided.
950#[derive(Debug, Error)]
951#[error("too many drives")]
952pub struct TooManyDrives;
953
954impl DriveRibbon {
955    /// Create a new `DriveRibbon` from a vector of `Disk`s.
956    pub fn from_vec(drives: Vec<Disk>) -> Result<Self, TooManyDrives> {
957        match drives.len() {
958            0 => Ok(Self::None),
959            1 => Ok(Self::Single(drives.into_iter().next().unwrap())),
960            _ => Err(TooManyDrives),
961        }
962    }
963}
964
965/// Errors returned by `FloppyDiskController::new`.
966#[derive(Debug, Error)]
967pub enum NewFloppyDiskControllerError {
968    /// The disk is not a standard size.
969    #[error("disk is non-standard size: {0} bytes")]
970    NonStandardDisk(u64),
971}
972
973impl FloppyDiskController {
974    /// Create a new floppy disk controller.
975    pub fn new(
976        guest_memory: GuestMemory,
977        interrupt: LineInterrupt,
978        register_pio: &mut dyn RegisterPortIoIntercept,
979        pio_base_addr: u16,
980        disk_drive: DriveRibbon,
981        dma: Box<dyn IsaDmaChannel>,
982    ) -> Result<Self, NewFloppyDiskControllerError> {
983        let mut pio_base = register_pio.new_io_region("base", 6);
984        let mut pio_control = register_pio.new_io_region("control", 1);
985
986        pio_base.map(pio_base_addr);
987        // take note of the 1-byte "hole" in this register space!
988        // it is important, as it turns out that IDE controllers claim this port for themselves!
989        pio_control.map(pio_base_addr + RegisterOffset::DIGITAL_INPUT.0);
990
991        Ok(Self {
992            guest_memory,
993            rt: FloppyRt {
994                interrupt,
995                pio_base,
996                pio_control,
997            },
998            state: FloppyState::new(
999                {
1000                    match &disk_drive {
1001                        DriveRibbon::None => {
1002                            // TODO: this is bogus, but works fine given that we
1003                            // don't support multi disks / hot add/remove
1004                            0
1005                        }
1006                        DriveRibbon::Single(disk) => {
1007                            let file_size = disk.sector_count() * disk.sector_size() as u64;
1008
1009                            let image_type = FloppyImageType::from_file_size(file_size)
1010                                .ok_or(NewFloppyDiskControllerError::NonStandardDisk(file_size))?;
1011                            image_type.sectors()
1012                        }
1013                    }
1014                },
1015                match &disk_drive {
1016                    DriveRibbon::Single(disk) => disk.is_read_only(),
1017                    DriveRibbon::None => false,
1018                },
1019            ),
1020            disk_drive,
1021            dma,
1022            command_buffer: CommandBuffer::new(),
1023            io: None,
1024            waker: None,
1025        })
1026    }
1027
1028    /// Sets the asynchronous IO to be polled in `poll_device`.
1029    fn set_io<F, Fut>(&mut self, f: F)
1030    where
1031        F: FnOnce(Disk) -> Fut,
1032        Fut: 'static + Future<Output = Result<(), disk_backend::DiskError>> + Send,
1033    {
1034        let DriveRibbon::Single(disk) = &self.disk_drive else {
1035            panic!();
1036        };
1037
1038        let fut = (f)(disk.clone());
1039        assert!(self.io.is_none());
1040        self.io = Some(Io(Box::pin(fut)));
1041        // Ensure poll_device gets called again.
1042        if let Some(waker) = self.waker.take() {
1043            waker.wake();
1044        }
1045    }
1046
1047    fn handle_io_completion(&mut self, result: Result<(), disk_backend::DiskError>) {
1048        let command = self.state.pending_command;
1049        tracing::trace!(?command, ?result, "io completion");
1050
1051        let result = match command {
1052            FloppyCommand::READ_NORMAL_DATA
1053            | FloppyCommand::READ_NORMAL_DEL_DATA
1054            | FloppyCommand::READ_DEL_DATA
1055            | FloppyCommand::SEEK_AND_READ
1056            | FloppyCommand::ALT_SEEK_AND_READ
1057            | FloppyCommand::READ_TRACK => match result {
1058                Ok(()) => self.read_complete(),
1059                Err(err) => Err(err),
1060            },
1061            FloppyCommand::WRITE_NORMAL_DATA
1062            | FloppyCommand::WRITE_DATA
1063            | FloppyCommand::WRITE_DEL_DATA
1064            | FloppyCommand::SEEK_AND_WRITE => match result {
1065                Ok(()) => self.write_complete(),
1066                Err(err) => Err(err),
1067            },
1068            FloppyCommand::FORMAT_TRACK | FloppyCommand::FORMAT_DOUBLE_DENSITY_MODE => match result
1069            {
1070                Ok(()) => self.write_zeros_complete(),
1071                Err(err) => Err(err),
1072            },
1073            _ => {
1074                tracelimit::error_ratelimited!(?command, "unexpected command!");
1075                return;
1076            }
1077        };
1078
1079        if let Err(err) = result {
1080            let wo_error = matches!(err, disk_backend::DiskError::ReadOnly);
1081            self.set_output_status(true, wo_error, true);
1082        }
1083
1084        self.state.pending_command = FloppyCommand::INVALID;
1085        self.complete_command(true);
1086    }
1087
1088    // This function is called when we are done reading from a floppy drive image
1089    // asynchronously or if the data was already in the cache.
1090    fn read_complete(&mut self) -> Result<(), disk_backend::DiskError> {
1091        // TODO: we should be checking if the DMA channel is OK before firing
1092        // off a storage backend request...
1093        let buffer = match self.dma.request(IsaDmaDirection::Write) {
1094            Some(r) => r,
1095            None => {
1096                tracelimit::error_ratelimited!("request_dma for read failed");
1097                return Err(disk_backend::DiskError::Io(std::io::Error::other(
1098                    "request_dma for read failed",
1099                )));
1100            }
1101        };
1102
1103        let size = (buffer.size.div_ceil(STANDARD_FLOPPY_SECTOR_SIZE) * STANDARD_FLOPPY_SECTOR_SIZE)
1104            as u32;
1105
1106        let buffer_ptr = &self.command_buffer.buffer[0..size as usize][..size as usize];
1107
1108        let res = self
1109            .guest_memory
1110            .write_from_atomic(buffer.address, buffer_ptr);
1111
1112        self.dma.complete();
1113
1114        if let Err(err) = res {
1115            tracelimit::error_ratelimited!(
1116                error = &err as &dyn std::error::Error,
1117                "dma transfer failed"
1118            );
1119            return Err(disk_backend::DiskError::MemoryAccess(err.into()));
1120        }
1121
1122        self.set_output_status(false, false, false);
1123
1124        Ok(())
1125    }
1126
1127    fn write_complete(&mut self) -> Result<(), disk_backend::DiskError> {
1128        self.set_output_status(false, false, false);
1129        Ok(())
1130    }
1131
1132    fn write_zeros_complete(&mut self) -> Result<(), disk_backend::DiskError> {
1133        self.set_output_status(false, false, true);
1134        Ok(())
1135    }
1136
1137    /// Return the offset of `addr` from the region's base address.
1138    ///
1139    /// Returns `None` if the provided `addr` is outside of the memory
1140    /// region, or the region is currently unmapped.
1141    pub fn offset_of(&self, addr: u16) -> Option<u16> {
1142        self.rt.pio_base.offset_of(addr).or_else(|| {
1143            self.rt
1144                .pio_control
1145                .offset_of(addr)
1146                .map(|_| RegisterOffset::DIGITAL_INPUT.0)
1147        })
1148    }
1149
1150    fn raise_interrupt(&mut self, is_reset: bool) {
1151        if self.state.digital_output.dma_enabled() || is_reset {
1152            self.rt.interrupt.set_level(true);
1153            self.state.interrupt_level = true;
1154        }
1155    }
1156
1157    fn lower_interrupt(&mut self) {
1158        self.rt.interrupt.set_level(false);
1159        self.state.interrupt_level = false;
1160    }
1161
1162    // e.g., a reset in the DOR register (meaning bit 2..3 is low)
1163    // will deassert irq, reset all state info like cur cylinder,
1164    // but will preserve contents of DOR register itself
1165    fn reset(&mut self, preserve_digital_output: bool) {
1166        self.lower_interrupt();
1167        self.state = FloppyState {
1168            digital_output: if preserve_digital_output {
1169                self.state.digital_output
1170            } else {
1171                protocol::DigitalOutputRegister::new()
1172            },
1173            ..FloppyState::new(
1174                self.state.internals.sectors_per_track,
1175                self.state.internals.media_write_protected,
1176            )
1177        };
1178
1179        // At the end of a reset we always do want to set RQM bit in MSR high,
1180        // so this is fine
1181        self.state.main_status = protocol::MainStatusRegister::new().with_main_request(true);
1182
1183        tracing::trace!(
1184            preserve_digital_output,
1185            "controller reset - deasserting floppy interrupt"
1186        );
1187    }
1188
1189    fn parse_input_for_readwrite(&mut self) {
1190        let input = protocol::InputRegister::from(self.state.input_bytes[1]);
1191        if input.drive_select() != 0 {
1192            tracelimit::warn_ratelimited!(
1193                "Drive selected as outside of what is supported in data read"
1194            );
1195        }
1196
1197        let head = input.head();
1198
1199        self.state.position.head = head;
1200        self.state.position.cylinder = self.state.input_bytes[2];
1201        if self.state.position.cylinder > FLOPPY_TOTAL_CYLINDERS {
1202            tracelimit::warn_ratelimited!(?self.state.position.cylinder, "Floppy seek to cylinder > 80");
1203        }
1204        self.state.position.sector = self.state.input_bytes[4];
1205        self.state.end_of_track = self.state.input_bytes[6];
1206        if self.state.input_bytes[5] != 2 || self.state.input_bytes[8] != 0xFF {
1207            tracelimit::warn_ratelimited!(?self.state.input_bytes, "non-standard floppy read command parameters for PC floppy format");
1208        }
1209    }
1210
1211    fn get_sense_output(&mut self) -> &mut protocol::StatusRegister0 {
1212        match self.state.sense_output {
1213            Some(SenseOutput::Value { ref mut value }) => value,
1214            _ => {
1215                self.state.sense_output = Some(SenseOutput::Value {
1216                    value: protocol::StatusRegister0::new(),
1217                });
1218
1219                match self.state.sense_output {
1220                    Some(SenseOutput::Value { ref mut value }) => value,
1221                    _ => panic!(),
1222                }
1223            }
1224        }
1225    }
1226
1227    fn handle_sense_interrupt_status(&mut self) {
1228        match self.state.sense_output {
1229            Some(SenseOutput::ResetCounter { ref mut count }) => {
1230                // If the controller was just reset, it needs to send four
1231                // consecutive interrupts - one for each possible drive. The
1232                // bottom two bits of the ST0 (passed back as the first output
1233                // parameter) should increase from 0 to 3.
1234                if *count > 0 {
1235                    let out = protocol::StatusRegister0::from(4 - *count)
1236                        .with_invalid_command(true)
1237                        .with_abnormal_termination(true);
1238                    self.state.sense_output = if (*count - 1) == 0 {
1239                        None
1240                    } else {
1241                        Some(SenseOutput::ResetCounter { count: *count - 1 })
1242                    };
1243                    self.state.output_bytes.push(self.state.position.cylinder);
1244                    self.state.output_bytes.push(out.into());
1245                } else {
1246                    tracelimit::error_ratelimited!(
1247                        "SENSE_INTERRUPT_STATUS called with ResetCount stage 0. p lease fix me"
1248                    );
1249                    self.state.output_bytes.push(INVALID_COMMAND_STATUS);
1250                    self.state.output_bytes.push(INVALID_COMMAND_STATUS);
1251                }
1252            }
1253            Some(SenseOutput::Value { value }) => {
1254                self.state.output_bytes.push(self.state.position.cylinder);
1255                self.state.output_bytes.push(value.into());
1256
1257                self.state.sense_output = None;
1258            }
1259            _ => {
1260                self.state.output_bytes.push(INVALID_COMMAND_STATUS);
1261                self.state.output_bytes.push(INVALID_COMMAND_STATUS);
1262            }
1263        }
1264
1265        tracing::trace!("sense interrupt status cmd - deasserting floppy interrupt");
1266
1267        self.lower_interrupt();
1268
1269        self.state.main_status = (self.state.main_status)
1270            .with_data_direction(protocol::DataDirection::Write.as_bool())
1271            .with_non_dma_mode(false)
1272            .with_busy(false)
1273            .with_main_request(true);
1274    }
1275
1276    fn handle_sense_drive_status(&mut self) {
1277        let input = protocol::InputRegister::from(self.state.input_bytes[1]);
1278        let drive: u8 = input.drive_select();
1279        if drive != 0 {
1280            tracelimit::warn_ratelimited!(
1281                ?drive,
1282                "Floppy drive number out of range from DSDT enforcement"
1283            );
1284        }
1285
1286        let head: u8 = input.head();
1287        self.state.position.head = head;
1288
1289        let output = protocol::StatusRegister3::new()
1290            .with_drive_select(drive)
1291            .with_head(head)
1292            .with_unused1(true)
1293            .with_track0(self.state.position.cylinder == 0)
1294            .with_unused2(true)
1295            .with_write_protected(self.state.internals.media_write_protected);
1296
1297        self.state.output_bytes.push(output.into());
1298        self.get_sense_output().set_seek_end(true);
1299    }
1300
1301    fn set_output_status(&mut self, rw_error: bool, wo_error: bool, end_seek: bool) {
1302        if !self.state.output_bytes.is_empty() {
1303            tracelimit::warn_ratelimited!("output_setup_long called with non-empty output_bytes");
1304        }
1305        self.state.output_bytes.push(0x2); // sector size code for standard size of 512 bytes
1306        self.state.output_bytes.push(1);
1307
1308        self.state.output_bytes.push(self.state.position.head);
1309        self.state.output_bytes.push(self.state.position.cylinder);
1310        self.state.output_bytes.push(
1311            protocol::StatusRegister2::new()
1312                .with_missing_address(rw_error)
1313                .with_bad_cylinder(rw_error)
1314                .into(),
1315        );
1316
1317        self.state.output_bytes.push(
1318            protocol::StatusRegister1::new()
1319                .with_no_data(rw_error)
1320                .with_missing_address(rw_error)
1321                .with_write_protected(wo_error)
1322                .into(),
1323        );
1324
1325        self.state.output_bytes.push({
1326            let drive = 0; // again, we only support one drive, but could be changed in future
1327            let out = protocol::StatusRegister0::new()
1328                .with_drive_select(drive)
1329                .with_head(self.state.position.head)
1330                .with_abnormal_termination(rw_error || wo_error)
1331                .with_seek_end(end_seek);
1332
1333            out.into()
1334        });
1335    }
1336
1337    fn complete_command(&mut self, request_interrupt: bool) {
1338        let has_output = !self.state.output_bytes.is_empty();
1339        self.state.main_status.set_busy(has_output);
1340        self.state.main_status.set_non_dma_mode(false);
1341
1342        let dma_type = if has_output {
1343            protocol::DataDirection::Read
1344        } else {
1345            protocol::DataDirection::Write
1346        };
1347        self.state
1348            .main_status
1349            .set_data_direction(dma_type.as_bool());
1350        self.state.main_status.set_main_request(true);
1351
1352        if request_interrupt {
1353            self.raise_interrupt(false);
1354        }
1355    }
1356
1357    // Output bytes should be in reverse order of the output in the 82077AA spec.
1358    // This is because the output bytes are popped off the end of the vector.
1359    // E.g., 7 byte output for READ_ID is ST0, ST1, ST2, C, H, R, N.
1360    // So output_bytes[0] is N, output_bytes[6] is ST0.
1361    fn handle_data_write(&mut self, data: u8) {
1362        // technically proper byte flow would pend on whether rqm bit for main request was enabled
1363        if !self.state.digital_output.controller_enabled() {
1364            // Do not handle commands if we're in a reset state.
1365            return;
1366        }
1367
1368        self.state.input_bytes.push(data);
1369        let command = FloppyCommand(self.state.input_bytes[0]);
1370
1371        // we want this to be below update of input buffer so that we
1372        // don't otherwise misreport what the command byte is
1373        // side effect is multiple trace lines of one command issue
1374        tracing::trace!(
1375            ?data,
1376            ?self.state.input_bytes,
1377            "floppy byte (cmd or param)"
1378        );
1379
1380        self.handle_command(command);
1381    }
1382
1383    fn handle_command(&mut self, command: FloppyCommand) {
1384        if !self.state.output_bytes.is_empty() {
1385            tracelimit::warn_ratelimited!(output_bytes = ?self.state.output_bytes, "Floppy data register write with bytes still pending");
1386        }
1387        self.state.output_bytes.clear();
1388
1389        if self.state.input_bytes.len() < command.input_bytes_needed() {
1390            tracing::debug!(
1391                ?command,
1392                bytes_needed = ?command.input_bytes_needed(),
1393                bytes_received = ?self.state.input_bytes.len(),
1394                "floppy command missing (or waiting for) parameters"
1395            );
1396
1397            // Command is still waiting for more bytes
1398            self.state.main_status.set_busy(true);
1399            return;
1400        }
1401
1402        tracing::trace!(
1403            ?command,
1404            input_bytes = ?self.state.input_bytes,
1405            "executing floppy command"
1406        );
1407
1408        // The controller appears to help along poorly written software
1409        // which does not correctly clear the INT signal by issuing a
1410        // sense-interrupt-status command. If we see a command come
1411        // through which is not a sense-interrupt-status and there
1412        // is already an interrupt pending, we will deassert the INT signal.
1413        if self.state.interrupt_level && command != FloppyCommand::SENSE_INTERRUPT_STATUS {
1414            tracing::trace!(
1415                ?command,
1416                "Floppy interrupt level was high before command execution. Now de-asserting interrupt"
1417            );
1418            self.lower_interrupt();
1419            self.state.main_status.set_active_drives(0);
1420        }
1421
1422        let mut complete_command = true;
1423        let mut request_interrupt = false;
1424
1425        match command {
1426            FloppyCommand::READ_NORMAL_DATA
1427            | FloppyCommand::READ_NORMAL_DEL_DATA
1428            | FloppyCommand::READ_DEL_DATA
1429            | FloppyCommand::SEEK_AND_READ
1430            | FloppyCommand::ALT_SEEK_AND_READ => {
1431                let success = self.handle_read();
1432                request_interrupt = !success;
1433                complete_command = !success;
1434            }
1435            FloppyCommand::WRITE_NORMAL_DATA
1436            | FloppyCommand::WRITE_DATA
1437            | FloppyCommand::WRITE_DEL_DATA
1438            | FloppyCommand::SEEK_AND_WRITE => {
1439                let success = self.handle_write();
1440                request_interrupt = !success;
1441                complete_command = !success;
1442            }
1443            FloppyCommand::READ_TRACK => {
1444                // Set the starting cylinder to 0
1445                self.state.input_bytes[2] = 0;
1446                let success = self.handle_read();
1447                request_interrupt = !success;
1448                complete_command = !success;
1449            }
1450            FloppyCommand::VERSION => {
1451                // magic number returned by 82077AA controllers
1452                self.state.output_bytes.push(0x90);
1453            }
1454            FloppyCommand::FORMAT_TRACK | FloppyCommand::FORMAT_DOUBLE_DENSITY_MODE => {
1455                let success = self.format();
1456                request_interrupt = !success;
1457                complete_command = !success;
1458            }
1459            FloppyCommand::SEEK => {
1460                self.handle_seek();
1461                request_interrupt = true;
1462            }
1463            FloppyCommand::RECALIBRATE => {
1464                self.handle_recalibrate();
1465                request_interrupt = true;
1466            }
1467            FloppyCommand::SENSE_INTERRUPT_STATUS => {
1468                self.handle_sense_interrupt_status();
1469            }
1470            FloppyCommand::SPECIFY => self.handle_specify(),
1471            FloppyCommand::SENSE_DRIVE_STATUS => self.handle_sense_drive_status(),
1472            FloppyCommand::DUMP_REGISTERS => self.handle_dump_registers(),
1473            FloppyCommand::READ_ID => {
1474                self.read_id();
1475                request_interrupt = true;
1476            }
1477
1478            // These commands lock out or unlock software resets. Ignore the lock command but respond as if we care.
1479            // Pass back lock/unlock bit in bit 4.
1480            FloppyCommand::UNLOCK_FIFO_FUNCTIONS => {
1481                self.state.output_bytes.push(0);
1482            }
1483            FloppyCommand::LOCK_FIFO_FUNCTIONS => {
1484                self.state.output_bytes.push(0x10);
1485            }
1486            FloppyCommand::PART_ID => {
1487                self.state.output_bytes.push(0x01);
1488            }
1489            FloppyCommand::CONFIGURE | FloppyCommand::PERP288_MODE => {
1490                // Ignore the data bytes. No response, no interrupt.
1491                tracing::debug!(?command, "command ignored");
1492            }
1493            _ => {
1494                tracelimit::error_ratelimited!(?command, "unimplemented/unsupported command");
1495                self.state.output_bytes.push(INVALID_COMMAND_STATUS);
1496            }
1497        }
1498
1499        // Finished processing command, so no longer need input
1500        self.state.input_bytes.clear();
1501
1502        if !self.state.output_bytes.is_empty() {
1503            if self.state.output_bytes.len() != command.result_bytes_expected() {
1504                tracelimit::warn_ratelimited!(?command, output_bytes = ?self.state.output_bytes, "command output size doesn't match expected");
1505            } else {
1506                tracing::trace!(
1507                    ?command,
1508                    output_bytes = ?self.state.output_bytes,
1509                    "floppy command output"
1510                );
1511            }
1512        }
1513
1514        self.state.pending_command = if complete_command {
1515            self.complete_command(request_interrupt);
1516            FloppyCommand::INVALID
1517        } else {
1518            command
1519        };
1520
1521        tracing::trace!(
1522            main_status = ?self.state.main_status,
1523            digital_output = ?self.state.digital_output,
1524            sense_output = ?self.state.sense_output,
1525            dma_disabled = ?self.state.dma_disabled,
1526            cylinder = ?self.state.position.cylinder,
1527            head = ?self.state.position.head,
1528            sector = ?self.state.position.sector,
1529            interrupt_level = ?self.state.interrupt_level,
1530            "floppy state"
1531        );
1532
1533        tracing::trace!("floppy command completed");
1534    }
1535
1536    fn handle_read(&mut self) -> bool {
1537        // clear floppy changed flag
1538        self.state.internals.floppy_changed = false;
1539
1540        // set interrupt cause
1541        self.get_sense_output().set_seek_end(true);
1542
1543        // clear RQM
1544        self.state.main_status.set_main_request(false);
1545
1546        // per section 4.2.1 of 82077AA spec, can operate with or
1547        // without DMA. We want DMA disabled currently because
1548        // DMA implementation is a stub. self.state.dma_disabled
1549        // is hard-coded to true for now via FloppyCommand::SPECIFY
1550        if self.state.dma_disabled {
1551            tracelimit::warn_ratelimited!("non-dma mode is not supported");
1552            self.state.main_status.set_non_dma_mode(true);
1553        }
1554
1555        // mark drive as busy. this should? always set lsb
1556        let input = protocol::InputRegister::from(self.state.input_bytes[1]);
1557        let busy_drive = input.drive_select();
1558        self.state
1559            .main_status
1560            .set_active_drives(self.state.main_status.active_drives() | (1 << busy_drive));
1561
1562        self.state.main_status.set_busy(true);
1563
1564        self.parse_input_for_readwrite();
1565
1566        let error = !self.read_data();
1567        if error {
1568            self.state.internals.io_pending = false;
1569            self.set_output_status(error, false, error);
1570        }
1571        !error
1572    }
1573
1574    fn read_data(&mut self) -> bool {
1575        if !self.state.internals.floppy_present {
1576            tracelimit::error_ratelimited!("read attempted, but floppy not present");
1577            return false;
1578        }
1579
1580        if self.state.position.sector == 0
1581            || self.state.position.sector > self.state.end_of_track
1582            || self.state.position.sector > self.state.internals.sectors_per_track
1583        {
1584            tracelimit::error_ratelimited!(
1585                position = ?self.state.position,
1586                end_of_track = self.state.end_of_track,
1587                sectors_per_track = self.state.internals.sectors_per_track,
1588                "invalid read position"
1589            );
1590            return false;
1591        }
1592
1593        if self.state.position.cylinder > FLOPPY_TOTAL_CYLINDERS {
1594            tracelimit::error_ratelimited!(sector = ?self.state.position.sector, "bad sector in floppy read");
1595            return false;
1596        }
1597
1598        self.state.internals.io_pending = true;
1599        let size_hint = self.dma.check_transfer_size() as usize;
1600
1601        // now to read the next sector from the floppy
1602        let lba = (self.state.position).chs_to_lba(self.state.internals.sectors_per_track) as u64;
1603
1604        let size = {
1605            let num = (size_hint.div_ceil(STANDARD_FLOPPY_SECTOR_SIZE)
1606                * STANDARD_FLOPPY_SECTOR_SIZE) as u32;
1607            if num < STANDARD_FLOPPY_SECTOR_SIZE as u32 {
1608                STANDARD_FLOPPY_SECTOR_SIZE as u32
1609            } else {
1610                num
1611            }
1612        };
1613
1614        let command_buffer = self.command_buffer.access();
1615
1616        tracing::trace!(lba, size, "starting disk read");
1617        self.set_io(async move |disk| {
1618            let buffers = command_buffer.buffers(0, size as usize, true);
1619            disk.read_vectored(&buffers, lba).await
1620        });
1621
1622        true
1623    }
1624
1625    fn handle_write(&mut self) -> bool {
1626        self.state.internals.floppy_changed = false;
1627
1628        // set interrupt cause
1629        self.get_sense_output().set_seek_end(true);
1630
1631        // clear RQM
1632        self.state.main_status.set_main_request(false);
1633
1634        // per section 4.2.1 of 82077AA spec, can operate with or
1635        // without DMA. We want DMA disabled currently because
1636        // DMA implementation is a stub. self.state.dma_disabled
1637        // is hard-coded to true for now via FloppyCommand::SPECIFY
1638        if self.state.dma_disabled {
1639            tracelimit::warn_ratelimited!("non-dma mode is not supported");
1640            self.state.main_status.set_non_dma_mode(true);
1641        }
1642
1643        // mark drive as busy. this should? always set lsb
1644        let input = protocol::InputRegister::from(self.state.input_bytes[1]);
1645        let busy_drive = input.drive_select();
1646        self.state
1647            .main_status
1648            .set_active_drives(self.state.main_status.active_drives() | (1 << busy_drive));
1649
1650        self.state.main_status.set_busy(true);
1651
1652        self.parse_input_for_readwrite();
1653
1654        let wo_error = self.state.internals.media_write_protected;
1655        let error = if wo_error { true } else { !self.write_data() };
1656
1657        if error {
1658            self.set_output_status(error, wo_error, error);
1659        }
1660        !error
1661    }
1662
1663    fn write_data(&mut self) -> bool {
1664        if !self.state.internals.floppy_present {
1665            tracelimit::error_ratelimited!("write attempted, but floppy not present");
1666            return false;
1667        }
1668
1669        if self.state.position.sector == 0
1670            || self.state.position.sector > self.state.end_of_track
1671            || self.state.position.sector > self.state.internals.sectors_per_track
1672        {
1673            tracelimit::error_ratelimited!(
1674                position = ?self.state.position,
1675                end_of_track = self.state.end_of_track,
1676                sectors_per_track = self.state.internals.sectors_per_track,
1677                "invalid write position"
1678            );
1679            return false;
1680        }
1681
1682        let lba = (self.state.position).chs_to_lba(self.state.internals.sectors_per_track) as u64;
1683        let buffer = match self.dma.request(IsaDmaDirection::Read) {
1684            Some(r) => r,
1685            None => {
1686                tracelimit::error_ratelimited!("request_dma for write failed");
1687                return false;
1688            }
1689        };
1690
1691        let size = buffer.size.div_ceil(STANDARD_FLOPPY_SECTOR_SIZE) * STANDARD_FLOPPY_SECTOR_SIZE;
1692
1693        let command_buffer = self.command_buffer.access();
1694
1695        let buffer_ptr = &self.command_buffer.buffer[0..size as usize][..size as usize];
1696        let r = self.guest_memory.read_to_atomic(buffer.address, buffer_ptr);
1697
1698        self.dma.complete();
1699
1700        if let Err(err) = r {
1701            tracelimit::error_ratelimited!(
1702                error = &err as &dyn std::error::Error,
1703                "dma transfer failed"
1704            );
1705
1706            return false;
1707        }
1708
1709        let DriveRibbon::Single(disk) = &self.disk_drive else {
1710            tracelimit::error_ratelimited!("No disk");
1711            return false;
1712        };
1713
1714        if disk.is_read_only() {
1715            tracelimit::error_ratelimited!("Read only");
1716            return false;
1717        }
1718
1719        self.set_io(async move |disk| {
1720            let buffers = command_buffer.buffers(0, size as usize, false);
1721            let result = disk.write_vectored(&buffers, lba, false).await;
1722            if let Err(err) = result {
1723                tracelimit::error_ratelimited!(
1724                    error = &err as &dyn std::error::Error,
1725                    "write failed"
1726                );
1727                return Err(err);
1728            }
1729            let result = disk.sync_cache().await;
1730            if let Err(err) = result {
1731                tracelimit::error_ratelimited!(
1732                    error = &err as &dyn std::error::Error,
1733                    "flush failed"
1734                );
1735                return Err(err);
1736            }
1737
1738            result
1739        });
1740
1741        true
1742    }
1743
1744    fn write_zeros(&mut self) -> bool {
1745        let DriveRibbon::Single(disk) = &self.disk_drive else {
1746            tracelimit::error_ratelimited!("No disk");
1747            return false;
1748        };
1749
1750        if disk.is_read_only() {
1751            tracelimit::error_ratelimited!("Read only");
1752            return false;
1753        }
1754
1755        let buffer = match self.dma.request(IsaDmaDirection::Read) {
1756            Some(r) => r,
1757            None => {
1758                tracelimit::error_ratelimited!("request_dma for format failed");
1759                return false;
1760            }
1761        };
1762
1763        let size = (buffer.size.div_ceil(STANDARD_FLOPPY_SECTOR_SIZE) * STANDARD_FLOPPY_SECTOR_SIZE)
1764            as u32;
1765
1766        let command_buffer = self.command_buffer.access();
1767
1768        let buffer_ptr = &self.command_buffer.buffer[0..size as usize][..size as usize];
1769        let r = self.guest_memory.read_to_atomic(buffer.address, buffer_ptr);
1770
1771        self.dma.complete();
1772
1773        if let Err(err) = r {
1774            tracelimit::error_ratelimited!(
1775                error = &err as &dyn std::error::Error,
1776                "dma transfer failed"
1777            );
1778
1779            return false;
1780        }
1781
1782        let Some(cylinder) = buffer_ptr.first() else {
1783            tracelimit::error_ratelimited!("failed to get(0)");
1784            return false;
1785        };
1786
1787        let cylinder = cylinder.load(Ordering::Relaxed) as u64;
1788
1789        let Some(head) = buffer_ptr.get(1) else {
1790            tracelimit::error_ratelimited!("failed to get(1)");
1791            return false;
1792        };
1793
1794        let head = head.load(Ordering::Relaxed) as u64;
1795
1796        let size = STANDARD_FLOPPY_SECTOR_SIZE * self.state.internals.sectors_per_track as usize;
1797        let buffers = command_buffer.buffers(0, size, false);
1798
1799        let res = buffers.guest_memory().zero_range(&buffers.range());
1800        if let Err(err) = res {
1801            tracelimit::error_ratelimited!(
1802                error = &err as &dyn std::error::Error,
1803                "zero_range failed"
1804            );
1805            return false;
1806        }
1807
1808        let lba = (cylinder * 2 + head) * self.state.internals.sectors_per_track as u64;
1809
1810        tracing::trace!(?cylinder, ?head, ?lba, ?buffer_ptr, "Format: ");
1811
1812        self.set_io(async move |disk| {
1813            let buffers = command_buffer.buffers(0, size, false);
1814            let result = disk.write_vectored(&buffers, lba, false).await;
1815            if let Err(err) = result {
1816                tracelimit::error_ratelimited!(
1817                    error = &err as &dyn std::error::Error,
1818                    "write failed"
1819                );
1820                return Err(err);
1821            }
1822            let result = disk.sync_cache().await;
1823            if let Err(err) = result {
1824                tracelimit::error_ratelimited!(
1825                    error = &err as &dyn std::error::Error,
1826                    "flush failed"
1827                );
1828                return Err(err);
1829            }
1830            result
1831        });
1832
1833        true
1834    }
1835
1836    fn format(&mut self) -> bool {
1837        let wo_err_occurred = self.state.internals.media_write_protected;
1838        let error = if wo_err_occurred {
1839            true
1840        } else {
1841            self.state.main_status.set_busy(true);
1842            !self.write_zeros()
1843        };
1844
1845        if error {
1846            self.set_output_status(error, wo_err_occurred, true);
1847        }
1848        !error
1849    }
1850
1851    fn handle_seek(&mut self) {
1852        self.state.internals.floppy_changed = false;
1853
1854        self.state.position.sector = 0;
1855
1856        let input = protocol::InputRegister::from(self.state.input_bytes[1]);
1857        self.state.position.head = input.head();
1858
1859        self.state.position.cylinder = if self.state.input_bytes[2] >= FLOPPY_TOTAL_CYLINDERS {
1860            tracelimit::warn_ratelimited!(?self.state.position.cylinder, "Floppy seek to cylinder > 80");
1861            0
1862        } else {
1863            self.state.input_bytes[2] // this is the new cylinder number
1864        };
1865
1866        self.recalibrate();
1867    }
1868
1869    fn handle_recalibrate(&mut self) {
1870        self.state.position.cylinder = 0;
1871        self.recalibrate();
1872    }
1873
1874    fn recalibrate(&mut self) {
1875        if let Some(SenseOutput::ResetCounter { .. }) = self.state.sense_output {
1876            self.state.sense_output = None;
1877        }
1878
1879        // We don't have any hardware, e.g., read/write head, that needs
1880        // to move, so just immediately signal completion. These commands
1881        // can interrupt a reset sequence, most can't.
1882        // Also, both arms cause reset stage set to 0 implicitly
1883        let head = self.state.position.head;
1884        self.get_sense_output().set_seek_end(true);
1885        self.get_sense_output().set_head(head);
1886
1887        // Set the appropriate disk to active
1888        let input = protocol::InputRegister::from(self.state.input_bytes[1]);
1889        let busy_drive = input.drive_select();
1890        self.state
1891            .main_status
1892            .set_active_drives(self.state.main_status.active_drives() | (1 << busy_drive));
1893        if busy_drive > 0 {
1894            tracelimit::warn_ratelimited!(
1895                ?busy_drive,
1896                "Floppy seek to drive outside of what is supported"
1897            );
1898        }
1899    }
1900
1901    fn handle_specify(&mut self) {
1902        let param1 = protocol::SpecifyParam1::from(self.state.input_bytes[1]);
1903        let param2 = protocol::SpecifyParam2::from(self.state.input_bytes[2]);
1904
1905        self.state.head_unload_timer = param1.head_unload_timer();
1906        self.state.step_rate_time = param1.step_rate_time();
1907        self.state.head_load_timer = param2.head_load_timer();
1908        self.state.dma_disabled = param2.dma_disabled();
1909    }
1910
1911    fn handle_dump_registers(&mut self) {
1912        self.state.output_bytes.push(self.state.position.cylinder);
1913        self.state.output_bytes.push(0); // drive 1 cur cylinder (PCN), drive disabled -> 0
1914        self.state.output_bytes.push(0); // drive 2 PCN. drive disabled, so default 0
1915        self.state.output_bytes.push(0); // drive 3 PCN. drive disabled, so default 0
1916
1917        self.state.output_bytes.push(
1918            protocol::SpecifyParam1::new()
1919                .with_head_unload_timer(self.state.head_unload_timer)
1920                .with_step_rate_time(self.state.step_rate_time)
1921                .into(),
1922        );
1923        self.state.output_bytes.push(
1924            protocol::SpecifyParam2::new()
1925                .with_head_load_timer(self.state.head_load_timer)
1926                .with_dma_disabled(self.state.dma_disabled)
1927                .into(),
1928        );
1929
1930        // TODO: Sector per track should not be 0, if disk is inserted / formatted
1931        self.state.output_bytes.push(0); // SC (Number of Sectors, per track). aka EOT (end of track/number of final sector)
1932        self.state.output_bytes.push(0); // various flags dealing with PERP288
1933        self.state.output_bytes.push(0); // configure info (never set?)
1934
1935        // TODO: write precomp enum and setting
1936        self.state.output_bytes.push(0); // write precomp start track no. (never set?)
1937    }
1938
1939    fn read_id(&mut self) {
1940        // handle input
1941        let input = protocol::InputRegister::from(self.state.input_bytes[1]);
1942        self.state.position.head = input.head();
1943
1944        // handle output
1945        self.set_output_status(false, false, false);
1946        let head = self.state.position.head;
1947        self.get_sense_output().set_head(head);
1948    }
1949}
1950
1951mod save_restore {
1952    use super::*;
1953    use vmcore::save_restore::RestoreError;
1954    use vmcore::save_restore::SaveError;
1955    use vmcore::save_restore::SaveRestore;
1956
1957    mod state {
1958        use mesh::payload::Protobuf;
1959        use vmcore::save_restore::SavedStateRoot;
1960
1961        #[derive(Protobuf, SavedStateRoot)]
1962        #[mesh(package = "chipset.floppy")]
1963        pub struct SavedState {
1964            #[mesh(1)]
1965            pub digital_output: u8,
1966            #[mesh(2)]
1967            pub main_status: u8,
1968            #[mesh(3)]
1969            pub input_bytes: Vec<u8>,
1970            #[mesh(4)]
1971            pub output_bytes: Vec<u8>,
1972            #[mesh(5)]
1973            pub head_unload_timer: u8,
1974            #[mesh(6)]
1975            pub step_rate_time: u8,
1976            #[mesh(7)]
1977            pub head_load_timer: u8,
1978            #[mesh(8)]
1979            pub dma_disabled: bool,
1980            #[mesh(9)]
1981            pub interrupt_output: Option<SavedInterruptOutput>,
1982            #[mesh(10)]
1983            pub interrupt_level: bool,
1984
1985            #[mesh(11)]
1986            pub end_of_track: u8,
1987            // Below fields are for future-proofing:
1988            // Unused today as we only support one drive.
1989            #[mesh(12)]
1990            pub drive: u8,
1991            // Only cylinder of the first floppy is used today.
1992            #[mesh(13)]
1993            pub floppies: [SavedFloppyState; 1],
1994            #[mesh(14)]
1995            pub pending_command: u8,
1996        }
1997
1998        #[derive(Protobuf)]
1999        #[mesh(package = "chipset.floppy")]
2000        pub struct SavedFloppyState {
2001            #[mesh(1)]
2002            pub cylinder: u8,
2003            #[mesh(2)]
2004            pub head: u8,
2005            #[mesh(3)]
2006            pub sector: u8,
2007            #[mesh(4)]
2008            pub internals: SavedFloppyStateInternals,
2009        }
2010
2011        #[derive(Protobuf)]
2012        #[mesh(package = "chipset.floppy")]
2013        pub enum SavedInterruptOutput {
2014            #[mesh(1)]
2015            ResetCounter {
2016                #[mesh(1)]
2017                count: u8,
2018            },
2019            #[mesh(2)]
2020            Value {
2021                #[mesh(1)]
2022                value: u8,
2023            },
2024        }
2025
2026        impl From<SavedInterruptOutput> for super::SenseOutput {
2027            fn from(value: SavedInterruptOutput) -> Self {
2028                match value {
2029                    SavedInterruptOutput::ResetCounter { count } => {
2030                        super::SenseOutput::ResetCounter { count }
2031                    }
2032                    SavedInterruptOutput::Value { value } => super::SenseOutput::Value {
2033                        value: super::protocol::StatusRegister0::from(value),
2034                    },
2035                }
2036            }
2037        }
2038
2039        impl From<super::SenseOutput> for SavedInterruptOutput {
2040            fn from(value: super::SenseOutput) -> Self {
2041                match value {
2042                    super::SenseOutput::ResetCounter { count } => {
2043                        SavedInterruptOutput::ResetCounter { count }
2044                    }
2045                    super::SenseOutput::Value { value } => SavedInterruptOutput::Value {
2046                        value: u8::from(value),
2047                    },
2048                }
2049            }
2050        }
2051
2052        #[derive(Protobuf, Clone, Copy)]
2053        #[mesh(package = "chipset.floppy")]
2054        pub struct SavedFloppyStateInternals {
2055            #[mesh(1)]
2056            floppy_changed: bool,
2057            #[mesh(2)]
2058            floppy_present: bool,
2059            #[mesh(3)]
2060            media_write_protected: bool,
2061            #[mesh(4)]
2062            io_pending: bool,
2063
2064            #[mesh(5)]
2065            num_bytes_rd: u32,
2066            #[mesh(6)]
2067            num_bytes_wr: u32,
2068            #[mesh(7)]
2069            sectors_per_track: u8,
2070            #[mesh(8)]
2071            start_sector_pos: u32,
2072            #[mesh(9)]
2073            sector_cache_start_logical: u32,
2074            #[mesh(10)]
2075            sector_cache_end_logical: u32,
2076        }
2077
2078        impl From<super::FloppyStateInternals> for SavedFloppyStateInternals {
2079            fn from(value: super::FloppyStateInternals) -> Self {
2080                let super::FloppyStateInternals {
2081                    floppy_changed,
2082                    floppy_present,
2083                    media_write_protected,
2084                    io_pending,
2085                    num_bytes_rd,
2086                    num_bytes_wr,
2087                    sectors_per_track,
2088                    start_sector_pos,
2089                    sector_cache_start_logical,
2090                    sector_cache_end_logical,
2091                } = value;
2092
2093                Self {
2094                    floppy_changed,
2095                    floppy_present,
2096                    media_write_protected,
2097                    io_pending,
2098                    num_bytes_rd,
2099                    num_bytes_wr,
2100                    sectors_per_track,
2101                    start_sector_pos,
2102                    sector_cache_start_logical,
2103                    sector_cache_end_logical,
2104                }
2105            }
2106        }
2107
2108        impl From<SavedFloppyStateInternals> for super::FloppyStateInternals {
2109            fn from(value: SavedFloppyStateInternals) -> Self {
2110                let SavedFloppyStateInternals {
2111                    floppy_changed,
2112                    floppy_present,
2113                    media_write_protected,
2114                    io_pending,
2115                    num_bytes_rd,
2116                    num_bytes_wr,
2117                    sectors_per_track,
2118                    start_sector_pos,
2119                    sector_cache_start_logical,
2120                    sector_cache_end_logical,
2121                } = value;
2122
2123                Self {
2124                    floppy_changed,
2125                    floppy_present,
2126                    media_write_protected,
2127                    io_pending,
2128                    num_bytes_rd,
2129                    num_bytes_wr,
2130                    sectors_per_track,
2131                    start_sector_pos,
2132                    sector_cache_start_logical,
2133                    sector_cache_end_logical,
2134                }
2135            }
2136        }
2137    }
2138
2139    impl SaveRestore for FloppyDiskController {
2140        type SavedState = state::SavedState;
2141
2142        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
2143            let FloppyState {
2144                digital_output,
2145                main_status,
2146                ref input_bytes,
2147                ref output_bytes,
2148                head_unload_timer,
2149                step_rate_time,
2150                head_load_timer,
2151                dma_disabled,
2152                sense_output: ref interrupt_output,
2153                interrupt_level,
2154                position,
2155                internals,
2156                end_of_track,
2157                pending_command,
2158            } = self.state;
2159
2160            let saved_state = state::SavedState {
2161                digital_output: digital_output.into(),
2162                main_status: main_status.into(),
2163                input_bytes: input_bytes.to_vec(),
2164                output_bytes: output_bytes.to_vec(),
2165                head_unload_timer,
2166                step_rate_time,
2167                head_load_timer,
2168                dma_disabled,
2169                interrupt_output: interrupt_output.clone().map(|x| x.into()),
2170                interrupt_level,
2171                end_of_track,
2172                drive: 0,
2173                floppies: [state::SavedFloppyState {
2174                    cylinder: position.cylinder,
2175                    head: position.head,
2176                    sector: position.sector,
2177                    internals: internals.into(),
2178                }],
2179                pending_command: pending_command.0,
2180            };
2181
2182            Ok(saved_state)
2183        }
2184
2185        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
2186            let state::SavedState {
2187                digital_output,
2188                main_status,
2189                input_bytes,
2190                output_bytes,
2191                head_unload_timer,
2192                step_rate_time,
2193                head_load_timer,
2194                dma_disabled,
2195                interrupt_output,
2196                interrupt_level,
2197                end_of_track,
2198                drive: _,
2199                floppies,
2200                pending_command,
2201            } = state;
2202
2203            self.state = FloppyState {
2204                digital_output: digital_output.into(),
2205                main_status: main_status.into(),
2206                input_bytes: input_bytes.as_slice().try_into().map_err(
2207                    |e: arrayvec::CapacityError| RestoreError::InvalidSavedState(e.into()),
2208                )?,
2209                output_bytes: output_bytes.as_slice().try_into().map_err(
2210                    |e: arrayvec::CapacityError| RestoreError::InvalidSavedState(e.into()),
2211                )?,
2212                head_unload_timer,
2213                step_rate_time,
2214                head_load_timer,
2215                dma_disabled,
2216                sense_output: interrupt_output.map(|x| x.into()),
2217                interrupt_level,
2218                end_of_track,
2219                position: ReadWriteHeadLocation {
2220                    cylinder: floppies[0].cylinder,
2221                    head: floppies[0].head,
2222                    sector: floppies[0].sector,
2223                },
2224                internals: floppies[0].internals.into(),
2225                pending_command: FloppyCommand(pending_command),
2226            };
2227
2228            self.rt.interrupt.set_level(interrupt_level);
2229
2230            Ok(())
2231        }
2232    }
2233}