Skip to main content

ide/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Legacy PCI/ISA IDE controller emulator (PIIX4-compatible).
5//!
6//! Emulates the storage portion of an Intel PIIX4 (82371AB) PCI-to-ISA bridge
7//! with two IDE channels (primary + secondary), each supporting up to two
8//! devices. PCI vendor/device ID: `8086:7111`.
9//!
10//! # Drive types
11//!
12//! - **ATA hard drives** — use [`Disk`] for I/O. Support
13//!   PIO and DMA modes, 28-bit and 48-bit LBA, `IDENTIFY DEVICE`, `FLUSH CACHE`.
14//! - **ATAPI optical drives** — use `PACKET COMMAND` (0xA0) to transport SCSI
15//!   CDBs over the ATA interface, delegating to [`AsyncScsiDisk`].
16//!
17//! # Port I/O
18//!
19//! Primary channel: 0x1F0–0x1F7 + 0x3F6. Secondary: 0x170–0x177 + 0x376.
20//! Bus master DMA via PCI BAR4 (PRD scatter-gather table).
21//!
22//! # Enlightened I/O
23//!
24//! Microsoft-specific optimization: enlightened INT13 commands via ports
25//! 0x1E0 (primary) and 0x160 (secondary). The guest writes an
26//! `EnlightenedInt13Command` packet GPA, collapsing the multi-exit register
27//! programming sequence into a single VM exit. Uses the `DeferredWrite`
28//! pattern for async completion.
29
30#![expect(missing_docs)]
31#![forbid(unsafe_code)]
32
33mod drive;
34mod protocol;
35
36use crate::drive::save_restore::DriveSaveRestore;
37use crate::protocol::BusMasterReg;
38use crate::protocol::DeviceControlReg;
39use crate::protocol::IdeCommand;
40use crate::protocol::IdeConfigSpace;
41use crate::protocol::Status;
42use chipset_device::ChipsetDevice;
43use chipset_device::io::IoError;
44use chipset_device::io::IoResult;
45use chipset_device::io::deferred::DeferredWrite;
46use chipset_device::io::deferred::defer_write;
47use chipset_device::pci::PciConfigSpace;
48use chipset_device::pio::ControlPortIoIntercept;
49use chipset_device::pio::PortIoIntercept;
50use chipset_device::pio::RegisterPortIoIntercept;
51use chipset_device::poll_device::PollDevice;
52use disk_backend::Disk;
53use drive::DiskDrive;
54use drive::DriveRegister;
55use guestmem::GuestMemory;
56use ide_resources::IdePath;
57use inspect::Inspect;
58use inspect::InspectMut;
59use open_enum::open_enum;
60use pci_core::spec::cfg_space::Command;
61use pci_core::spec::cfg_space::HEADER_TYPE_00_SIZE;
62use pci_core::spec::cfg_space::HeaderType00;
63use protocol::BusMasterCommandReg;
64use protocol::BusMasterStatusReg;
65use scsi::CdbFlags;
66use scsi::ScsiOp;
67use scsi_core::AsyncScsiDisk;
68use scsi_defs as scsi;
69use std::fmt::Debug;
70use std::mem::offset_of;
71use std::ops::RangeInclusive;
72use std::sync::Arc;
73use std::task::Context;
74use thiserror::Error;
75use vmcore::device_state::ChangeDeviceState;
76use vmcore::line_interrupt::LineInterrupt;
77use zerocopy::IntoBytes;
78
79open_enum! {
80    pub enum IdeIoPort: u16 {
81        PRI_ENLIGHTENED = 0x1E0,
82        PRI_DATA = 0x1F0,
83        PRI_ERROR_FEATURES = 0x1F1,
84        PRI_SECTOR_COUNT = 0x1F2,
85        PRI_SECTOR_NUM = 0x1F3,
86        PRI_CYLINDER_LSB = 0x1F4,
87        PRI_CYLINDER_MSB = 0x1F5,
88        PRI_DEVICE_HEAD = 0x1F6,
89        PRI_STATUS_CMD = 0x1F7,
90        PRI_ALT_STATUS_DEVICE_CTL = 0x3F6,
91        SEC_ENLIGHTENED = 0x160,
92        SEC_DATA = 0x170,
93        SEC_ERROR_FEATURES = 0x171,
94        SEC_SECTOR_COUNT = 0x172,
95        SEC_SECTOR_NUM = 0x173,
96        SEC_CYLINDER_LSB = 0x174,
97        SEC_CYLINDER_MSB = 0x175,
98        SEC_DEVICE_HEAD = 0x176,
99        SEC_STATUS_CMD = 0x177,
100        SEC_ALT_STATUS_DEVICE_CTL = 0x376,
101    }
102}
103
104enum Port {
105    Data,
106    Drive(DriveRegister),
107    Enlightened,
108    BusMaster(BusMasterReg),
109}
110
111#[derive(Debug, Copy, Clone, PartialEq, Eq, Inspect)]
112enum DmaType {
113    /// Read from guest memory.
114    Read,
115    /// Write to guest memory.
116    Write,
117}
118
119#[derive(Debug, Inspect)]
120struct BusMasterState {
121    #[inspect(hex)]
122    cmd_status_reg: u32,
123    #[inspect(hex)]
124    port_addr_reg: u32,
125    #[inspect(hex)]
126    timing_reg: u32,
127    #[inspect(hex)]
128    secondary_timing_reg: u32,
129    #[inspect(hex)]
130    dma_ctl_reg: u32,
131}
132
133// bus master
134const DEFAULT_BUS_MASTER_PORT_ADDR_REG: u32 = 0x0000_0001;
135const DEFAULT_BUS_MASTER_CMD_STATUS_REG: u32 = 0x0280_0000;
136
137impl BusMasterState {
138    fn new() -> Self {
139        Self {
140            cmd_status_reg: DEFAULT_BUS_MASTER_CMD_STATUS_REG,
141            port_addr_reg: DEFAULT_BUS_MASTER_PORT_ADDR_REG,
142            // Enable IDE decode at startup, don't wait for the BIOS (this is
143            // not actually configurable with our hardware).
144            //
145            // TODO: define a bitfield, hard wire these bits to 1 so that they
146            // can't be cleared.
147            timing_reg: 0x80008000,
148            secondary_timing_reg: 0,
149            dma_ctl_reg: 0,
150        }
151    }
152}
153
154#[derive(Debug, Default, Inspect)]
155struct ChannelBusMasterState {
156    command_reg: BusMasterCommandReg,
157    status_reg: BusMasterStatusReg,
158    #[inspect(hex)]
159    desc_table_ptr: u32,
160    dma_state: Option<DmaState>,
161    dma_error: bool,
162}
163
164impl ChannelBusMasterState {
165    fn dma_io_type(&self) -> DmaType {
166        if self.command_reg.write() {
167            DmaType::Write
168        } else {
169            DmaType::Read
170        }
171    }
172}
173
174/// PCI-based IDE controller
175pub struct IdeDevice {
176    /// IDE controllers can support up to two IDE channels (primary and secondary)
177    /// with up to two devices (drives) per channel for a total of four IDE devices.
178    channels: [Channel; 2],
179    bus_master_state: BusMasterState,
180    bus_master_pio_dynamic: Box<dyn ControlPortIoIntercept>,
181}
182
183impl InspectMut for IdeDevice {
184    fn inspect_mut(&mut self, req: inspect::Request<'_>) {
185        req.respond()
186            .field_mut("primary", &mut self.channels[0])
187            .field_mut("secondary", &mut self.channels[1])
188            .field("bus_master_state", &self.bus_master_state);
189    }
190}
191
192#[derive(Inspect, Debug)]
193struct EnlightenedCdWrite {
194    #[inspect(skip)]
195    deferred: DeferredWrite,
196    old_adapter_control_reg: u8,
197    guest_address: u64,
198    old_features_reg: u8,
199    data_buffer: u32,
200    skip_bytes_head: u16,
201    byte_count: u32,
202    block_count: u16,
203    drive_index: usize,
204}
205
206#[derive(Inspect, Debug)]
207struct EnlightenedHddWrite {
208    #[inspect(skip)]
209    deferred: DeferredWrite,
210    old_adapter_control_reg: u8,
211    guest_address: u64,
212    drive_index: usize,
213}
214
215#[derive(Debug, Inspect)]
216#[inspect(tag = "drive_type")]
217enum EnlightenedWrite {
218    Hard(#[inspect(rename = "write")] EnlightenedHddWrite),
219    Optical(#[inspect(rename = "write")] EnlightenedCdWrite),
220}
221
222#[derive(Debug, Error)]
223pub enum NewDeviceError {
224    #[error("disk too large: {0} bytes")]
225    DiskTooLarge(u64),
226}
227
228impl IdeDevice {
229    /// Creates an IDE device from the provided channel drive configuration.
230    pub fn new(
231        guest_memory: GuestMemory,
232        register_pio: &mut dyn RegisterPortIoIntercept,
233        primary_channel_drives: [Option<DriveMedia>; 2],
234        secondary_channel_drives: [Option<DriveMedia>; 2],
235        primary_line_interrupt: LineInterrupt,
236        secondary_line_interrupt: LineInterrupt,
237    ) -> Result<Self, NewDeviceError> {
238        let channels = [
239            Channel::new(
240                primary_channel_drives,
241                ChannelType::Primary,
242                primary_line_interrupt,
243                guest_memory.clone(),
244            )?,
245            Channel::new(
246                secondary_channel_drives,
247                ChannelType::Secondary,
248                secondary_line_interrupt,
249                guest_memory,
250            )?,
251        ];
252
253        Ok(Self {
254            channels,
255            bus_master_state: BusMasterState::new(),
256            bus_master_pio_dynamic: register_pio.new_io_region("ide bus master", 16),
257        })
258    }
259
260    fn parse_port(&self, io_port: u16) -> Option<(Port, usize)> {
261        match IdeIoPort(io_port) {
262            IdeIoPort::PRI_ENLIGHTENED => Some((Port::Enlightened, 0)),
263            IdeIoPort::PRI_DATA => Some((Port::Data, 0)),
264            IdeIoPort::PRI_ERROR_FEATURES => Some((Port::Drive(DriveRegister::ErrorFeatures), 0)),
265            IdeIoPort::PRI_SECTOR_COUNT => Some((Port::Drive(DriveRegister::SectorCount), 0)),
266            IdeIoPort::PRI_SECTOR_NUM => Some((Port::Drive(DriveRegister::LbaLow), 0)),
267            IdeIoPort::PRI_CYLINDER_LSB => Some((Port::Drive(DriveRegister::LbaMid), 0)),
268            IdeIoPort::PRI_CYLINDER_MSB => Some((Port::Drive(DriveRegister::LbaHigh), 0)),
269            IdeIoPort::PRI_DEVICE_HEAD => Some((Port::Drive(DriveRegister::DeviceHead), 0)),
270            IdeIoPort::PRI_STATUS_CMD => Some((Port::Drive(DriveRegister::StatusCmd), 0)),
271            IdeIoPort::PRI_ALT_STATUS_DEVICE_CTL => {
272                Some((Port::Drive(DriveRegister::AlternateStatusDeviceControl), 0))
273            }
274            IdeIoPort::SEC_ENLIGHTENED => Some((Port::Enlightened, 1)),
275            IdeIoPort::SEC_DATA => Some((Port::Data, 1)),
276            IdeIoPort::SEC_ERROR_FEATURES => Some((Port::Drive(DriveRegister::ErrorFeatures), 1)),
277            IdeIoPort::SEC_SECTOR_COUNT => Some((Port::Drive(DriveRegister::SectorCount), 1)),
278            IdeIoPort::SEC_SECTOR_NUM => Some((Port::Drive(DriveRegister::LbaLow), 1)),
279            IdeIoPort::SEC_CYLINDER_LSB => Some((Port::Drive(DriveRegister::LbaMid), 1)),
280            IdeIoPort::SEC_CYLINDER_MSB => Some((Port::Drive(DriveRegister::LbaHigh), 1)),
281            IdeIoPort::SEC_DEVICE_HEAD => Some((Port::Drive(DriveRegister::DeviceHead), 1)),
282            IdeIoPort::SEC_STATUS_CMD => Some((Port::Drive(DriveRegister::StatusCmd), 1)),
283            IdeIoPort::SEC_ALT_STATUS_DEVICE_CTL => {
284                Some((Port::Drive(DriveRegister::AlternateStatusDeviceControl), 1))
285            }
286            io_port
287                if (IdeIoPort::PRI_ENLIGHTENED..=IdeIoPort::PRI_STATUS_CMD).contains(&io_port) =>
288            {
289                None
290            }
291            io_port
292                if (IdeIoPort::SEC_ENLIGHTENED..=IdeIoPort::SEC_STATUS_CMD).contains(&io_port) =>
293            {
294                None
295            }
296            _ => {
297                if self.bus_master_state.cmd_status_reg
298                    & protocol::PCI_CONFIG_STATUS_IO_SPACE_ENABLE_MASK
299                    == 0
300                {
301                    return None;
302                }
303
304                // If the port does not match any of the statically registered IO ports then
305                // the port is part of the dynamically registered bus master range. The IDE
306                // bus master range spans 16 ports with the first 8 corresponding to the primary
307                // IDE channel.
308                Some((
309                    Port::BusMaster(BusMasterReg(io_port & 0x7)),
310                    (io_port as usize & 0x8) >> 3,
311                ))
312            }
313        }
314    }
315}
316
317impl Channel {
318    fn enlightened_port_write(
319        &mut self,
320        data: &[u8],
321        bus_master_state: &BusMasterState,
322    ) -> IoResult {
323        if data.len() != 4 {
324            return IoResult::Err(IoError::InvalidAccessSize);
325        }
326
327        if self.enlightened_write.is_some() {
328            tracelimit::error_ratelimited!("enlightened write while one is in progress, ignoring");
329            return IoResult::Ok;
330        }
331
332        // Read the EnlightenedInt13Command packet directly from guest ram
333        let addr = u32::from_ne_bytes(data.try_into().unwrap());
334
335        let eint13_cmd = match self
336            .guest_memory
337            .read_plain::<protocol::EnlightenedInt13Command>(addr as u64)
338        {
339            Ok(cmd) => cmd,
340            Err(err) => {
341                tracelimit::error_ratelimited!(
342                    error = &err as &dyn std::error::Error,
343                    "failed to read enlightened IO command"
344                );
345                return IoResult::Ok;
346            }
347        };
348
349        // Write out the drive-head register FIRST because that is used to
350        // select the current drive.
351        self.write_drive_register(
352            DriveRegister::DeviceHead,
353            eint13_cmd.device_head.into(),
354            bus_master_state,
355        );
356
357        if let Some(status) = self.current_drive_status() {
358            if status.err() {
359                tracelimit::warn_ratelimited!(
360                    "drive is in error state, ignoring enlightened command",
361                );
362                return IoResult::Ok;
363            } else if status.bsy() || status.drq() {
364                tracelimit::warn_ratelimited!(
365                    "command is already pending on this drive, ignoring enlightened command"
366                );
367                return IoResult::Ok;
368            }
369        }
370
371        let result = if let Some(drive_type) = self.current_drive_type() {
372            match drive_type {
373                DriveType::Optical => {
374                    self.enlightened_cd_command(addr.into(), eint13_cmd, bus_master_state)
375                }
376                DriveType::Hard => {
377                    self.enlightened_hdd_command(addr.into(), eint13_cmd, bus_master_state)
378                }
379            }
380        } else {
381            tracelimit::warn_ratelimited!(
382                eint13_cmd = ?eint13_cmd,
383                drive_idx = self.state.current_drive_idx,
384                "Enlightened IO command: No attached drive"
385            );
386            IoResult::Ok
387        };
388
389        self.post_drive_access(bus_master_state);
390        result
391    }
392
393    fn enlightened_hdd_command(
394        &mut self,
395        guest_address: u64,
396        eint13_cmd: protocol::EnlightenedInt13Command,
397        bus_master_state: &BusMasterState,
398    ) -> IoResult {
399        let mut lba48 = eint13_cmd.lba_high as u64;
400        lba48 <<= 32;
401        lba48 |= eint13_cmd.lba_low as u64;
402
403        tracing::trace!(
404            command = ?eint13_cmd.command,
405            lba = lba48,
406            block_count = eint13_cmd.block_count,
407            buffer = eint13_cmd.data_buffer,
408            guest_address,
409            "enlightened hdd command"
410        );
411
412        // The enlightened INT13 path is a DMA-only fast path used by
413        // the Hyper-V BIOS. Non-DMA commands (PIO reads/writes,
414        // IDENTIFY_DEVICE, etc.) would leave the drive with a PIO
415        // buffer that DMA can't drain, causing the deferred write to
416        // never complete.
417        let cmd = eint13_cmd.command;
418        if !matches!(
419            cmd,
420            IdeCommand::READ_DMA
421                | IdeCommand::READ_DMA_ALT
422                | IdeCommand::WRITE_DMA
423                | IdeCommand::WRITE_DMA_ALT
424                | IdeCommand::READ_DMA_EXT
425                | IdeCommand::WRITE_DMA_EXT
426                | IdeCommand::WRITE_DMA_FUA_EXT
427        ) {
428            tracelimit::warn_ratelimited!(?cmd, "ignoring non-DMA command in enlightened path");
429            return IoResult::Ok;
430        }
431
432        // Write out the PRD register for the bus master
433        self.write_bus_master_reg(
434            BusMasterReg::TABLE_PTR,
435            eint13_cmd.data_buffer.as_bytes(),
436            bus_master_state,
437        )
438        .unwrap();
439
440        // Now that we know what the IDE command is, disambiguate between
441        // 28-bit LBA and 48-bit LBA
442        if cmd == IdeCommand::READ_DMA_EXT || cmd == IdeCommand::WRITE_DMA_EXT {
443            // 48-bit LBA, high 24 bits of logical block address
444            self.write_drive_register(
445                DriveRegister::LbaLow,
446                (eint13_cmd.lba_low >> 24) as u8,
447                bus_master_state,
448            );
449
450            self.write_drive_register(
451                DriveRegister::LbaMid,
452                eint13_cmd.lba_high as u8,
453                bus_master_state,
454            );
455
456            self.write_drive_register(
457                DriveRegister::LbaHigh,
458                (eint13_cmd.lba_high >> 8) as u8,
459                bus_master_state,
460            );
461
462            // 48-bit LBA, high 8 bits of sector count
463            // Write the low-byte of the sector count
464            self.write_drive_register(
465                DriveRegister::SectorCount,
466                (eint13_cmd.block_count >> 8) as u8,
467                bus_master_state,
468            );
469        }
470
471        // Write the low-byte of the sector count
472        self.write_drive_register(
473            DriveRegister::SectorCount,
474            eint13_cmd.block_count as u8,
475            bus_master_state,
476        );
477
478        // Finish writing the LBA low bytes to the FIFOs
479        self.write_drive_register(
480            DriveRegister::LbaLow,
481            eint13_cmd.lba_low as u8,
482            bus_master_state,
483        );
484        self.write_drive_register(
485            DriveRegister::LbaMid,
486            (eint13_cmd.lba_low >> 8) as u8,
487            bus_master_state,
488        );
489        self.write_drive_register(
490            DriveRegister::LbaHigh,
491            (eint13_cmd.lba_low >> 16) as u8,
492            bus_master_state,
493        );
494
495        // Make sure that the IDE channel will not cause an interrupt since this
496        // enlightened operation is intended to be 100% synchronous.
497        let old_adapter_control_reg = self.state.shadow_adapter_control_reg;
498        self.write_drive_register(
499            DriveRegister::AlternateStatusDeviceControl,
500            DeviceControlReg::new()
501                .with_interrupt_mask(true)
502                .into_bits(),
503            bus_master_state,
504        );
505
506        // Start the dma engine.
507        let mut bus_master_flags = BusMasterCommandReg::new().with_start(true);
508        if cmd == IdeCommand::READ_DMA_EXT
509            || cmd == IdeCommand::READ_DMA
510            || cmd == IdeCommand::READ_DMA_ALT
511        {
512            // set rw flag to 1 to inticate that bus master is performing a read
513            bus_master_flags.set_write(true);
514        }
515
516        self.write_bus_master_reg(
517            BusMasterReg::COMMAND,
518            &[bus_master_flags.into_bits() as u8],
519            bus_master_state,
520        )
521        .unwrap();
522
523        // Start the IDE command
524        self.write_drive_register(DriveRegister::StatusCmd, cmd.0, bus_master_state);
525
526        // Defer the write.
527        let (write, token) = defer_write();
528        self.enlightened_write = Some(EnlightenedWrite::Hard(EnlightenedHddWrite {
529            deferred: write,
530            old_adapter_control_reg,
531            guest_address,
532            drive_index: self.state.current_drive_idx,
533        }));
534
535        tracing::trace!(enlightened_write = ?self.enlightened_write, "enlightened_hdd_command");
536        if let Some(status) = self.current_drive_status() {
537            if status.drq() {
538                tracelimit::warn_ratelimited!(
539                    "command is waiting for data read from guest or data write to guest"
540                );
541                return IoResult::Ok;
542            }
543        }
544        IoResult::Defer(token)
545    }
546
547    fn enlightened_cd_command(
548        &mut self,
549        guest_address: u64,
550        eint13_cmd: protocol::EnlightenedInt13Command,
551        bus_master_state: &BusMasterState,
552    ) -> IoResult {
553        tracing::trace!(
554            guest_address,
555            command = ?eint13_cmd,
556            "enlightened cd command"
557        );
558
559        // Save the old Precompensation byte because we must NOT use traditional DMA
560        // with this command. Just read the bytes into the track cache so we can use
561        // WriteRamBytes to move them to the guest.
562        let old_features_reg = self.state.shadow_features_reg;
563        self.write_drive_register(DriveRegister::ErrorFeatures, 0, bus_master_state);
564
565        // Make sure that any code path through the IDE completion and error code does NOT
566        // generate an interrupt. This enlightenment is intended to operate completely
567        // synchronously.
568        let old_adapter_control_reg = self.state.shadow_adapter_control_reg;
569        self.write_drive_register(
570            DriveRegister::AlternateStatusDeviceControl,
571            DeviceControlReg::new()
572                .with_interrupt_mask(true)
573                .into_bits(),
574            bus_master_state,
575        );
576
577        // Start the Atapi packet command
578        self.write_drive_register(
579            DriveRegister::StatusCmd,
580            IdeCommand::PACKET_COMMAND.0,
581            bus_master_state,
582        );
583
584        // Construct the SCSI command packet in the single sector buffer that
585        // we're about to dispatch.
586        let cdb = scsi::Cdb10 {
587            operation_code: ScsiOp::READ,
588            flags: CdbFlags::new(),
589            logical_block: eint13_cmd.lba_low.into(),
590            reserved2: 0,
591            transfer_blocks: eint13_cmd.block_count.into(),
592            control: 0,
593        };
594
595        // Assume the device is configured for 12-byte commands.
596        let mut command = [0; 12];
597        command[..cdb.as_bytes().len()].copy_from_slice(cdb.as_bytes());
598
599        // Spawn the IO read which eventually puts the data into the track cache
600        // Wait synchronously for the command to complete reading the data into the track cache.
601        self.write_drive_data(command.as_bytes(), bus_master_state);
602
603        // Defer the write.
604        let (write, token) = defer_write();
605        self.enlightened_write = Some(EnlightenedWrite::Optical(EnlightenedCdWrite {
606            deferred: write,
607            old_adapter_control_reg,
608            guest_address,
609            old_features_reg,
610            data_buffer: eint13_cmd.data_buffer,
611            skip_bytes_head: eint13_cmd.skip_bytes_head,
612            byte_count: eint13_cmd.byte_count,
613            block_count: eint13_cmd.block_count,
614            drive_index: self.state.current_drive_idx,
615        }));
616
617        tracing::trace!(enlightened_write = ?self.enlightened_write, "enlightened_cd_command");
618        if let Some(status) = self.current_drive_status() {
619            if status.drq() {
620                tracelimit::warn_ratelimited!(
621                    "command is waiting for data read from guest or data write to guest"
622                );
623                return IoResult::Ok;
624            }
625        }
626        IoResult::Defer(token)
627    }
628
629    fn complete_enlightened_hdd_write(
630        &mut self,
631        write: EnlightenedHddWrite,
632        bus_master_state: &BusMasterState,
633    ) {
634        // Stop the dma engine (probably stopped already, but this mimics the BIOS
635        // code which this enlightenment replaces).
636        self.write_bus_master_reg(BusMasterReg::COMMAND, &[0], bus_master_state)
637            .unwrap();
638
639        // Clear the pending interrupt and read status.
640        let status = self.read_drive_register(DriveRegister::StatusCmd, bus_master_state);
641        let status = Status::from_bits(status);
642
643        if status.err() {
644            // If there was an error, copy back the status into the enlightened INT13 command in
645            // the guest so that the guest can check it.
646            if let Err(err) = self.guest_memory.write_at(
647                write.guest_address
648                    + offset_of!(protocol::EnlightenedInt13Command, result_status) as u64,
649                &[status.into_bits()],
650            ) {
651                tracelimit::error_ratelimited!(
652                    ?status,
653                    error = &err as &dyn std::error::Error,
654                    "failed to write eint13 status back"
655                );
656            }
657        }
658
659        // Possibly re-enable IDE channel interrupts for the next operation
660        self.write_drive_register(
661            DriveRegister::AlternateStatusDeviceControl,
662            write.old_adapter_control_reg,
663            bus_master_state,
664        );
665
666        write.deferred.complete();
667    }
668
669    fn complete_enlightened_cd_write(
670        &mut self,
671        write: EnlightenedCdWrite,
672        bus_master_state: &BusMasterState,
673    ) {
674        // Clear the pending interrupt and read status.
675        let status = self.read_drive_register(DriveRegister::StatusCmd, bus_master_state);
676        let status = Status::from_bits(status);
677
678        if status.err() {
679            // If there was an error, copy back the status into the enlightened INT13 command in
680            // the guest so that the guest can check it.
681            if let Err(err) = self.guest_memory.write_at(
682                write.guest_address
683                    + offset_of!(protocol::EnlightenedInt13Command, result_status) as u64,
684                &[status.into_bits()],
685            ) {
686                tracelimit::error_ratelimited!(
687                    ?status,
688                    error = &err as &dyn std::error::Error,
689                    "failed to write eint13 status back"
690                );
691            }
692        } else {
693            let mut remaining =
694                (write.block_count as u32 * protocol::CD_DRIVE_SECTOR_BYTES) as usize;
695
696            // Skip over the unused portion of the result.
697            let skip = (write.skip_bytes_head as usize).min(remaining);
698            remaining -= skip;
699            self.skip_drive_data(skip, bus_master_state);
700
701            // Read the requested part of the result.
702            let byte_count = (write.byte_count as usize).min(remaining);
703            remaining -= byte_count;
704
705            let mut copied = 0;
706            while copied < byte_count {
707                let mut buf = [0; 512];
708                let len = (byte_count - copied).min(buf.len());
709                let buf = &mut buf[..len];
710                self.read_drive_data(buf, bus_master_state);
711                if let Err(err) = self
712                    .guest_memory
713                    .write_at((write.data_buffer as u64).wrapping_add(copied as u64), buf)
714                {
715                    tracelimit::warn_ratelimited!(
716                        error = &err as &dyn std::error::Error,
717                        "failed to write enlightened result to guest memory"
718                    );
719                }
720                copied += buf.len();
721            }
722
723            // Read any remaining data to prepare the device for the next command.
724            self.skip_drive_data(remaining, bus_master_state);
725        }
726
727        // Restore the precompensation setting which existed prior to this read
728        self.write_drive_register(
729            DriveRegister::ErrorFeatures,
730            write.old_features_reg,
731            bus_master_state,
732        );
733
734        // Possibly re-enable IDE channel interrupts for the next operation
735        self.write_drive_register(
736            DriveRegister::AlternateStatusDeviceControl,
737            write.old_adapter_control_reg,
738            bus_master_state,
739        );
740
741        tracing::trace!("enlightened cd write completed");
742        write.deferred.complete();
743    }
744
745    fn perform_dma_memory_phase(&mut self) {
746        let Some(drive) = &mut self.drives[self.state.current_drive_idx] else {
747            return;
748        };
749
750        if self.bus_master_state.dma_error {
751            if drive.handle_read_dma_descriptor_error() {
752                self.bus_master_state.dma_error = false;
753            }
754            return;
755        }
756
757        let mut dma_avail = match drive.dma_request() {
758            Some((dma_type, avail)) if *dma_type == self.bus_master_state.dma_io_type() => {
759                avail as u32
760            }
761            _ => {
762                // No active, appropriate DMA buffer.
763                return;
764            }
765        };
766
767        let Some(dma) = &mut self.bus_master_state.dma_state else {
768            return;
769        };
770
771        while dma_avail > 0 {
772            // If the number of bytes left in the transfer is zero, we need to read the next Dma descriptor
773            if dma.transfer_bytes_left == 0 {
774                assert!(!dma.transfer_complete);
775
776                // The descriptor table pointer points to a table of Dma
777                // scatter/gather descriptors built by the operating system.
778                // This table tells us where the place the incoming data for
779                // reads or where to get the outgoing data for writes. The
780                // last valid table entry will have the high bit of the
781                // EndOfTable field set.
782                let descriptor_addr: u64 = self
783                    .bus_master_state
784                    .desc_table_ptr
785                    .wrapping_add(8 * (dma.descriptor_idx as u32))
786                    .into();
787
788                let cur_desc_table_entry = match self
789                    .guest_memory
790                    .read_plain::<protocol::BusMasterDmaDesc>(descriptor_addr)
791                {
792                    Ok(cur_desc_table_entry) => cur_desc_table_entry,
793                    Err(err) => {
794                        self.bus_master_state.dma_state = None;
795                        if !drive.handle_read_dma_descriptor_error() {
796                            self.bus_master_state.dma_error = true;
797                        }
798                        tracelimit::error_ratelimited!(
799                            error = &err as &dyn std::error::Error,
800                            "dma descriptor read error"
801                        );
802                        return;
803                    }
804                };
805
806                tracing::trace!(entry = ?cur_desc_table_entry, "read dma desc");
807
808                dma.transfer_bytes_left = cur_desc_table_entry.byte_count.into();
809                // A zero byte length implies 64Kb
810                if cur_desc_table_entry.byte_count == 0 {
811                    dma.transfer_bytes_left = 0x10000;
812                }
813
814                dma.transfer_base_addr = cur_desc_table_entry.mem_physical_base.into();
815                dma.transfer_complete = (cur_desc_table_entry.end_of_table & 0x80) != 0;
816
817                // Increment to the next descriptor.
818                dma.descriptor_idx += 1;
819                if dma.transfer_complete {
820                    dma.descriptor_idx = 0;
821                }
822            }
823
824            // Transfer byte count is limited by the transfer size and the number of bytes in our IDE buffer.
825            let bytes_to_transfer = dma_avail.min(dma.transfer_bytes_left);
826
827            assert!(bytes_to_transfer != 0);
828
829            if let Err(err) = drive.dma_transfer(
830                &self.guest_memory,
831                dma.transfer_base_addr,
832                bytes_to_transfer as usize,
833            ) {
834                // The guest pointed the DMA engine at memory it can't access.
835                // Stop the transfer and raise the bus master DMA error, just as
836                // real hardware would on a failed bus access.
837                self.bus_master_state.dma_state = None;
838                if !drive.handle_read_dma_descriptor_error() {
839                    self.bus_master_state.dma_error = true;
840                }
841                tracelimit::error_ratelimited!(
842                    error = &err as &dyn std::error::Error,
843                    "dma transfer memory access error"
844                );
845                return;
846            }
847
848            dma_avail -= bytes_to_transfer;
849            dma.transfer_base_addr += bytes_to_transfer as u64;
850            dma.transfer_bytes_left -= bytes_to_transfer;
851            if dma.transfer_bytes_left == 0 && dma.transfer_complete {
852                if dma_avail > 0 {
853                    // If descriptor table ended before buffer was exhausted, advance buffer to end to indicate completion of operation
854                    drive.set_prd_exhausted();
855                    drive.dma_advance_buffer(dma_avail as usize);
856                }
857                tracing::trace!("dma transfer is complete");
858                self.bus_master_state.dma_state = None;
859                break;
860            }
861        }
862    }
863}
864
865impl ChangeDeviceState for IdeDevice {
866    fn start(&mut self) {}
867
868    async fn stop(&mut self) {}
869
870    async fn reset(&mut self) {
871        self.bus_master_pio_dynamic.unmap();
872        self.bus_master_state = BusMasterState::new();
873        for channel in &mut self.channels {
874            channel.reset();
875        }
876    }
877}
878
879impl ChipsetDevice for IdeDevice {
880    fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
881        Some(self)
882    }
883
884    fn supports_pci(&mut self) -> Option<&mut dyn PciConfigSpace> {
885        Some(self)
886    }
887
888    fn supports_poll_device(&mut self) -> Option<&mut dyn PollDevice> {
889        Some(self)
890    }
891}
892
893impl PollDevice for IdeDevice {
894    fn poll_device(&mut self, cx: &mut Context<'_>) {
895        for channel in &mut self.channels {
896            channel.poll_device(cx, &self.bus_master_state);
897        }
898    }
899}
900
901impl PortIoIntercept for IdeDevice {
902    fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
903        match self.parse_port(io_port) {
904            Some((port, index)) => match port {
905                Port::Data => {
906                    self.channels[index].read_drive_data(data, &self.bus_master_state);
907                    IoResult::Ok
908                }
909                Port::Drive(register) => {
910                    data[0] =
911                        self.channels[index].read_drive_register(register, &self.bus_master_state);
912                    IoResult::Ok
913                }
914                Port::Enlightened => IoResult::Err(IoError::InvalidRegister),
915                Port::BusMaster(offset) => self.channels[index].read_bus_master_reg(offset, data),
916            },
917            None => IoResult::Err(IoError::InvalidRegister),
918        }
919    }
920
921    fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
922        match self.parse_port(io_port) {
923            Some((port, index)) => match port {
924                Port::Data => {
925                    self.channels[index].write_drive_data(data, &self.bus_master_state);
926                    IoResult::Ok
927                }
928                Port::Drive(register) => {
929                    self.channels[index].write_drive_register(
930                        register,
931                        data[0],
932                        &self.bus_master_state,
933                    );
934                    IoResult::Ok
935                }
936                Port::Enlightened => {
937                    self.channels[index].enlightened_port_write(data, &self.bus_master_state)
938                }
939                Port::BusMaster(offset) => {
940                    self.channels[index].write_bus_master_reg(offset, data, &self.bus_master_state)
941                }
942            },
943            None => IoResult::Err(IoError::InvalidRegister),
944        }
945    }
946
947    fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u16>)] {
948        &[
949            (
950                "ide primary channel",
951                IdeIoPort::PRI_ENLIGHTENED.0..=IdeIoPort::PRI_STATUS_CMD.0,
952            ),
953            (
954                "ide primary channel control",
955                IdeIoPort::PRI_ALT_STATUS_DEVICE_CTL.0..=IdeIoPort::PRI_ALT_STATUS_DEVICE_CTL.0,
956            ),
957            (
958                "ide secondary channel",
959                IdeIoPort::SEC_ENLIGHTENED.0..=IdeIoPort::SEC_STATUS_CMD.0,
960            ),
961            (
962                "ide secondary channel control",
963                IdeIoPort::SEC_ALT_STATUS_DEVICE_CTL.0..=IdeIoPort::SEC_ALT_STATUS_DEVICE_CTL.0,
964            ),
965        ]
966    }
967}
968
969impl PciConfigSpace for IdeDevice {
970    fn pci_cfg_read(&mut self, offset: u16, value: &mut u32) -> IoResult {
971        *value = if offset < HEADER_TYPE_00_SIZE {
972            match HeaderType00(offset) {
973                HeaderType00::DEVICE_VENDOR => protocol::BX_PCI_ISA_BRIDGE_IDE_IDREG_VALUE,
974                HeaderType00::STATUS_COMMAND => self.bus_master_state.cmd_status_reg,
975                HeaderType00::CLASS_REVISION => protocol::BX_PCI_IDE_CLASS_WORD,
976                HeaderType00::BAR4 => self.bus_master_state.port_addr_reg,
977                offset => {
978                    tracing::debug!(?offset, "undefined type00 header read");
979                    0
980                }
981            }
982        } else {
983            match IdeConfigSpace(offset) {
984                IdeConfigSpace::PRIMARY_TIMING_REG_ADDR => self.bus_master_state.timing_reg,
985                IdeConfigSpace::SECONDARY_TIMING_REG_ADDR => {
986                    self.bus_master_state.secondary_timing_reg
987                }
988                IdeConfigSpace::UDMA_CTL_REG_ADDR => self.bus_master_state.dma_ctl_reg,
989                IdeConfigSpace::MANUFACTURE_ID_REG_ADDR => {
990                    // This is a private versioning register, and no one is supposed to use it.
991                    // But a real Triton chipset returns this value...
992                    0x00000F30
993                }
994                offset => {
995                    // Allow reads from undefined areas.
996                    tracing::trace!(?offset, "undefined ide pci config space read");
997                    return IoResult::Err(IoError::InvalidRegister);
998                }
999            }
1000        };
1001
1002        tracing::trace!(?offset, value, "ide pci config space read");
1003        IoResult::Ok
1004    }
1005
1006    fn pci_cfg_write(&mut self, offset: u16, value: u32) -> IoResult {
1007        if offset < HEADER_TYPE_00_SIZE {
1008            let offset = HeaderType00(offset);
1009            tracing::trace!(?offset, value, "ide pci config space write");
1010
1011            const BUS_MASTER_IO_ENABLE_MASK: u32 = Command::new()
1012                .with_pio_enabled(true)
1013                .with_bus_master(true)
1014                .into_bits() as u32;
1015
1016            match offset {
1017                HeaderType00::STATUS_COMMAND => {
1018                    // Several bits are used to reset status bits when written as 1s.
1019                    self.bus_master_state.cmd_status_reg &= !(0x38000000 & value);
1020                    // Only allow writes to two bits (0 and 2). All other bits are read-only.
1021                    self.bus_master_state.cmd_status_reg &= !BUS_MASTER_IO_ENABLE_MASK;
1022
1023                    self.bus_master_state.cmd_status_reg |= value & BUS_MASTER_IO_ENABLE_MASK;
1024
1025                    // Is the io address space enabled for bus mastering and is bus mastering enabled?
1026                    if (self.bus_master_state.cmd_status_reg
1027                        & protocol::CFCS_BUS_MASTER_IO_ENABLE_MASK)
1028                        != protocol::CFCS_BUS_MASTER_IO_ENABLE_MASK
1029                    {
1030                        self.bus_master_pio_dynamic.unmap();
1031                        tracing::trace!("disabling bus master io range");
1032                    } else {
1033                        // Install the callbacks for the current bus primary I/O port range (which
1034                        // is dynamically configurable through PCI config registers)
1035                        let first_port = (self.bus_master_state.port_addr_reg as u16) & 0xFFF0;
1036                        tracing::trace!(?first_port, "enabling bus master range");
1037
1038                        // Change the io range for the bus master registers. Some of the bus master
1039                        // registers can be cached on writes.
1040                        self.bus_master_pio_dynamic.map(first_port);
1041                    }
1042                }
1043                HeaderType00::BAR4 => {
1044                    // Only allow writes to bits 4 to 15
1045                    self.bus_master_state.port_addr_reg =
1046                        (value & 0x0000FFF0) | DEFAULT_BUS_MASTER_PORT_ADDR_REG;
1047                }
1048                _ => tracing::debug!(?offset, "undefined type00 header write"),
1049            }
1050        } else {
1051            let offset = IdeConfigSpace(offset);
1052            tracing::trace!(?offset, value, "ide pci config space write");
1053
1054            match offset {
1055                IdeConfigSpace::PRIMARY_TIMING_REG_ADDR => self.bus_master_state.timing_reg = value,
1056                IdeConfigSpace::SECONDARY_TIMING_REG_ADDR => {
1057                    self.bus_master_state.secondary_timing_reg = value
1058                }
1059                IdeConfigSpace::UDMA_CTL_REG_ADDR => self.bus_master_state.dma_ctl_reg = value,
1060                _ => tracing::trace!(?offset, "undefined ide pci config space write"),
1061            }
1062        }
1063
1064        IoResult::Ok
1065    }
1066
1067    fn suggested_bdf(&mut self) -> Option<(u8, u8, u8)> {
1068        Some((0, 7, 1)) // as per PIIX4 spec
1069    }
1070}
1071
1072/// IDE Channel
1073enum ChannelType {
1074    Primary,
1075    Secondary,
1076}
1077
1078#[derive(Inspect)]
1079#[inspect(tag = "drive_type")]
1080pub enum DriveMedia {
1081    HardDrive(#[inspect(rename = "backend")] Disk),
1082    OpticalDrive(#[inspect(rename = "backend")] Arc<dyn AsyncScsiDisk>),
1083}
1084
1085impl DriveMedia {
1086    pub fn hard_disk(disk: Disk) -> Self {
1087        DriveMedia::HardDrive(disk)
1088    }
1089
1090    pub fn optical_disk(scsi_disk: Arc<dyn AsyncScsiDisk>) -> Self {
1091        DriveMedia::OpticalDrive(scsi_disk)
1092    }
1093}
1094
1095#[derive(Debug, Default, Inspect)]
1096struct ChannelState {
1097    current_drive_idx: usize,
1098    shadow_adapter_control_reg: u8,
1099    shadow_features_reg: u8,
1100}
1101
1102#[derive(InspectMut)]
1103struct Channel {
1104    #[inspect(mut, with = "inspect_drives")]
1105    drives: [Option<DiskDrive>; 2],
1106    interrupt: LineInterrupt,
1107    state: ChannelState,
1108    bus_master_state: ChannelBusMasterState,
1109    enlightened_write: Option<EnlightenedWrite>,
1110    guest_memory: GuestMemory,
1111    #[inspect(skip)]
1112    channel: u8,
1113}
1114
1115fn inspect_drives(drives: &mut [Option<DiskDrive>]) -> impl '_ + InspectMut {
1116    inspect::adhoc_mut(|req| {
1117        let mut resp = req.respond();
1118        for (i, drive) in drives.iter_mut().enumerate() {
1119            resp.field_mut(&i.to_string(), drive);
1120        }
1121    })
1122}
1123
1124impl Channel {
1125    fn new(
1126        channel_drives: [Option<DriveMedia>; 2],
1127        channel_type: ChannelType,
1128        interrupt: LineInterrupt,
1129        guest_memory: GuestMemory,
1130    ) -> Result<Self, NewDeviceError> {
1131        let [primary_media, secondary_media] = channel_drives;
1132
1133        let channel_number = match channel_type {
1134            ChannelType::Primary => 0,
1135            ChannelType::Secondary => 1,
1136        };
1137
1138        Ok(Self {
1139            drives: [
1140                primary_media
1141                    .map(|media| {
1142                        DiskDrive::new(
1143                            media,
1144                            IdePath {
1145                                channel: channel_number,
1146                                drive: 0,
1147                            },
1148                        )
1149                    })
1150                    .transpose()?,
1151                secondary_media
1152                    .map(|media| {
1153                        DiskDrive::new(
1154                            media,
1155                            IdePath {
1156                                channel: channel_number,
1157                                drive: 1,
1158                            },
1159                        )
1160                    })
1161                    .transpose()?,
1162            ],
1163            interrupt,
1164            state: ChannelState::default(),
1165            bus_master_state: ChannelBusMasterState::default(),
1166            enlightened_write: None,
1167            guest_memory,
1168            channel: channel_number,
1169        })
1170    }
1171
1172    fn reset(&mut self) {
1173        tracelimit::info_ratelimited!(channel = self.channel, "channel reset");
1174        self.interrupt.set_level(false);
1175        self.state = ChannelState::default();
1176        self.bus_master_state = ChannelBusMasterState::default();
1177        for drive in self.drives.iter_mut().flatten() {
1178            drive.reset();
1179        }
1180    }
1181
1182    fn poll_device(&mut self, cx: &mut Context<'_>, bus_master_state: &BusMasterState) {
1183        for drive in self.drives.iter_mut().flatten() {
1184            drive.poll_device(cx);
1185        }
1186        self.post_drive_access(bus_master_state);
1187    }
1188
1189    fn current_drive_status(&mut self) -> Option<Status> {
1190        if let Some(drive) = &mut self.drives[self.state.current_drive_idx] {
1191            let status = drive.read_register(DriveRegister::AlternateStatusDeviceControl);
1192            Some(Status::from_bits(status))
1193        } else {
1194            None
1195        }
1196    }
1197
1198    fn drive_status(&mut self, drive_index: usize) -> Status {
1199        // Reading the values from Status register without clearing any bits
1200        assert!(self.drives[drive_index].is_some());
1201        let status = self.drives[drive_index]
1202            .as_mut()
1203            .unwrap()
1204            .read_register(DriveRegister::AlternateStatusDeviceControl);
1205        Status::from_bits(status)
1206    }
1207
1208    fn current_drive_type(&self) -> Option<DriveType> {
1209        self.drives[self.state.current_drive_idx]
1210            .as_ref()
1211            .map(|drive| drive.drive_type())
1212    }
1213
1214    fn drive_type(&mut self, drive_index: usize) -> DriveType {
1215        assert!(self.drives[drive_index].is_some());
1216        self.drives[drive_index]
1217            .as_ref()
1218            .map(|drive| drive.drive_type())
1219            .unwrap()
1220    }
1221
1222    fn post_drive_access(&mut self, bus_master_state: &BusMasterState) {
1223        // DMA first, since this may affect the other operations.
1224        self.perform_dma_memory_phase();
1225
1226        // Enlightened writes.
1227        if let Some(enlightened_write) = &self.enlightened_write {
1228            let drive_index = match enlightened_write {
1229                EnlightenedWrite::Hard(enlightened_hdd_write) => enlightened_hdd_write.drive_index,
1230                EnlightenedWrite::Optical(enlightened_cd_write) => enlightened_cd_write.drive_index,
1231            };
1232
1233            let status = self.drive_status(drive_index);
1234            let completed = match self.drive_type(drive_index) {
1235                DriveType::Hard => !(status.bsy() || status.drq()),
1236                DriveType::Optical => status.drdy(),
1237            };
1238            if completed {
1239                // The command is done.
1240                let write = self.enlightened_write.take().unwrap();
1241                match write {
1242                    EnlightenedWrite::Hard(write) => {
1243                        self.complete_enlightened_hdd_write(write, bus_master_state)
1244                    }
1245                    EnlightenedWrite::Optical(write) => {
1246                        self.complete_enlightened_cd_write(write, bus_master_state)
1247                    }
1248                }
1249            }
1250        }
1251
1252        // Update interrupt state.
1253        let interrupt = self
1254            .drives
1255            .iter()
1256            .flatten()
1257            .any(|drive| drive.interrupt_pending());
1258        if interrupt {
1259            tracing::trace!(channel = self.channel, interrupt, "post_drive_access");
1260            self.bus_master_state.status_reg.set_interrupt(true);
1261        }
1262        self.interrupt.set_level(interrupt);
1263    }
1264
1265    /// Returns Ok(true) if an interrupt should be delivered. The whole result
1266    /// of this should be passed to `io_port_completion`
1267    fn read_drive_register(
1268        &mut self,
1269        port: DriveRegister,
1270        bus_master_state: &BusMasterState,
1271    ) -> u8 {
1272        // Call the selected drive, but fall back to drive 0 if drive 1 is not present.
1273        let mut drive = self.drives[self.state.current_drive_idx].as_mut();
1274        if drive.is_none() {
1275            drive = self.drives[0].as_mut();
1276        }
1277
1278        let data = if let Some(drive) = drive {
1279            // Fill with zeroes if the drive doesn't write it (which can happen for some registers if
1280            // the device is not selected).
1281            drive.read_register(port)
1282        } else {
1283            // Returns 0x7f if neither device is present. This is align with ATA-5 "Devices shall not have a pull-up resistor on DD7"
1284            // Note: Legacy implementation returns 0xff in this case.
1285            0x7f
1286        };
1287
1288        tracing::trace!(?port, ?data, channel = self.channel, "io port read");
1289        self.post_drive_access(bus_master_state);
1290        data
1291    }
1292
1293    /// Returns Ok(true) if an interrupt should be delivered. The whole result
1294    /// of this should be passed to `io_port_completion` except in the case of
1295    /// the enlightened port path.
1296    fn write_drive_register(
1297        &mut self,
1298        port: DriveRegister,
1299        data: u8,
1300        bus_master_state: &BusMasterState,
1301    ) {
1302        tracing::trace!(?port, ?data, channel = self.channel, "io port write");
1303
1304        match port {
1305            DriveRegister::DeviceHead => {
1306                // Shadow the device bit for use in the DMA engine.
1307                self.state.current_drive_idx = ((data >> 4) & 1) as usize;
1308            }
1309            DriveRegister::AlternateStatusDeviceControl => {
1310                // Save this for restoring in the enlightened path.
1311                self.state.shadow_adapter_control_reg = data;
1312                let v = DeviceControlReg::from_bits_truncate(data);
1313                if v.reset() && (self.drives[0].is_some() || self.drives[1].is_some()) {
1314                    self.state = ChannelState::default();
1315                }
1316            }
1317            DriveRegister::ErrorFeatures => {
1318                // Save this for restoring in the enlightened path.
1319                self.state.shadow_features_reg = data;
1320            }
1321            _ => {}
1322        }
1323
1324        // Call both drives.
1325        if let Some(drive) = &mut self.drives[1] {
1326            drive.write_register(port, data);
1327        }
1328        if let Some(drive) = &mut self.drives[0] {
1329            drive.write_register(port, data);
1330        }
1331
1332        self.post_drive_access(bus_master_state);
1333    }
1334
1335    fn read_drive_data(&mut self, data: &mut [u8], bus_master_state: &BusMasterState) {
1336        // Call the selected drive, but fall back to drive 0 if drive 1 is not present.
1337        let mut drive = self.drives[self.state.current_drive_idx].as_mut();
1338        if drive.is_none() {
1339            drive = self.drives[0].as_mut();
1340        }
1341
1342        data.fill(0xff);
1343        // DD7 must be low to conform to the ATA spec.
1344        data[0] = 0x7f;
1345
1346        if let Some(drive) = drive {
1347            drive.pio_read(data);
1348        };
1349
1350        self.post_drive_access(bus_master_state);
1351    }
1352
1353    fn skip_drive_data(&mut self, mut len: usize, bus_master_state: &BusMasterState) {
1354        let mut buf = [0; 512];
1355        while len > 0 {
1356            let this_len = len.min(buf.len());
1357            let buf = &mut buf[..this_len];
1358            self.read_drive_data(buf, bus_master_state);
1359            len -= buf.len();
1360        }
1361    }
1362
1363    fn write_drive_data(&mut self, data: &[u8], bus_master_state: &BusMasterState) {
1364        if let Some(drive) = &mut self.drives[0] {
1365            drive.pio_write(data);
1366        }
1367        if let Some(drive) = &mut self.drives[1] {
1368            drive.pio_write(data);
1369        }
1370        self.post_drive_access(bus_master_state);
1371    }
1372
1373    fn read_bus_master_reg(&mut self, bus_master_reg: BusMasterReg, data: &mut [u8]) -> IoResult {
1374        let data_len = data.len();
1375        match bus_master_reg {
1376            BusMasterReg::COMMAND => match data_len {
1377                1 | 2 => data.copy_from_slice(
1378                    &self.bus_master_state.command_reg.into_bits().to_ne_bytes()[..data_len],
1379                ),
1380                _ => return IoResult::Err(IoError::InvalidAccessSize),
1381            },
1382            BusMasterReg::STATUS => {
1383                let mut status = self.bus_master_state.status_reg;
1384
1385                if self.bus_master_state.dma_state.is_some() {
1386                    status.set_active(true);
1387                }
1388
1389                match data_len {
1390                    1 | 2 => data.copy_from_slice(&status.into_bits().to_ne_bytes()[..data_len]),
1391                    _ => return IoResult::Err(IoError::InvalidAccessSize),
1392                }
1393            }
1394            BusMasterReg::TABLE_PTR => match data_len {
1395                2 | 4 => data.copy_from_slice(
1396                    &self.bus_master_state.desc_table_ptr.to_ne_bytes()[..data_len],
1397                ),
1398                _ => return IoResult::Err(IoError::InvalidAccessSize),
1399            },
1400            BusMasterReg::TABLE_PTR2 => {
1401                if data_len == 2 {
1402                    data.copy_from_slice(&self.bus_master_state.desc_table_ptr.to_ne_bytes()[2..4]);
1403                } else {
1404                    return IoResult::Err(IoError::InvalidAccessSize);
1405                }
1406            }
1407            _ => return IoResult::Err(IoError::InvalidRegister),
1408        }
1409
1410        tracing::trace!(?bus_master_reg, ?data, "bus master register read");
1411        IoResult::Ok
1412    }
1413
1414    fn write_bus_master_reg(
1415        &mut self,
1416        bus_master_reg: BusMasterReg,
1417        data: &[u8],
1418        bus_master_state: &BusMasterState,
1419    ) -> IoResult {
1420        let value: u64 = match data.len() {
1421            1 => u8::from_ne_bytes(data.as_bytes().try_into().unwrap()).into(),
1422            2 => u16::from_ne_bytes(data.as_bytes().try_into().unwrap()).into(),
1423            4 => u32::from_ne_bytes(data.as_bytes().try_into().unwrap()).into(),
1424            _ => return IoResult::Err(IoError::InvalidAccessSize),
1425        };
1426
1427        tracing::trace!(?bus_master_reg, value, "bus master register write");
1428
1429        match bus_master_reg {
1430            BusMasterReg::COMMAND => {
1431                // A write to this register will begin/end a dma transfer
1432                // and control its direction (write vs. read).
1433                if data.len() > 2 {
1434                    return IoResult::Err(IoError::InvalidAccessSize);
1435                }
1436
1437                let old_value = self.bus_master_state.command_reg;
1438                // TODO: make sure all bits that should be preserved are defined.
1439                let mut new_value = BusMasterCommandReg::from_bits_truncate(value as u32);
1440
1441                // The read/write bit is marked as read-only when dma is active.
1442                if old_value.start() {
1443                    // Set the new value of the read/write flag to match the
1444                    // existing value, regardless of the new value of the flag.
1445                    new_value.set_write(old_value.write());
1446                    if !new_value.start() {
1447                        self.bus_master_state.dma_state = None
1448                    }
1449                } else if new_value.start() {
1450                    self.bus_master_state.dma_state = Some(Default::default());
1451                };
1452
1453                self.bus_master_state.command_reg = new_value;
1454            }
1455            BusMasterReg::STATUS => {
1456                if data.len() > 2 {
1457                    return IoResult::Err(IoError::InvalidAccessSize);
1458                }
1459
1460                let value = BusMasterStatusReg::from_bits_truncate(value as u32);
1461                let old_value = self.bus_master_state.status_reg;
1462                let mut new_value = old_value.with_settable(value.settable());
1463
1464                // These bits are reset if a one is written
1465                if value.interrupt() {
1466                    new_value.set_interrupt(false);
1467                }
1468                if value.dma_error() {
1469                    new_value.set_dma_error(false);
1470                }
1471
1472                tracing::trace!(?old_value, ?new_value, "set bus master status");
1473                self.bus_master_state.status_reg = new_value;
1474            }
1475            BusMasterReg::TABLE_PTR => {
1476                if data.len() < 2 {
1477                    return IoResult::Err(IoError::InvalidAccessSize);
1478                }
1479
1480                if data.len() == 4 {
1481                    // Writing the whole 32-bits pointer with the lowest 2 bits zeroed-out.
1482                    self.bus_master_state.desc_table_ptr = value as u32 & 0xffff_fffc;
1483                } else {
1484                    // Writing the low 16-bits of the 32-bits pointer, with the
1485                    // lowest 2 bits zeroed-out
1486                    self.bus_master_state.desc_table_ptr = (self.bus_master_state.desc_table_ptr
1487                        & 0xffff_0000)
1488                        | (value as u32 & 0x0000_fffc);
1489                }
1490            }
1491            BusMasterReg::TABLE_PTR2 => {
1492                // Documentation doesn't mention this offset. Apparently OS/2 writes to this port.
1493                // Port not written to in recent Windows boot.
1494                self.bus_master_state.desc_table_ptr = (self.bus_master_state.desc_table_ptr
1495                    & 0xffff)
1496                    | ((value as u32 & 0xffff) << 16);
1497            }
1498            _ => return IoResult::Err(IoError::InvalidRegister),
1499        }
1500
1501        self.post_drive_access(bus_master_state);
1502        IoResult::Ok
1503    }
1504}
1505
1506#[derive(Debug, Copy, Clone, PartialEq)]
1507enum DriveType {
1508    Hard,
1509    Optical,
1510}
1511
1512#[derive(Debug, Default, Inspect)]
1513struct DmaState {
1514    descriptor_idx: u8,
1515    transfer_complete: bool,
1516    transfer_bytes_left: u32,
1517    transfer_base_addr: u64,
1518}
1519
1520mod save_restore {
1521    use super::*;
1522    use vmcore::save_restore::RestoreError;
1523    use vmcore::save_restore::SaveError;
1524    use vmcore::save_restore::SaveRestore;
1525
1526    mod state {
1527        use crate::drive::save_restore::state::SavedDriveState;
1528        use mesh::payload::Protobuf;
1529        use vmcore::save_restore::SavedStateRoot;
1530
1531        #[derive(Protobuf)]
1532        #[mesh(package = "storage.ide.controller")]
1533        pub struct SavedBusMasterState {
1534            #[mesh(1)]
1535            pub cmd_status_reg: u32,
1536            #[mesh(2)]
1537            pub port_addr_reg: u32,
1538            #[mesh(3)]
1539            pub timing_reg: u32,
1540            #[mesh(4)]
1541            pub secondary_timing_reg: u32,
1542            #[mesh(5)]
1543            pub dma_ctl_reg: u32,
1544        }
1545
1546        #[derive(Protobuf, SavedStateRoot)]
1547        #[mesh(package = "storage.ide.controller")]
1548        pub struct SavedState {
1549            #[mesh(1)]
1550            pub bus_master: SavedBusMasterState,
1551            #[mesh(2)]
1552            pub channel0: SavedChannelState,
1553            #[mesh(3)]
1554            pub channel1: SavedChannelState,
1555        }
1556
1557        #[derive(Protobuf)]
1558        #[mesh(package = "storage.ide.controller")]
1559        pub struct SavedDmaState {
1560            #[mesh(1)]
1561            pub descriptor_idx: u8,
1562            #[mesh(2)]
1563            pub transfer_complete: bool,
1564            #[mesh(3)]
1565            pub transfer_bytes_left: u32,
1566            #[mesh(4)]
1567            pub transfer_base_addr: u64,
1568        }
1569
1570        #[derive(Protobuf)]
1571        #[mesh(package = "storage.ide.controller")]
1572        pub struct SavedChannelBusMasterState {
1573            #[mesh(1)]
1574            pub command_reg: u32,
1575            #[mesh(2)]
1576            pub status_reg: u32,
1577            #[mesh(3)]
1578            pub desc_table_ptr: u32,
1579            #[mesh(4)]
1580            pub dma_state: Option<SavedDmaState>,
1581            #[mesh(5)]
1582            pub dma_error: bool,
1583        }
1584
1585        #[derive(Protobuf)]
1586        #[mesh(package = "storage.ide.controller")]
1587        pub struct SavedChannelState {
1588            #[mesh(1)]
1589            pub current_drive_idx: u8,
1590            #[mesh(2)]
1591            pub shadow_adapter_control_reg: u8,
1592            #[mesh(3)]
1593            pub shadow_features_reg: u8,
1594            #[mesh(4)]
1595            pub bus_master: SavedChannelBusMasterState,
1596            #[mesh(5)]
1597            pub drive0: Option<SavedDriveState>,
1598            #[mesh(6)]
1599            pub drive1: Option<SavedDriveState>,
1600        }
1601    }
1602
1603    impl SaveRestore for IdeDevice {
1604        type SavedState = state::SavedState;
1605
1606        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
1607            let BusMasterState {
1608                cmd_status_reg,
1609                port_addr_reg,
1610                timing_reg,
1611                secondary_timing_reg,
1612                dma_ctl_reg,
1613            } = self.bus_master_state;
1614
1615            let bus_master = state::SavedBusMasterState {
1616                cmd_status_reg,
1617                port_addr_reg,
1618                timing_reg,
1619                secondary_timing_reg,
1620                dma_ctl_reg,
1621            };
1622
1623            let saved_state = state::SavedState {
1624                bus_master,
1625                channel0: self.channels[0].save()?,
1626                channel1: self.channels[1].save()?,
1627            };
1628
1629            Ok(saved_state)
1630        }
1631
1632        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
1633            let state::SavedState {
1634                bus_master:
1635                    state::SavedBusMasterState {
1636                        cmd_status_reg,
1637                        port_addr_reg,
1638                        timing_reg,
1639                        secondary_timing_reg,
1640                        dma_ctl_reg,
1641                    },
1642                channel0,
1643                channel1,
1644            } = state;
1645
1646            self.bus_master_state = BusMasterState {
1647                cmd_status_reg,
1648                port_addr_reg,
1649                timing_reg,
1650                secondary_timing_reg,
1651                dma_ctl_reg,
1652            };
1653
1654            self.channels[0].restore(channel0)?;
1655            self.channels[1].restore(channel1)?;
1656
1657            Ok(())
1658        }
1659    }
1660
1661    #[derive(Debug, Error)]
1662    enum ChannelRestoreError {
1663        #[error("missing drive for state")]
1664        MissingDriveForState,
1665        #[error("missing state for drive")]
1666        MissingStateForDrive,
1667    }
1668
1669    impl Channel {
1670        fn save(&mut self) -> Result<state::SavedChannelState, SaveError> {
1671            // We wait for the completion of deferred IOs as part of pause.
1672            assert!(self.enlightened_write.is_none());
1673
1674            let ChannelState {
1675                current_drive_idx,
1676                shadow_adapter_control_reg,
1677                shadow_features_reg,
1678            } = self.state;
1679
1680            let ChannelBusMasterState {
1681                command_reg,
1682                status_reg,
1683                desc_table_ptr,
1684                dma_state,
1685                dma_error,
1686            } = &self.bus_master_state;
1687
1688            let saved_state = state::SavedChannelState {
1689                current_drive_idx: current_drive_idx as u8,
1690                shadow_adapter_control_reg,
1691                shadow_features_reg,
1692                bus_master: state::SavedChannelBusMasterState {
1693                    command_reg: command_reg.into_bits(),
1694                    status_reg: status_reg.into_bits(),
1695                    desc_table_ptr: *desc_table_ptr,
1696                    dma_state: dma_state.as_ref().map(|dma| {
1697                        let DmaState {
1698                            descriptor_idx,
1699                            transfer_complete,
1700                            transfer_bytes_left,
1701                            transfer_base_addr,
1702                        } = dma;
1703
1704                        state::SavedDmaState {
1705                            descriptor_idx: *descriptor_idx,
1706                            transfer_complete: *transfer_complete,
1707                            transfer_bytes_left: *transfer_bytes_left,
1708                            transfer_base_addr: *transfer_base_addr,
1709                        }
1710                    }),
1711                    dma_error: *dma_error,
1712                },
1713                drive0: self.drives[0]
1714                    .as_mut()
1715                    .map(|drive| drive.save())
1716                    .transpose()?,
1717                drive1: self.drives[1]
1718                    .as_mut()
1719                    .map(|drive| drive.save())
1720                    .transpose()?,
1721            };
1722
1723            Ok(saved_state)
1724        }
1725
1726        fn restore(&mut self, state: state::SavedChannelState) -> Result<(), RestoreError> {
1727            let state::SavedChannelState {
1728                current_drive_idx,
1729                shadow_adapter_control_reg,
1730                shadow_features_reg,
1731                bus_master:
1732                    state::SavedChannelBusMasterState {
1733                        command_reg,
1734                        status_reg,
1735                        desc_table_ptr,
1736                        dma_state,
1737                        dma_error,
1738                    },
1739                drive0,
1740                drive1,
1741            } = state;
1742
1743            self.state = ChannelState {
1744                current_drive_idx: current_drive_idx as usize,
1745                shadow_adapter_control_reg,
1746                shadow_features_reg,
1747            };
1748
1749            self.bus_master_state = ChannelBusMasterState {
1750                command_reg: BusMasterCommandReg::from_bits(command_reg),
1751                status_reg: BusMasterStatusReg::from_bits(status_reg),
1752                desc_table_ptr,
1753                dma_state: dma_state.map(|dma| {
1754                    let state::SavedDmaState {
1755                        descriptor_idx,
1756                        transfer_complete,
1757                        transfer_bytes_left,
1758                        transfer_base_addr,
1759                    } = dma;
1760
1761                    DmaState {
1762                        descriptor_idx,
1763                        transfer_complete,
1764                        transfer_bytes_left,
1765                        transfer_base_addr,
1766                    }
1767                }),
1768                dma_error,
1769            };
1770
1771            for (drive, state) in self.drives.iter_mut().zip([drive0, drive1]) {
1772                match (drive, state) {
1773                    (Some(drive), Some(state)) => drive.restore(state)?,
1774                    (None, None) => {}
1775                    (Some(_), None) => {
1776                        return Err(RestoreError::InvalidSavedState(
1777                            ChannelRestoreError::MissingStateForDrive.into(),
1778                        ));
1779                    }
1780                    (None, Some(_)) => {
1781                        return Err(RestoreError::InvalidSavedState(
1782                            ChannelRestoreError::MissingDriveForState.into(),
1783                        ));
1784                    }
1785                }
1786            }
1787
1788            Ok(())
1789        }
1790    }
1791}
1792
1793#[cfg(test)]
1794mod tests {
1795    use super::*;
1796    use crate::IdeIoPort;
1797    use crate::protocol::BusMasterDmaDesc;
1798    use crate::protocol::DeviceHeadReg;
1799    use crate::protocol::IdeCommand;
1800    use chipset_device::pio::ExternallyManagedPortIoIntercepts;
1801    use disk_file::FileDisk;
1802    use pal_async::async_test;
1803    use scsidisk::atapi_scsi::AtapiScsiDisk;
1804    use scsidisk::scsidvd::SimpleScsiDvd;
1805    use std::fs::File;
1806    use std::future::poll_fn;
1807    use std::io::Read;
1808    use std::io::Write;
1809    use std::task::Poll;
1810    use tempfile::NamedTempFile;
1811    use test_with_tracing::test;
1812    use zerocopy::FromBytes;
1813    use zerocopy::FromZeros;
1814    use zerocopy::IntoBytes;
1815
1816    #[derive(Debug, Inspect)]
1817    struct MediaGeometry {
1818        sectors_per_track: u32,
1819        cylinder_count: u32,
1820        head_count: u32,
1821        total_sectors: u64,
1822    }
1823
1824    impl MediaGeometry {
1825        fn new(total_sectors: u64, sector_size: u32) -> Result<Self, NewDeviceError> {
1826            if total_sectors > protocol::MAX_BYTES_48BIT_LBA / sector_size as u64 {
1827                return Err(NewDeviceError::DiskTooLarge(
1828                    total_sectors * sector_size as u64,
1829                ));
1830            }
1831            let hard_drive_sectors = total_sectors.min(protocol::MAX_CHS_SECTORS as u64);
1832            let mut sectors_per_track;
1833            let mut cylinders_times_heads;
1834            let mut head_count;
1835
1836            if hard_drive_sectors > (16 * 63 * 0xFFFF) {
1837                sectors_per_track = 255;
1838                head_count = 16;
1839                cylinders_times_heads = hard_drive_sectors / (sectors_per_track as u64);
1840            } else {
1841                sectors_per_track = 17;
1842                cylinders_times_heads = hard_drive_sectors / (sectors_per_track as u64);
1843
1844                head_count = std::cmp::max((cylinders_times_heads as u32).div_ceil(1024), 4);
1845
1846                if (cylinders_times_heads >= (head_count as u64) * 1024) || head_count > 16 {
1847                    // Always use 16 heads
1848                    head_count = 16;
1849                    sectors_per_track = 31;
1850                    cylinders_times_heads = hard_drive_sectors / (sectors_per_track as u64);
1851                }
1852
1853                if cylinders_times_heads >= (head_count as u64) * 1024 {
1854                    // Always use 16 heads
1855                    head_count = 16;
1856                    sectors_per_track = 63;
1857                    cylinders_times_heads = hard_drive_sectors / (sectors_per_track as u64);
1858                }
1859            }
1860            Ok(MediaGeometry {
1861                sectors_per_track,
1862                cylinder_count: (cylinders_times_heads / (head_count as u64)) as u32,
1863                head_count,
1864                total_sectors,
1865            })
1866        }
1867    }
1868
1869    struct CommandParams {
1870        sector_count: u8,
1871        sector_num: u8,
1872        cylinder_lsb: u8,
1873        cylinder_msb: u8,
1874        device_head: u8,
1875    }
1876
1877    #[expect(dead_code)]
1878    enum Addressing {
1879        Chs,
1880        Lba28Bit,
1881        Lba48Bit,
1882    }
1883
1884    fn ide_test_setup(
1885        guest_memory: Option<GuestMemory>,
1886        drive_type: DriveType,
1887    ) -> (IdeDevice, File, Vec<u32>, MediaGeometry) {
1888        let test_guest_mem = match guest_memory {
1889            Some(test_gm) => test_gm,
1890            None => GuestMemory::allocate(16 * 1024),
1891        };
1892
1893        // prep file (write 4GB, numbers 1-1048576 (1GB))
1894        let temp_file = NamedTempFile::new().unwrap();
1895        let mut handle1 = temp_file.reopen().unwrap();
1896        let handle2 = temp_file.reopen().unwrap();
1897        let data = (0..0x100000_u32).collect::<Vec<_>>();
1898        handle1.write_all(data.as_bytes()).unwrap();
1899
1900        let disk = Disk::new(FileDisk::open(handle1, false).unwrap()).unwrap();
1901        let geometry = MediaGeometry::new(disk.sector_count(), disk.sector_size()).unwrap();
1902
1903        let media = match drive_type {
1904            DriveType::Hard => DriveMedia::hard_disk(disk),
1905            DriveType::Optical => DriveMedia::optical_disk(Arc::new(AtapiScsiDisk::new(Arc::new(
1906                SimpleScsiDvd::new(Some(disk)),
1907            )))),
1908        };
1909
1910        let ide_device = IdeDevice::new(
1911            test_guest_mem,
1912            &mut ExternallyManagedPortIoIntercepts,
1913            [Some(media), None],
1914            [None, None],
1915            LineInterrupt::detached(),
1916            LineInterrupt::detached(),
1917        )
1918        .unwrap();
1919
1920        (ide_device, handle2, data, geometry)
1921    }
1922
1923    // IDE Test Host protocol functions
1924    fn get_status(ide_controller: &mut IdeDevice, dev_path: &IdePath) -> Status {
1925        let mut data = [0_u8; 1];
1926        ide_controller
1927            .io_read(
1928                io_port(IdeIoPort::PRI_STATUS_CMD, dev_path.channel.into()),
1929                &mut data,
1930            )
1931            .unwrap();
1932
1933        Status::from_bits(data[0])
1934    }
1935
1936    async fn check_status_loop(ide_device: &mut IdeDevice, dev_path: &IdePath) -> Status {
1937        // loop until device is not busy and is ready to transfer data.
1938        wait_for(ide_device, |ide_device| {
1939            let status: Status = get_status(ide_device, dev_path);
1940            (!status.bsy() && !status.drq()).then_some(status)
1941        })
1942        .await
1943    }
1944
1945    async fn check_command_ready(ide_device: &mut IdeDevice, dev_path: &IdePath) -> Status {
1946        // loop until device is not busy and is ready to transfer data.
1947        wait_for(ide_device, |ide_device| {
1948            let status: Status = get_status(ide_device, dev_path);
1949            (!status.bsy() && status.drdy()).then_some(status)
1950        })
1951        .await
1952    }
1953
1954    fn io_port(io_port: IdeIoPort, channel_idx: usize) -> u16 {
1955        if channel_idx == 0 {
1956            io_port.0
1957        } else {
1958            io_port.0 - IdeIoPort::PRI_DATA.0 + IdeIoPort::SEC_DATA.0
1959        }
1960    }
1961
1962    // Host: setup command parameters by writing to features, sector count/number,
1963    // cylinder low/high, dev/head registers
1964    fn write_command_params(
1965        controller: &mut IdeDevice,
1966        dev_path: &IdePath,
1967        sector: u32,
1968        sector_count: u8,
1969        addr: Addressing,
1970        geometry: &MediaGeometry,
1971    ) {
1972        let channel_idx: usize = dev_path.channel as usize;
1973
1974        let io_params = match addr {
1975            Addressing::Chs => {
1976                let sectors_per_track = geometry.sectors_per_track;
1977                let head_count = geometry.head_count;
1978
1979                let sector_num: u8 = ((sector % sectors_per_track) as u8) + 1;
1980                let cylinders: u16 = (sector / (head_count * sectors_per_track)) as u16;
1981                let cylinder_lsb: u8 = cylinders as u8;
1982                let cylinder_msb: u8 = (cylinders >> 8) as u8;
1983                let device_head: u8 = (sector / sectors_per_track % head_count) as u8;
1984
1985                CommandParams {
1986                    sector_count,
1987                    sector_num,
1988                    cylinder_lsb,
1989                    cylinder_msb,
1990                    device_head,
1991                }
1992            }
1993            Addressing::Lba28Bit => {
1994                let sector_num = sector as u8;
1995                let cylinder = (sector & 0x00FF_FF00) >> 8;
1996                let cylinder_lsb: u8 = cylinder as u8;
1997                let cylinder_msb: u8 = (cylinder >> 8) as u8;
1998                let device_head = DeviceHeadReg::new()
1999                    .with_head((sector >> 24) as u8)
2000                    .with_lba(true)
2001                    .into();
2002
2003                CommandParams {
2004                    sector_count,
2005                    sector_num,
2006                    cylinder_lsb,
2007                    cylinder_msb,
2008                    device_head,
2009                }
2010            }
2011            Addressing::Lba48Bit => todo!(),
2012        };
2013
2014        controller
2015            .io_write(
2016                io_port(IdeIoPort::PRI_SECTOR_COUNT, channel_idx),
2017                &[io_params.sector_count],
2018            )
2019            .unwrap();
2020        controller
2021            .io_write(
2022                io_port(IdeIoPort::PRI_SECTOR_NUM, channel_idx),
2023                &[io_params.sector_num],
2024            )
2025            .unwrap();
2026        controller
2027            .io_write(
2028                io_port(IdeIoPort::PRI_CYLINDER_LSB, channel_idx),
2029                &[io_params.cylinder_lsb],
2030            )
2031            .unwrap();
2032        controller
2033            .io_write(
2034                io_port(IdeIoPort::PRI_CYLINDER_MSB, channel_idx),
2035                &[io_params.cylinder_msb],
2036            )
2037            .unwrap();
2038        controller
2039            .io_write(
2040                io_port(IdeIoPort::PRI_DEVICE_HEAD, channel_idx),
2041                &[io_params.device_head],
2042            )
2043            .unwrap();
2044    }
2045
2046    // Host: Execute device selection protocol
2047    async fn device_select(ide_controller: &mut IdeDevice, dev_path: &IdePath) {
2048        check_status_loop(ide_controller, dev_path).await;
2049
2050        let dev_idx: u8 = dev_path.drive;
2051        ide_controller
2052            .io_write(
2053                io_port(IdeIoPort::PRI_DEVICE_HEAD, dev_path.channel.into()),
2054                &[dev_idx],
2055            )
2056            .unwrap();
2057
2058        check_status_loop(ide_controller, dev_path).await;
2059    }
2060
2061    // Host: Write command code to command register, wait 400ns before reading status register
2062    fn execute_command(ide_controller: &mut IdeDevice, dev_path: &IdePath, command: u8) {
2063        ide_controller
2064            .io_write(
2065                io_port(IdeIoPort::PRI_STATUS_CMD, dev_path.channel.into()),
2066                &[command],
2067            )
2068            .unwrap();
2069    }
2070
2071    fn execute_soft_reset_command(ide_controller: &mut IdeDevice, dev_path: &IdePath, command: u8) {
2072        ide_controller
2073            .io_write(
2074                io_port(
2075                    IdeIoPort::PRI_ALT_STATUS_DEVICE_CTL,
2076                    dev_path.channel.into(),
2077                ),
2078                &[command],
2079            )
2080            .unwrap();
2081    }
2082
2083    fn get_dma_state(ide_controller: &mut IdeDevice, dev_path: &IdePath) -> bool {
2084        // Returns true if DMA state exists, false if None
2085        ide_controller.channels[dev_path.channel as usize]
2086            .bus_master_state
2087            .dma_state
2088            .is_some()
2089    }
2090
2091    fn prep_ide_channel(ide_controller: &mut IdeDevice, drive_type: DriveType, dev_path: &IdePath) {
2092        match drive_type {
2093            DriveType::Hard => {
2094                // SET MULTIPLE MODE - sets number of sectors per block to 128
2095                // this is needed when computing IO sector count for a read/write
2096                execute_command(ide_controller, dev_path, IdeCommand::SET_MULTI_BLOCK_MODE.0);
2097            }
2098            DriveType::Optical => {
2099                // Optical is a ATAPI (PACKET) drive and does not support the SET_MULTI_BLOCK_MODE command
2100            }
2101        }
2102    }
2103
2104    // Waits for a condition on the IDE device, polling the device until then.
2105    async fn wait_for<T>(
2106        ide_device: &mut IdeDevice,
2107        mut f: impl FnMut(&mut IdeDevice) -> Option<T>,
2108    ) -> T {
2109        poll_fn(|cx| {
2110            ide_device.poll_device(cx);
2111            let r = f(ide_device);
2112            if let Some(r) = r {
2113                Poll::Ready(r)
2114            } else {
2115                Poll::Pending
2116            }
2117        })
2118        .await
2119    }
2120
2121    // IDE Command Tests
2122    // Command: WRITE SECTOR(S)
2123    #[async_test]
2124    async fn write_sectors_test() {
2125        const START_SECTOR: u32 = 0;
2126        const SECTOR_COUNT: u8 = 4;
2127
2128        let dev_path = IdePath::default();
2129        let (mut ide_device, mut disk, _file_contents, geometry) =
2130            ide_test_setup(None, DriveType::Hard);
2131
2132        // select device [0,0] = primary channel, primary drive
2133        device_select(&mut ide_device, &dev_path).await;
2134        prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2135
2136        // write to first 4 sectors
2137        write_command_params(
2138            &mut ide_device,
2139            &dev_path,
2140            START_SECTOR,
2141            SECTOR_COUNT,
2142            Addressing::Lba28Bit,
2143            &geometry,
2144        );
2145
2146        execute_command(&mut ide_device, &dev_path, IdeCommand::WRITE_SECTORS.0);
2147
2148        // drive status should contain DRQ as data is ready to be exchanged with host
2149        let status = get_status(&mut ide_device, &dev_path);
2150        assert!(status.drq() && !status.bsy());
2151
2152        // PIO - writes to data port
2153        let data = &[0xFF_u8; 2][..];
2154        for _ in 0..SECTOR_COUNT {
2155            let status = check_command_ready(&mut ide_device, &dev_path).await;
2156            assert!(status.drq());
2157            assert!(!status.err());
2158            for _ in 0..protocol::HARD_DRIVE_SECTOR_BYTES / 2 {
2159                ide_device.io_write(IdeIoPort::PRI_DATA.0, data).unwrap();
2160            }
2161        }
2162
2163        let status = check_command_ready(&mut ide_device, &dev_path).await;
2164        assert!(!status.err());
2165        assert!(!status.drq());
2166
2167        let buffer =
2168            &mut [0_u8; (protocol::HARD_DRIVE_SECTOR_BYTES * SECTOR_COUNT as u32) as usize][..];
2169        disk.read_exact(buffer).unwrap();
2170        for byte in buffer {
2171            assert_eq!(*byte, 0xFF);
2172        }
2173    }
2174
2175    #[async_test]
2176    async fn software_reset_test() {
2177        const START_SECTOR: u32 = 0;
2178        const SECTOR_COUNT: u8 = 4;
2179
2180        let dev_path = IdePath::default();
2181        let (mut ide_device, _disk, _file_contents, geometry) =
2182            ide_test_setup(None, DriveType::Hard);
2183
2184        // select device [0,0] = primary channel, primary drive
2185        device_select(&mut ide_device, &dev_path).await;
2186        prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2187
2188        // write to first 4 sectors
2189        write_command_params(
2190            &mut ide_device,
2191            &dev_path,
2192            START_SECTOR,
2193            SECTOR_COUNT,
2194            Addressing::Lba28Bit,
2195            &geometry,
2196        );
2197
2198        execute_command(&mut ide_device, &dev_path, IdeCommand::WRITE_SECTORS.0);
2199        // drive status should contain DRQ as data is ready to be exchanged with host
2200        let status = get_status(&mut ide_device, &dev_path);
2201        assert!(status.drq() && !status.bsy());
2202
2203        execute_soft_reset_command(&mut ide_device, &dev_path, IdeCommand::SOFT_RESET.0);
2204        let status = get_status(&mut ide_device, &dev_path);
2205        assert!(status.bsy());
2206    }
2207
2208    // Command: READ SECTOR(S)
2209    #[async_test]
2210    async fn read_sectors_test() {
2211        const START_SECTOR: u32 = 0;
2212        const SECTOR_COUNT: u8 = 4;
2213
2214        let dev_path = IdePath::default();
2215        let (mut ide_device, _disk, file_contents, geometry) =
2216            ide_test_setup(None, DriveType::Hard);
2217
2218        // select device [0,0] = primary channel, primary drive
2219        device_select(&mut ide_device, &dev_path).await;
2220        prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2221
2222        // read the first 4 sectors
2223        write_command_params(
2224            &mut ide_device,
2225            &dev_path,
2226            START_SECTOR,
2227            SECTOR_COUNT,
2228            Addressing::Lba28Bit,
2229            &geometry,
2230        );
2231
2232        // PIO - writes sectors to track cache buffer
2233        execute_command(&mut ide_device, &dev_path, IdeCommand::READ_SECTORS.0);
2234
2235        let status = check_command_ready(&mut ide_device, &dev_path).await;
2236        assert!(status.drq());
2237        assert!(!status.err());
2238
2239        // PIO - reads data from track cache buffer
2240        let content_bytes = file_contents.as_bytes();
2241        for sector in 0..SECTOR_COUNT {
2242            let status = check_command_ready(&mut ide_device, &dev_path).await;
2243            assert!(status.drq());
2244            assert!(!status.err());
2245            for word in 0..protocol::HARD_DRIVE_SECTOR_BYTES / 2 {
2246                let data = &mut [0, 0][..];
2247                ide_device.io_read(IdeIoPort::PRI_DATA.0, data).unwrap();
2248
2249                let i = sector as usize * protocol::HARD_DRIVE_SECTOR_BYTES as usize / 2
2250                    + word as usize;
2251                assert_eq!(data[0], content_bytes[i * 2]);
2252                assert_eq!(data[1], content_bytes[i * 2 + 1]);
2253            }
2254        }
2255    }
2256
2257    // Command: READ SECTOR(S) - enlightened
2258    async fn enlightened_cmd_test(drive_type: DriveType) {
2259        const SECTOR_COUNT: u16 = 4;
2260        const BYTE_COUNT: u16 = SECTOR_COUNT * protocol::HARD_DRIVE_SECTOR_BYTES as u16;
2261
2262        let test_guest_mem = GuestMemory::allocate(16384);
2263
2264        let table_gpa = 0x1000;
2265        let data_gpa = 0x2000;
2266        test_guest_mem
2267            .write_plain(
2268                table_gpa,
2269                &BusMasterDmaDesc {
2270                    mem_physical_base: data_gpa,
2271                    byte_count: BYTE_COUNT,
2272                    unused: 0,
2273                    end_of_table: 0x80,
2274                },
2275            )
2276            .unwrap();
2277
2278        let (data_buffer, byte_count) = match drive_type {
2279            DriveType::Hard => (table_gpa as u32, 0),
2280            DriveType::Optical => (data_gpa, BYTE_COUNT.into()),
2281        };
2282
2283        let eint13_command = protocol::EnlightenedInt13Command {
2284            command: IdeCommand::READ_DMA_EXT,
2285            device_head: DeviceHeadReg::new().with_lba(true),
2286            flags: 0,
2287            result_status: 0,
2288            lba_low: 0,
2289            lba_high: 0,
2290            block_count: SECTOR_COUNT,
2291            byte_count,
2292            data_buffer,
2293            skip_bytes_head: 0,
2294            skip_bytes_tail: 0,
2295        };
2296        test_guest_mem.write_plain(0, &eint13_command).unwrap();
2297
2298        let dev_path = IdePath::default();
2299        let (mut ide_device, _disk, file_contents, _geometry) =
2300            ide_test_setup(Some(test_guest_mem.clone()), drive_type);
2301
2302        // select device [0,0] = primary channel, primary drive
2303        device_select(&mut ide_device, &dev_path).await;
2304        prep_ide_channel(&mut ide_device, drive_type, &dev_path);
2305
2306        // READ SECTORS - enlightened
2307        let r = ide_device.io_write(IdeIoPort::PRI_ENLIGHTENED.0, 0_u32.as_bytes()); // read from gpa 0
2308
2309        match r {
2310            IoResult::Defer(mut deferred) => {
2311                poll_fn(|cx| {
2312                    ide_device.poll_device(cx);
2313                    deferred.poll_write(cx)
2314                })
2315                .await
2316                .unwrap();
2317            }
2318            _ => panic!("{:?}", r),
2319        }
2320
2321        let mut buffer = vec![0u8; BYTE_COUNT as usize];
2322        test_guest_mem
2323            .read_at(data_gpa.into(), &mut buffer)
2324            .unwrap();
2325        assert_eq!(buffer, file_contents.as_bytes()[..buffer.len()]);
2326    }
2327
2328    // Command: READ SECTOR(S) - enlightened
2329    #[async_test]
2330    async fn enlightened_cd_cmd_test() {
2331        enlightened_cmd_test(DriveType::Optical).await
2332    }
2333
2334    #[async_test]
2335    async fn enlightened_hdd_cmd_test() {
2336        enlightened_cmd_test(DriveType::Hard).await
2337    }
2338
2339    // Command: READ SECTOR(S) - enlightened
2340    // However, provide incomplete PRD table
2341    #[async_test]
2342    async fn enlightened_cmd_test_incomplete_prd() {
2343        const SECTOR_COUNT: u16 = 8;
2344        const BYTE_COUNT: u16 = SECTOR_COUNT * protocol::HARD_DRIVE_SECTOR_BYTES as u16;
2345
2346        let test_guest_mem = GuestMemory::allocate(16384);
2347
2348        let table_gpa = 0x1000;
2349        let data_gpa = 0x2000;
2350        test_guest_mem
2351            .write_plain(
2352                table_gpa,
2353                &BusMasterDmaDesc {
2354                    mem_physical_base: data_gpa,
2355                    byte_count: BYTE_COUNT / 2,
2356                    unused: 0,
2357                    end_of_table: 0x80, // Mark end before second PRD
2358                },
2359            )
2360            .unwrap();
2361        test_guest_mem
2362            .write_plain(
2363                table_gpa + size_of::<BusMasterDmaDesc>() as u64,
2364                &BusMasterDmaDesc {
2365                    mem_physical_base: data_gpa,
2366                    byte_count: BYTE_COUNT / 2,
2367                    unused: 0,
2368                    end_of_table: 0x80,
2369                },
2370            )
2371            .unwrap();
2372
2373        let data_buffer = table_gpa as u32;
2374        let byte_count = 0;
2375
2376        let eint13_command = protocol::EnlightenedInt13Command {
2377            command: IdeCommand::READ_DMA_EXT,
2378            device_head: DeviceHeadReg::new().with_lba(true),
2379            flags: 0,
2380            result_status: 0,
2381            lba_low: 0,
2382            lba_high: 0,
2383            block_count: SECTOR_COUNT,
2384            byte_count,
2385            data_buffer,
2386            skip_bytes_head: 0,
2387            skip_bytes_tail: 0,
2388        };
2389        test_guest_mem.write_plain(0, &eint13_command).unwrap();
2390
2391        let dev_path = IdePath::default();
2392        let (mut ide_device, _disk, file_contents, _geometry) =
2393            ide_test_setup(Some(test_guest_mem.clone()), DriveType::Hard);
2394
2395        // select device [0,0] = primary channel, primary drive
2396        device_select(&mut ide_device, &dev_path).await;
2397        prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2398
2399        // READ SECTORS - enlightened
2400        let r = ide_device.io_write(IdeIoPort::PRI_ENLIGHTENED.0, 0_u32.as_bytes()); // read from gpa 0
2401
2402        match r {
2403            IoResult::Defer(mut deferred) => {
2404                poll_fn(|cx| {
2405                    ide_device.poll_device(cx);
2406                    deferred.poll_write(cx)
2407                })
2408                .await
2409                .unwrap();
2410            }
2411            _ => panic!("{:?}", r),
2412        }
2413
2414        let mut buffer = vec![0u8; BYTE_COUNT as usize / 2];
2415        test_guest_mem
2416            .read_at(data_gpa.into(), &mut buffer)
2417            .unwrap();
2418        assert_eq!(buffer, file_contents.as_bytes()[..buffer.len()]);
2419    }
2420
2421    #[async_test]
2422    async fn enlightened_cmd_test_dma_boundary_overflow() {
2423        // Tests a DMA descriptor that starts at a valid address but overflows into invalid memory
2424        // Guest memory: 0x0000-0x3FFF (16KB)
2425        // Descriptor: starts at 0x3000, requests 8KB -> overflows to 0x5000 (invalid)
2426
2427        const SECTOR_COUNT: u16 = 16; // 16 sectors = 8KB
2428        const BYTE_COUNT: u16 = SECTOR_COUNT * protocol::HARD_DRIVE_SECTOR_BYTES as u16;
2429
2430        let test_guest_mem = GuestMemory::allocate(16384);
2431
2432        let table_gpa = 0x1000;
2433        let data_gpa = 0x3000; // Valid start, but will overflow
2434        test_guest_mem
2435            .write_plain(
2436                table_gpa,
2437                &BusMasterDmaDesc {
2438                    mem_physical_base: data_gpa,
2439                    byte_count: BYTE_COUNT, // 8KB - overflows beyond 0x3FFF
2440                    unused: 0,
2441                    end_of_table: 0x80,
2442                },
2443            )
2444            .unwrap();
2445
2446        let data_buffer = table_gpa as u32;
2447        let byte_count = 0;
2448
2449        let eint13_command = protocol::EnlightenedInt13Command {
2450            command: IdeCommand::READ_DMA_ALT,
2451            device_head: DeviceHeadReg::new().with_lba(true),
2452            flags: 0,
2453            result_status: 0,
2454            lba_low: 0,
2455            lba_high: 0,
2456            block_count: SECTOR_COUNT,
2457            byte_count,
2458            data_buffer,
2459            skip_bytes_head: 0,
2460            skip_bytes_tail: 0,
2461        };
2462        test_guest_mem.write_plain(0, &eint13_command).unwrap();
2463
2464        let dev_path = IdePath::default();
2465        let (mut ide_device, _disk, _file_contents, _geometry) =
2466            ide_test_setup(Some(test_guest_mem.clone()), DriveType::Hard);
2467
2468        device_select(&mut ide_device, &dev_path).await;
2469        prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2470
2471        let r = ide_device.io_write(IdeIoPort::PRI_ENLIGHTENED.0, 0_u32.as_bytes());
2472
2473        match r {
2474            IoResult::Defer(mut deferred) => {
2475                poll_fn(|cx| {
2476                    ide_device.poll_device(cx);
2477                    deferred.poll_write(cx)
2478                })
2479                .await
2480                .unwrap();
2481            }
2482            _ => panic!("{:?}", r),
2483        }
2484
2485        let dma_state = get_dma_state(&mut ide_device, &dev_path);
2486        assert!(
2487            !dma_state,
2488            "Expected DMA state cleared - transfer from 0x{:x} with {} bytes overflows valid range 0x0-0x3FFF",
2489            data_gpa, BYTE_COUNT
2490        );
2491    }
2492
2493    #[async_test]
2494    async fn enlightened_cmd_test_dma_exact_boundary() {
2495        // Tests a DMA descriptor that ends exactly at the boundary of valid memory
2496        // Guest memory: 0x0000-0x3FFF (16KB)
2497        // Descriptor: starts at 0x3E00, requests 512 bytes -> ends at 0x4000 (just past boundary)
2498
2499        const BYTE_COUNT: u16 = 512;
2500
2501        let test_guest_mem = GuestMemory::allocate(16384);
2502
2503        let table_gpa = 0x1000;
2504        let data_gpa = 0x3E00; // 16KB - 512 bytes
2505        test_guest_mem
2506            .write_plain(
2507                table_gpa,
2508                &BusMasterDmaDesc {
2509                    mem_physical_base: data_gpa,
2510                    byte_count: BYTE_COUNT, // Ends exactly at boundary + 1
2511                    unused: 0,
2512                    end_of_table: 0x80,
2513                },
2514            )
2515            .unwrap();
2516
2517        let data_buffer = table_gpa as u32;
2518        let byte_count = 0;
2519
2520        let eint13_command = protocol::EnlightenedInt13Command {
2521            command: IdeCommand::READ_DMA_ALT,
2522            device_head: DeviceHeadReg::new().with_lba(true),
2523            flags: 0,
2524            result_status: 0,
2525            lba_low: 0,
2526            lba_high: 0,
2527            block_count: 1,
2528            byte_count,
2529            data_buffer,
2530            skip_bytes_head: 0,
2531            skip_bytes_tail: 0,
2532        };
2533        test_guest_mem.write_plain(0, &eint13_command).unwrap();
2534
2535        let dev_path = IdePath::default();
2536        let (mut ide_device, _disk, _file_contents, _geometry) =
2537            ide_test_setup(Some(test_guest_mem.clone()), DriveType::Hard);
2538
2539        device_select(&mut ide_device, &dev_path).await;
2540        prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2541
2542        let r = ide_device.io_write(IdeIoPort::PRI_ENLIGHTENED.0, 0_u32.as_bytes());
2543
2544        match r {
2545            IoResult::Defer(mut deferred) => {
2546                poll_fn(|cx| {
2547                    ide_device.poll_device(cx);
2548                    deferred.poll_write(cx)
2549                })
2550                .await
2551                .unwrap();
2552            }
2553            _ => panic!("{:?}", r),
2554        }
2555
2556        let dma_state = get_dma_state(&mut ide_device, &dev_path);
2557        assert!(
2558            !dma_state,
2559            "Expected DMA state cleared - transfer ending at 0x{:x} exceeds valid range",
2560            data_gpa + BYTE_COUNT as u32
2561        );
2562    }
2563
2564    #[async_test]
2565    async fn enlightened_cmd_test_dma_integer_overflow() {
2566        // Tests a DMA descriptor with byte_count that could cause integer overflow
2567        // Guest memory: 0x0000-0x3FFF (16KB)
2568        // Descriptor: mem_physical_base = 0x2000, byte_count = 0xFFFF (64KB)
2569        // The checked_add should catch this overflow
2570
2571        let test_guest_mem = GuestMemory::allocate(16384);
2572
2573        let table_gpa = 0x1000;
2574        let data_gpa = 0x2000;
2575        test_guest_mem
2576            .write_plain(
2577                table_gpa,
2578                &BusMasterDmaDesc {
2579                    mem_physical_base: data_gpa,
2580                    byte_count: 0xFFFF, // Maximum 16-bit value - would overflow
2581                    unused: 0,
2582                    end_of_table: 0x80,
2583                },
2584            )
2585            .unwrap();
2586
2587        let data_buffer = table_gpa as u32;
2588        let byte_count = 0;
2589
2590        let eint13_command = protocol::EnlightenedInt13Command {
2591            command: IdeCommand::READ_DMA_ALT,
2592            device_head: DeviceHeadReg::new().with_lba(true),
2593            flags: 0,
2594            result_status: 0,
2595            lba_low: 0,
2596            lba_high: 0,
2597            block_count: 128, // Arbitrary
2598            byte_count,
2599            data_buffer,
2600            skip_bytes_head: 0,
2601            skip_bytes_tail: 0,
2602        };
2603        test_guest_mem.write_plain(0, &eint13_command).unwrap();
2604
2605        let dev_path = IdePath::default();
2606        let (mut ide_device, _disk, _file_contents, _geometry) =
2607            ide_test_setup(Some(test_guest_mem.clone()), DriveType::Hard);
2608
2609        device_select(&mut ide_device, &dev_path).await;
2610        prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2611
2612        let r = ide_device.io_write(IdeIoPort::PRI_ENLIGHTENED.0, 0_u32.as_bytes());
2613
2614        match r {
2615            IoResult::Defer(mut deferred) => {
2616                poll_fn(|cx| {
2617                    ide_device.poll_device(cx);
2618                    deferred.poll_write(cx)
2619                })
2620                .await
2621                .unwrap();
2622            }
2623            _ => panic!("{:?}", r),
2624        }
2625
2626        let dma_state = get_dma_state(&mut ide_device, &dev_path);
2627        assert!(
2628            !dma_state,
2629            "Expected DMA state cleared - large byte_count 0xFFFF should be rejected"
2630        );
2631    }
2632
2633    #[async_test]
2634    async fn enlightened_cmd_test_dma_u32_max_overflow() {
2635        // Tests a DMA descriptor where mem_physical_base + transfer_bytes_left overflows u32
2636        // This tests the checked_add protection against u32 overflow
2637
2638        let test_guest_mem = GuestMemory::allocate(16384);
2639
2640        let table_gpa = 0x1000;
2641        let data_gpa = 0xFFFF_F000_u32; // Near u32::MAX
2642        test_guest_mem
2643            .write_plain(
2644                table_gpa,
2645                &BusMasterDmaDesc {
2646                    mem_physical_base: data_gpa,
2647                    byte_count: 0x2000, // Would overflow past u32::MAX
2648                    unused: 0,
2649                    end_of_table: 0x80,
2650                },
2651            )
2652            .unwrap();
2653
2654        let data_buffer = table_gpa as u32;
2655        let byte_count = 0;
2656
2657        let eint13_command = protocol::EnlightenedInt13Command {
2658            command: IdeCommand::READ_DMA_ALT,
2659            device_head: DeviceHeadReg::new().with_lba(true),
2660            flags: 0,
2661            result_status: 0,
2662            lba_low: 0,
2663            lba_high: 0,
2664            block_count: 16,
2665            byte_count,
2666            data_buffer,
2667            skip_bytes_head: 0,
2668            skip_bytes_tail: 0,
2669        };
2670        test_guest_mem.write_plain(0, &eint13_command).unwrap();
2671
2672        let dev_path = IdePath::default();
2673        let (mut ide_device, _disk, _file_contents, _geometry) =
2674            ide_test_setup(Some(test_guest_mem.clone()), DriveType::Hard);
2675
2676        device_select(&mut ide_device, &dev_path).await;
2677        prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2678
2679        let r = ide_device.io_write(IdeIoPort::PRI_ENLIGHTENED.0, 0_u32.as_bytes());
2680
2681        match r {
2682            IoResult::Defer(mut deferred) => {
2683                poll_fn(|cx| {
2684                    ide_device.poll_device(cx);
2685                    deferred.poll_write(cx)
2686                })
2687                .await
2688                .unwrap();
2689            }
2690            _ => panic!("{:?}", r),
2691        }
2692
2693        let dma_state = get_dma_state(&mut ide_device, &dev_path);
2694        assert!(
2695            !dma_state,
2696            "Expected DMA state cleared - checked_add should catch u32 overflow from 0x{:x} + 0x2000",
2697            data_gpa
2698        );
2699    }
2700
2701    #[async_test]
2702    async fn enlightened_cmd_test_dma_zero_byte_count() {
2703        // Tests a DMA descriptor with byte_count = 0, which should be treated as 64KB
2704        // This could overflow if the base address is high enough
2705
2706        let test_guest_mem = GuestMemory::allocate(16384);
2707
2708        let table_gpa = 0x1000;
2709        let data_gpa = 0x1000;
2710        test_guest_mem
2711            .write_plain(
2712                table_gpa,
2713                &BusMasterDmaDesc {
2714                    mem_physical_base: data_gpa,
2715                    byte_count: 0, // Treated as 64KB (0x10000)
2716                    unused: 0,
2717                    end_of_table: 0x80,
2718                },
2719            )
2720            .unwrap();
2721
2722        let data_buffer = table_gpa as u32;
2723        let byte_count = 0;
2724
2725        let eint13_command = protocol::EnlightenedInt13Command {
2726            command: IdeCommand::READ_DMA_ALT,
2727            device_head: DeviceHeadReg::new().with_lba(true),
2728            flags: 0,
2729            result_status: 0,
2730            lba_low: 0,
2731            lba_high: 0,
2732            block_count: 128, // 64KB
2733            byte_count,
2734            data_buffer,
2735            skip_bytes_head: 0,
2736            skip_bytes_tail: 0,
2737        };
2738        test_guest_mem.write_plain(0, &eint13_command).unwrap();
2739
2740        let dev_path = IdePath::default();
2741        let (mut ide_device, _disk, _file_contents, _geometry) =
2742            ide_test_setup(Some(test_guest_mem.clone()), DriveType::Hard);
2743
2744        device_select(&mut ide_device, &dev_path).await;
2745        prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2746
2747        let r = ide_device.io_write(IdeIoPort::PRI_ENLIGHTENED.0, 0_u32.as_bytes());
2748
2749        match r {
2750            IoResult::Defer(mut deferred) => {
2751                poll_fn(|cx| {
2752                    ide_device.poll_device(cx);
2753                    deferred.poll_write(cx)
2754                })
2755                .await
2756                .unwrap();
2757            }
2758            _ => panic!("{:?}", r),
2759        }
2760
2761        let dma_state = get_dma_state(&mut ide_device, &dev_path);
2762        assert!(
2763            !dma_state,
2764            "Expected DMA state cleared - byte_count=0 implies 64KB which exceeds guest memory"
2765        );
2766    }
2767
2768    #[async_test]
2769    async fn identify_test_cd() {
2770        let dev_path = IdePath::default();
2771        let (mut ide_device, _disk, _file_contents, _geometry) =
2772            ide_test_setup(None, DriveType::Optical);
2773
2774        // select device [0,0] = primary channel, primary drive
2775        device_select(&mut ide_device, &dev_path).await;
2776        prep_ide_channel(&mut ide_device, DriveType::Optical, &dev_path);
2777
2778        // PIO - writes sectors to track cache buffer
2779        execute_command(
2780            &mut ide_device,
2781            &dev_path,
2782            IdeCommand::IDENTIFY_PACKET_DEVICE.0,
2783        );
2784
2785        let status = check_command_ready(&mut ide_device, &dev_path).await;
2786        assert!(status.drq());
2787        assert!(!status.err());
2788
2789        // PIO - reads data from track cache buffer
2790        let data = &mut [0_u8; protocol::IDENTIFY_DEVICE_BYTES];
2791        ide_device.io_read(IdeIoPort::PRI_DATA.0, data).unwrap();
2792        let features = protocol::IdeFeatures::read_from_prefix(&data[..])
2793            .unwrap()
2794            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
2795        let ex_features = protocol::IdeFeatures {
2796            config_bits: 0x85C0,
2797            serial_no: *b"                    ",
2798            buffer_size: 0x0080,
2799            firmware_revision: *b"        ",
2800            model_number: "iVtrau lDC                              ".as_bytes()[..]
2801                .try_into()
2802                .unwrap(),
2803            capabilities: 0x0300,
2804            pio_cycle_times: 0x0200,       // indicate fast I/O
2805            dma_cycle_times: 0x0200,       // indicate fast I/O
2806            new_words_valid_flags: 0x0003, // indicate next words are valid
2807            multi_sector_capabilities: 0x0100_u16 | protocol::MAX_SECTORS_MULT_TRANSFER_DEFAULT,
2808            single_word_dma_mode: 0x0007, // support up to mode 3, no mode active
2809            multi_word_dma_mode: 0x0407,  // support up to mode 3, mode 3 active
2810            enhanced_pio_mode: 0x0003,    // PIO mode 3 and 4 supported
2811            min_multi_dma_time: 0x0078,
2812            recommended_multi_dma_time: 0x0078,
2813            min_pio_cycle_time_no_flow: 0x01FC, // Taken from a real CD device
2814            min_pio_cycle_time_flow: 0x00B4,    // Taken from a real CD device
2815            ..FromZeros::new_zeroed()
2816        };
2817        assert_eq!(features.as_bytes(), ex_features.as_bytes());
2818    }
2819
2820    #[async_test]
2821    async fn identify_test_hdd() {
2822        let dev_path = IdePath::default();
2823        let (mut ide_device, _disk, _file_contents, geometry) =
2824            ide_test_setup(None, DriveType::Hard);
2825        // select device [0,0] = primary channel, primary drive
2826        device_select(&mut ide_device, &dev_path).await;
2827        prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2828        // PIO - writes sectors to track cache buffer
2829        execute_command(&mut ide_device, &dev_path, IdeCommand::IDENTIFY_DEVICE.0);
2830
2831        let status = check_command_ready(&mut ide_device, &dev_path).await;
2832        assert!(status.drq());
2833        assert!(!status.err());
2834
2835        // PIO - reads data from track cache buffer
2836        let data = &mut [0_u8; protocol::IDENTIFY_DEVICE_BYTES];
2837        ide_device.io_read(IdeIoPort::PRI_DATA.0, data).unwrap();
2838        let features = protocol::IdeFeatures::read_from_prefix(&data[..])
2839            .unwrap()
2840            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
2841
2842        let total_chs_sectors: u32 =
2843            geometry.sectors_per_track * geometry.cylinder_count * geometry.head_count;
2844        let (cylinders, heads, sectors_per_track) = if total_chs_sectors < protocol::MAX_CHS_SECTORS
2845        {
2846            (
2847                geometry.cylinder_count as u16,
2848                geometry.head_count as u16,
2849                geometry.sectors_per_track as u16,
2850            )
2851        } else {
2852            (0x3FFF, 16, 63)
2853        };
2854
2855        let firmware_revision = if dev_path.channel == 0 {
2856            ".1.1 0  "
2857        } else {
2858            ".1.1 1  "
2859        }
2860        .as_bytes()[..]
2861            .try_into()
2862            .unwrap();
2863
2864        let user_addressable_sectors =
2865            if geometry.total_sectors > (protocol::LBA_28BIT_MAX_SECTORS as u64) {
2866                protocol::LBA_28BIT_MAX_SECTORS
2867            } else {
2868                geometry.total_sectors as u32
2869            };
2870
2871        let ex_features = protocol::IdeFeatures {
2872            config_bits: 0x045A,
2873            cylinders,
2874            heads,
2875            unformatted_sectors_per_track: (protocol::HARD_DRIVE_SECTOR_BYTES
2876                * geometry.sectors_per_track) as u16,
2877            unformatted_bytes_per_sector: protocol::HARD_DRIVE_SECTOR_BYTES as u16,
2878            sectors_per_track,
2879            compact_flash: [0xABCD, 0xDCBA],
2880            vendor0: 0x0123,
2881            serial_no: *b"                    ",
2882            buffer_type: 3,
2883            buffer_size: 0x0080,
2884            firmware_revision,
2885            model_number: "iVtrau lDH                              ".as_bytes()[..]
2886                .try_into()
2887                .unwrap(),
2888            max_sectors_mult_transfer: (0x8000 | protocol::MAX_SECTORS_MULT_TRANSFER_DEFAULT),
2889            capabilities: 0x0F00,          // supports Dma, IORDY, LBA
2890            pio_cycle_times: 0x0200,       // indicate fast I/O
2891            dma_cycle_times: 0x0200,       // indicate fast I/O
2892            new_words_valid_flags: 0x0003, // indicate next words are valid
2893            log_cylinders: geometry.cylinder_count as u16,
2894            log_heads: geometry.head_count as u16,
2895            log_sectors_per_track: geometry.sectors_per_track as u16,
2896            log_total_sectors: total_chs_sectors.into(),
2897            multi_sector_capabilities: 0x0100_u16 | protocol::MAX_SECTORS_MULT_TRANSFER_DEFAULT,
2898            user_addressable_sectors: user_addressable_sectors.into(),
2899            single_word_dma_mode: 0x0007, // support up to mode 3, no mode active
2900            multi_word_dma_mode: 0x0407,  // support up to mode 3, mode 3 active
2901            enhanced_pio_mode: 0x0003,    // PIO mode 3 and 4 supported
2902            min_multi_dma_time: 0x0078,
2903            recommended_multi_dma_time: 0x0078,
2904            min_pio_cycle_time_no_flow: 0x014D,
2905            min_pio_cycle_time_flow: 0x0078,
2906            major_version_number: 0x01F0, // claim support for ATA4-ATA8
2907            minor_version_number: 0,
2908            command_set_supported: 0x0028, // support caching and power management
2909            command_sets_supported: 0x7400, // support flushing
2910            command_set_supported_ext: 0x4040, // write fua support for default write hardening
2911            command_set_enabled1: 0x0028,  // support caching and power management
2912            command_set_enabled2: 0x3400,  // support flushing
2913            command_set_default: 0x4040,   // write fua support for default write hardening
2914            total_sectors_48_bit: geometry.total_sectors.into(),
2915            default_sector_size_config: 0x4000, // describes the sector size related info. Reflect the underlying device sector size and logical:physical ratio
2916            logical_block_alignment: 0x4000, // describes alignment of logical blocks within physical block
2917            ..FromZeros::new_zeroed()
2918        };
2919        assert_eq!(features.as_bytes(), ex_features.as_bytes());
2920    }
2921
2922    /// Enlightened INT13 with a non-DMA command (READ_SECTORS) should not
2923    /// hang. Before the fix, this would start async disk IO that produces
2924    /// a PIO buffer on completion. The DMA engine can't drain a PIO buffer,
2925    /// so the deferred write completion check (!(bsy || drq)) never passes.
2926    #[async_test]
2927    async fn enlightened_hdd_non_dma_cmd_completes() {
2928        let test_guest_mem = GuestMemory::allocate(16384);
2929
2930        // Set up a PRD table (the enlightened path always writes it,
2931        // even though READ_SECTORS won't use it)
2932        let table_gpa: u64 = 0x1000;
2933        let data_gpa: u32 = 0x2000;
2934        test_guest_mem
2935            .write_plain(
2936                table_gpa,
2937                &BusMasterDmaDesc {
2938                    mem_physical_base: data_gpa,
2939                    byte_count: 512,
2940                    unused: 0,
2941                    end_of_table: 0x80,
2942                },
2943            )
2944            .unwrap();
2945
2946        // READ_SECTORS (0x20) is a PIO read command. The enlightened path
2947        // is designed for DMA commands only (READ_DMA_EXT, WRITE_DMA_EXT).
2948        // Sending a PIO command through it starts async disk IO, but the
2949        // resulting PIO buffer can't be drained by DMA -- hang forever.
2950        let eint13_command = protocol::EnlightenedInt13Command {
2951            command: IdeCommand::READ_SECTORS,
2952            device_head: DeviceHeadReg::new().with_lba(true),
2953            flags: 0,
2954            result_status: 0,
2955            lba_low: 0,
2956            lba_high: 0,
2957            block_count: 1,
2958            byte_count: 0,
2959            data_buffer: table_gpa as u32,
2960            skip_bytes_head: 0,
2961            skip_bytes_tail: 0,
2962        };
2963        test_guest_mem.write_plain(0, &eint13_command).unwrap();
2964
2965        let dev_path = IdePath::default();
2966        let (mut ide_device, _disk, _, _) =
2967            ide_test_setup(Some(test_guest_mem.clone()), DriveType::Hard);
2968
2969        device_select(&mut ide_device, &dev_path).await;
2970        prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2971
2972        // After fix: non-DMA commands through the enlightened path are
2973        // rejected early and return Ok (not Defer). Before the fix,
2974        // this would return Defer and hang forever.
2975        assert!(
2976            matches!(
2977                ide_device.io_write(IdeIoPort::PRI_ENLIGHTENED.0, 0_u32.as_bytes()),
2978                IoResult::Ok
2979            ),
2980            "non-DMA command (READ_SECTORS) via enlightened path should return Ok, not Defer"
2981        );
2982    }
2983}