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