Skip to main content

ide/
lib.rs

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