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