floppy_pcat_stub/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Stub Intel 82077AA Floppy Disk Controller, implementing a minimal subset of
5//! functionality required to boot using the Microsoft PCAT BIOS.
6//!
7//! It will unconditionally report that no floppy drives are present.
8
9#![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, // Read-only
38        STATUS_B = 1, // Read-only
39        DIGITAL_OUTPUT = 2,
40        TAPE_DRIVE = 3, // Obsolete
41        MAIN_STATUS = 4, // Read-only
42        DATA_RATE = 4, // Write-only
43        DATA = 5,
44        DIGITAL_INPUT = 7,// Read-only
45        CONFIG_CONTROL = 7, // Write-only
46    }
47}
48
49/// Floppy DOR - digital output register
50#[derive(Inspect)]
51#[bitfield(u8)]
52pub struct DigitalOutput {
53    #[bits(2)]
54    _drive_select: u8,
55    controller_enabled: bool,
56    dma_enabled: bool,
57    // This is really 4 separate bools, but for our convenience we treat
58    // it as a large number.
59    #[bits(4)]
60    motors_active: u8,
61}
62
63/// Floppy main status register
64#[derive(Inspect)]
65#[bitfield(u8)]
66pub struct MainStatus {
67    // This is really 4 separate bools, but for our convenience we treat
68    // it as a large number.
69    #[bits(4)]
70    active_drives: u8,
71    /// Indicates if the controller is currently executing a command
72    busy: bool,
73    _non_dma_mode: bool,
74    /// Data input/output (1 - output data to CPU, 0 - receive data from CPU).
75    /// Holds no meaning if main_request is not set.
76    data_direction: bool,
77    /// Indicates whether controller is ready to receive or send
78    /// data or commands via the data registers
79    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    // Floppy commands are written one byte at a time to the DATA register. The
103    // first byte specifies the issued command. The remaining bytes are used as
104    // inputs for the command.
105    fn input_bytes_needed(&self) -> usize {
106        // Add one to account for the command byte itself
107        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
145// Must implement this trait so this can "slot-in" where the real floppy
146// controller would be
147impl 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            // This port is completely unsupported by latest floppy controllers.
162            RegisterOffset::STATUS_A => 0xFF,
163            // Also unsupported but return 0xFC to indicate no tape drives present.
164            RegisterOffset::STATUS_B => NO_TAPE_DRIVES_PRESENT,
165            // Do nothing. This port is obsolete.
166            RegisterOffset::TAPE_DRIVE => 0xFF,
167            // Now the ports that actually do something.
168            RegisterOffset::DIGITAL_OUTPUT => self.state.digital_output.0,
169            RegisterOffset::MAIN_STATUS => {
170                // Indicate data register is ready for reading/writing.
171                if self.state.digital_output.controller_enabled() {
172                    self.state.main_status.0
173                } else {
174                    0
175                }
176            }
177            RegisterOffset::DATA => {
178                // If there are more bytes left to read then read them out now.
179                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                        // Reverse direction, now ready to receive a new command
183                        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                // The bottom seven bits are tristated, and always read as
193                // ones on a real floppy controller.
194                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 => {} // Do nothing. This port is obsolete.
224            RegisterOffset::CONFIG_CONTROL => {} // ignore writes
225            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                    // Always trigger a reset interrupt, even though DMA will be disabled
232                    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                    // Always trigger a reset interrupt, regardless of DMA configuration
248                    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                    // Do not handle commands if we're in a reset state.
264                    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                        // Head timing information is returned as part of the
290                        // DUMP REGISTERS command. This command also specifies
291                        // whether DMA is enabled but this is ignored for now.
292                        self.state.scd = [self.state.input_bytes[1], self.state.input_bytes[2]];
293                    }
294                    FloppyCommand::SENSE_DRIVE_STATUS => {
295                        // The lowest bit specifies the drive number, the next
296                        // is the track, the last is the head.
297                        // These get reported back in the output.
298                        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                        // We don't have any hardware that needs to move, so just
317                        // immediately signal completion. These commands can interrupt
318                        // a reset sequence, most can't.
319                        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                        // Set the appropriate disk to active
330                        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); // drive 1 cur cylinder, drive disabled -> 0
366                        self.state.output_bytes.push(0); // unknown hardcoded 0, maybe drive 2?
367                        self.state.output_bytes.push(0); // unknown hardcoded 0, maybe drive 3?
368                        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); // cur floppy sectors per track, no media -> 0
371                        self.state.output_bytes.push(0); // unknown hardcoded 0, perpendicular info?
372                        self.state.output_bytes.push(0); // configure info (never set?)
373                        self.state.output_bytes.push(0); // write precomp (never set?)
374                    }
375                    FloppyCommand::VERSION => {
376                        self.state.output_bytes.push(ENHANCED_CONTROLLER_VERSION);
377                    }
378                    FloppyCommand::PERP288_MODE => {} // Ignore the data byte. No response, no interrupt.
379                    FloppyCommand::CONFIGURE => {} // Ignore the data bytes. No response, no interrupt.
380                    FloppyCommand::PART_ID => {
381                        self.state.output_bytes.push(0x01);
382                    }
383                    // These commands lock out or unlock software resets. Ignore the lock command but respond as if we care.
384                    // Pass back lock/unlock bit in bit 4.
385                    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                    // Sets IO direction to Controller -> Host
403                    self.state.main_status.set_data_direction(true);
404                }
405
406                // Possibly add PCAT BIOS wait cancellation enlightenment to indicate
407                // emulated device activity.
408            }
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    // Used for command input
422    #[inspect(bytes)]
423    input_bytes: ArrayVec<u8, FIFO_SIZE>,
424
425    // Used for output status/results
426    #[inspect(bytes)]
427    output_bytes: ArrayVec<u8, FIFO_SIZE>,
428
429    scd: [u8; 2],
430
431    sense_output: Option<SenseOutput>,
432
433    // HACK: Our DSDT always reports that only 1 drive is available.
434    // If this changes in the future proper drive selection and indexing will
435    // need to be implemented here.
436    cur_cylinder: u8,
437
438    // Needed for save/restore
439    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/// Stub implementation of the Intel 82077AA Floppy Disk Controller.
472#[derive(InspectMut)]
473pub struct StubFloppyDiskController {
474    // Runtime glue
475    rt: FloppyRt,
476
477    // Volatile state
478    state: FloppyState,
479}
480
481impl StubFloppyDiskController {
482    /// Create a new `StubFloppyDiskController` instance.
483    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        // take note of the 1-byte "hole" in this register space!
493        // it is important, as it turns out that IDE controllers like to claim this port for themselves!
494        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    /// Return the offset of `addr` from the region's base address.
507    ///
508    /// Returns `None` if the provided `addr` is outside of the memory
509    /// region, or the region is currently unmapped.
510    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        // Main request will always be true for us as we don't support actually
543        // returning any data or delaying interrupts today. If these conditions
544        // change then more careful handling of main request may be necessary.
545        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            // Below fields are for future-proofing:
582            // Unused today as we only support one drive.
583            #[mesh(8)]
584            pub cur_drive: u8,
585            // Only cur_cylinder of the first floppy is used today.
586            #[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}