Skip to main content

ide/
lib.rs

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