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