chipset_legacy/winbond83977_sio/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Combo Floppy controller + SuperIO config controller, as specified by the
5//! Winbond W83977ATF SIO chipset.
6//!
7//! SIO Extended Function registers are shared with floppy disk controller
8//! registers. IO port reads/writes are forwarded to SIO config controller when
9//! the chipset is in config mode and are forwarded to the FDC otherwise.
10
11#![warn(missing_docs)]
12
13pub use self::maybe_floppy_disk_controller::MaybeStubFloppyDiskController;
14
15use self::super_io::SioController;
16use chipset_device::ChipsetDevice;
17use chipset_device::io::IoError;
18use chipset_device::io::IoResult;
19use chipset_device::pio::PortIoIntercept;
20use chipset_device::pio::RegisterPortIoIntercept;
21use chipset_device::poll_device::PollDevice;
22use floppy::DriveRibbon;
23use guestmem::GuestMemory;
24use inspect::InspectMut;
25use std::task::Context;
26use thiserror::Error;
27use vmcore::device_state::ChangeDeviceState;
28use vmcore::isa_dma_channel::IsaDmaChannel;
29use vmcore::line_interrupt::LineInterrupt;
30
31mod super_io;
32
33const PRI_FLOPPY_BASE_ADDR: u16 = 0x3F0;
34const SEC_FLOPPY_BASE_ADDR: u16 = 0x370;
35
36const PRI_EXT_FUNC_ENABLE_REG: u16 = 0x3F0;
37const SEC_EXT_FUNC_ENABLE_REG: u16 = 0x370;
38const PRI_EXT_FUNC_DATA_REG: u16 = 0x3F1;
39const SEC_EXT_FUNC_DATA_REG: u16 = 0x371;
40
41/// Combo Floppy controller + SuperIO config controller, as specified by the
42/// Winbond W83977ATF SIO chipset.
43///
44/// DEVNOTE: This device simply *aggregates* multiple sub-devices into a single
45/// `ChipsetDevice`, accounting for weird quirks like overlapping port-io and
46/// shared interrupt lines.
47///
48/// Notably: this device contains no additional volatile state that needs to be
49/// saved/restored, outside of the state of its sub-devices.
50#[derive(InspectMut)]
51pub struct Winbond83977FloppySioDevice<FDC: MaybeStubFloppyDiskController> {
52    // Sub-emulators
53    #[inspect(mut)]
54    sio: SioController,
55    #[inspect(mut)]
56    primary_fdc: FDC,
57    #[inspect(mut)]
58    secondary_fdc: FDC,
59}
60
61#[derive(Debug, Error)]
62#[expect(missing_docs)]
63pub enum NewWinbond83977FloppySioDeviceError<FdcError> {
64    #[error("failed to share interrupt line")]
65    LineShare(#[source] vmcore::line_interrupt::NewLineError),
66    #[error("failed to init primary floppy controller")]
67    BadPrimaryFdc(#[source] FdcError),
68    #[error("failed to init secondary floppy controller")]
69    BadSecondaryFdc(#[source] FdcError),
70}
71
72impl<FDC: MaybeStubFloppyDiskController> Winbond83977FloppySioDevice<FDC> {
73    /// Create a new `Winbond83977FloppySioDevice`
74    pub fn new(
75        guest_memory: GuestMemory,
76        interrupt: LineInterrupt,
77        register_pio: &mut dyn RegisterPortIoIntercept,
78        primary_disk_drive: DriveRibbon,
79        secondary_disk_drive: DriveRibbon,
80        primary_dma: Box<dyn IsaDmaChannel>,
81        secondary_dma: Box<dyn IsaDmaChannel>,
82    ) -> Result<Self, NewWinbond83977FloppySioDeviceError<FDC::NewError>> {
83        let secondary_interrupt = interrupt
84            .new_shared("floppy secondary")
85            .map_err(NewWinbond83977FloppySioDeviceError::LineShare)?;
86
87        Ok(Self {
88            sio: SioController::default(),
89            primary_fdc: FDC::new(
90                guest_memory.clone(),
91                interrupt,
92                register_pio,
93                PRI_FLOPPY_BASE_ADDR,
94                primary_disk_drive,
95                primary_dma,
96            )
97            .map_err(NewWinbond83977FloppySioDeviceError::BadPrimaryFdc)?,
98            secondary_fdc: FDC::new(
99                guest_memory,
100                secondary_interrupt,
101                register_pio,
102                SEC_FLOPPY_BASE_ADDR,
103                secondary_disk_drive,
104                secondary_dma,
105            )
106            .map_err(NewWinbond83977FloppySioDeviceError::BadSecondaryFdc)?,
107        })
108    }
109}
110
111impl<FDC: MaybeStubFloppyDiskController> ChangeDeviceState for Winbond83977FloppySioDevice<FDC> {
112    fn start(&mut self) {}
113
114    async fn stop(&mut self) {}
115
116    async fn reset(&mut self) {
117        self.sio.reset().await;
118        self.primary_fdc.reset().await;
119        self.secondary_fdc.reset().await;
120    }
121}
122
123impl<FDC: MaybeStubFloppyDiskController> ChipsetDevice for Winbond83977FloppySioDevice<FDC> {
124    fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
125        Some(self)
126    }
127
128    fn supports_poll_device(&mut self) -> Option<&mut dyn PollDevice> {
129        Some(self)
130    }
131}
132
133impl<FDC: MaybeStubFloppyDiskController> PollDevice for Winbond83977FloppySioDevice<FDC> {
134    fn poll_device(&mut self, cx: &mut Context<'_>) {
135        self.primary_fdc.poll_device(cx);
136        self.secondary_fdc.poll_device(cx);
137    }
138}
139
140impl<FDC: MaybeStubFloppyDiskController> PortIoIntercept for Winbond83977FloppySioDevice<FDC> {
141    fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
142        if data.len() != 1 {
143            return IoResult::Err(IoError::InvalidAccessSize);
144        }
145
146        if io_port == PRI_EXT_FUNC_DATA_REG || io_port == SEC_EXT_FUNC_DATA_REG {
147            // If read from SIO extended function register fails then fall back
148            // to reading from floppy port.
149            if let Ok(value) = self.sio.config_read() {
150                data[0] = value;
151                return IoResult::Ok;
152            }
153        }
154
155        if self.primary_fdc.offset_of(io_port).is_some() {
156            return self.primary_fdc.io_read(io_port, data);
157        }
158
159        if self.secondary_fdc.offset_of(io_port).is_some() {
160            return self.secondary_fdc.io_read(io_port, data);
161        }
162
163        IoResult::Err(IoError::InvalidRegister)
164    }
165
166    fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
167        if data.len() != 1 {
168            return IoResult::Err(IoError::InvalidAccessSize);
169        }
170
171        // Floppy controller STATUS_A (offset = 0) and STATUS_B (offset = 1) registers
172        // are read-only so forward all writes to these registers to SIO controller.
173        match io_port {
174            PRI_EXT_FUNC_ENABLE_REG | SEC_EXT_FUNC_ENABLE_REG => {
175                self.sio.update_config_state(data[0]);
176            }
177            PRI_EXT_FUNC_DATA_REG | SEC_EXT_FUNC_DATA_REG => self.sio.config_write(data[0]),
178            _ => {
179                if self.primary_fdc.offset_of(io_port).is_some() {
180                    return self.primary_fdc.io_write(io_port, data);
181                }
182
183                if self.secondary_fdc.offset_of(io_port).is_some() {
184                    return self.secondary_fdc.io_write(io_port, data);
185                }
186
187                return IoResult::Err(IoError::InvalidRegister);
188            }
189        }
190
191        IoResult::Ok
192    }
193}
194
195mod maybe_floppy_disk_controller {
196    use chipset_device::ChipsetDevice;
197    use chipset_device::pio::PortIoIntercept;
198    use chipset_device::pio::RegisterPortIoIntercept;
199    use chipset_device::poll_device::PollDevice;
200    use floppy::DriveRibbon;
201    use guestmem::GuestMemory;
202    use inspect::InspectMut;
203    use vmcore::device_state::ChangeDeviceState;
204    use vmcore::isa_dma_channel::IsaDmaChannel;
205    use vmcore::line_interrupt::LineInterrupt;
206
207    /// Trait that abstracts over different floppy controller implementations.
208    ///
209    /// This trait allows re-using the same code for both the fully-featured floppy
210    /// controller, and the stub floppy controller (which is required when booting
211    /// via BIOS using the Microsoft PCAT firmware blob).
212    pub trait MaybeStubFloppyDiskController:
213        Sized
214        + ChipsetDevice
215        + ChangeDeviceState
216        + InspectMut
217        + PollDevice
218        + PortIoIntercept
219        + vmcore::save_restore::SaveRestore
220    {
221        /// Error type returned by `new()`
222        type NewError: std::error::Error + Send + Sync + 'static;
223
224        /// Create a new `FloppyDiskController`
225        fn new(
226            guest_memory: GuestMemory,
227            interrupt: LineInterrupt,
228            register_pio: &mut dyn RegisterPortIoIntercept,
229            pio_base_addr: u16,
230            disk_drive: DriveRibbon,
231            dma: Box<dyn IsaDmaChannel>,
232        ) -> Result<Self, Self::NewError>;
233
234        /// Return the offset of the given IO port, if it is handled by this
235        /// device.
236        fn offset_of(&self, io_port: u16) -> Option<u16>;
237    }
238
239    impl MaybeStubFloppyDiskController for floppy::FloppyDiskController {
240        type NewError = floppy::NewFloppyDiskControllerError;
241
242        fn new(
243            guest_memory: GuestMemory,
244            interrupt: LineInterrupt,
245            register_pio: &mut dyn RegisterPortIoIntercept,
246            pio_base_addr: u16,
247            disk_drive: DriveRibbon,
248            dma: Box<dyn IsaDmaChannel>,
249        ) -> Result<Self, Self::NewError> {
250            Self::new(
251                guest_memory,
252                interrupt,
253                register_pio,
254                pio_base_addr,
255                disk_drive,
256                dma,
257            )
258        }
259
260        fn offset_of(&self, io_port: u16) -> Option<u16> {
261            self.offset_of(io_port)
262        }
263    }
264
265    impl MaybeStubFloppyDiskController for floppy_pcat_stub::StubFloppyDiskController {
266        type NewError = std::convert::Infallible;
267
268        fn new(
269            _guest_memory: GuestMemory,
270            interrupt: LineInterrupt,
271            register_pio: &mut dyn RegisterPortIoIntercept,
272            pio_base_addr: u16,
273            _disk_drive: DriveRibbon,
274            _dma: Box<dyn IsaDmaChannel>,
275        ) -> Result<Self, Self::NewError> {
276            Ok(Self::new(interrupt, register_pio, pio_base_addr))
277        }
278
279        fn offset_of(&self, io_port: u16) -> Option<u16> {
280            self.offset_of(io_port)
281        }
282    }
283}
284
285mod save_restore {
286    use super::*;
287    use vmcore::save_restore::RestoreError;
288    use vmcore::save_restore::SaveError;
289    use vmcore::save_restore::SaveRestore;
290
291    mod state {
292        use super::super_io::SioController;
293        use mesh::payload::Protobuf;
294        use vmcore::save_restore::SaveRestore;
295        use vmcore::save_restore::SavedStateRoot;
296
297        // would be nice to call this "chipset.winbond83977_superio_stub", but
298        // we can't easily change it without breaking compat with earlier
299        // underhill revisions
300        //
301        // in the future, there will be more robust saved state infrastructure
302        // that would allow these sorts of transforms... but that code doesn't
303        // exist just yet.
304        #[derive(Protobuf, SavedStateRoot)]
305        #[mesh(package = "chipset.winbond83977_superio", rename = "SavedState")]
306        pub struct StubSavedState {
307            #[mesh(1)]
308            pub floppy1: <floppy_pcat_stub::StubFloppyDiskController as SaveRestore>::SavedState,
309            #[mesh(2)]
310            pub floppy2: <floppy_pcat_stub::StubFloppyDiskController as SaveRestore>::SavedState,
311            #[mesh(3)]
312            pub sio: <SioController as SaveRestore>::SavedState,
313        }
314
315        #[derive(Protobuf, SavedStateRoot)]
316        #[mesh(package = "chipset.winbond83977_superio_nonstub")]
317        pub struct FullSavedState {
318            #[mesh(1)]
319            pub floppy1: <floppy::FloppyDiskController as SaveRestore>::SavedState,
320            #[mesh(2)]
321            pub floppy2: <floppy::FloppyDiskController as SaveRestore>::SavedState,
322            #[mesh(3)]
323            pub sio: <SioController as SaveRestore>::SavedState,
324        }
325    }
326
327    macro_rules! impl_save_restore {
328        ($saved_sate:ident, $ty:path) => {
329            impl SaveRestore for Winbond83977FloppySioDevice<$ty> {
330                type SavedState = state::$saved_sate;
331
332                fn save(&mut self) -> Result<Self::SavedState, SaveError> {
333                    let saved_state = state::$saved_sate {
334                        floppy1: self.primary_fdc.save()?,
335                        floppy2: self.secondary_fdc.save()?,
336                        sio: self.sio.save()?,
337                    };
338                    Ok(saved_state)
339                }
340
341                fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
342                    let state::$saved_sate {
343                        floppy1,
344                        floppy2,
345                        sio,
346                    } = state;
347
348                    self.primary_fdc.restore(floppy1)?;
349                    self.secondary_fdc.restore(floppy2)?;
350                    self.sio.restore(sio)?;
351                    Ok(())
352                }
353            }
354        };
355    }
356
357    impl_save_restore!(StubSavedState, floppy_pcat_stub::StubFloppyDiskController);
358    impl_save_restore!(FullSavedState, floppy::FloppyDiskController);
359}