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::ByteEnabledDwordRead;
48use chipset_device::pci::ByteEnabledDwordWrite;
49use chipset_device::pci::PciConfigSpace;
50use chipset_device::pio::ControlPortIoIntercept;
51use chipset_device::pio::PortIoIntercept;
52use chipset_device::pio::RegisterPortIoIntercept;
53use chipset_device::poll_device::PollDevice;
54use disk_backend::Disk;
55use drive::DiskDrive;
56use drive::DriveRegister;
57use guestmem::GuestMemory;
58use ide_resources::IdePath;
59use inspect::Inspect;
60use inspect::InspectMut;
61use open_enum::open_enum;
62use pci_core::spec::cfg_space::Command;
63use pci_core::spec::cfg_space::HEADER_TYPE_00_SIZE;
64use pci_core::spec::cfg_space::HeaderType00;
65use protocol::BusMasterCommandReg;
66use protocol::BusMasterStatusReg;
67use scsi::CdbFlags;
68use scsi::ScsiOp;
69use scsi_core::AsyncScsiDisk;
70use scsi_defs as scsi;
71use std::fmt::Debug;
72use std::mem::offset_of;
73use std::ops::RangeInclusive;
74use std::sync::Arc;
75use std::task::Context;
76use thiserror::Error;
77use vmcore::device_state::ChangeDeviceState;
78use vmcore::line_interrupt::LineInterrupt;
79use zerocopy::IntoBytes;
80
81open_enum! {
82 pub enum IdeIoPort: u16 {
83 PRI_ENLIGHTENED = 0x1E0,
84 PRI_DATA = 0x1F0,
85 PRI_ERROR_FEATURES = 0x1F1,
86 PRI_SECTOR_COUNT = 0x1F2,
87 PRI_SECTOR_NUM = 0x1F3,
88 PRI_CYLINDER_LSB = 0x1F4,
89 PRI_CYLINDER_MSB = 0x1F5,
90 PRI_DEVICE_HEAD = 0x1F6,
91 PRI_STATUS_CMD = 0x1F7,
92 PRI_ALT_STATUS_DEVICE_CTL = 0x3F6,
93 SEC_ENLIGHTENED = 0x160,
94 SEC_DATA = 0x170,
95 SEC_ERROR_FEATURES = 0x171,
96 SEC_SECTOR_COUNT = 0x172,
97 SEC_SECTOR_NUM = 0x173,
98 SEC_CYLINDER_LSB = 0x174,
99 SEC_CYLINDER_MSB = 0x175,
100 SEC_DEVICE_HEAD = 0x176,
101 SEC_STATUS_CMD = 0x177,
102 SEC_ALT_STATUS_DEVICE_CTL = 0x376,
103 }
104}
105
106enum Port {
107 Data,
108 Drive(DriveRegister),
109 Enlightened,
110 BusMaster(BusMasterReg),
111}
112
113#[derive(Debug, Copy, Clone, PartialEq, Eq, Inspect)]
114enum DmaType {
115 Read,
117 Write,
119}
120
121#[derive(Debug, Inspect)]
122struct BusMasterState {
123 #[inspect(hex)]
124 cmd_status_reg: u32,
125 #[inspect(hex)]
126 port_addr_reg: u32,
127 #[inspect(hex)]
128 timing_reg: u32,
129 #[inspect(hex)]
130 secondary_timing_reg: u32,
131 #[inspect(hex)]
132 dma_ctl_reg: u32,
133}
134
135const DEFAULT_BUS_MASTER_PORT_ADDR_REG: u32 = 0x0000_0001;
137const DEFAULT_BUS_MASTER_CMD_STATUS_REG: u32 = 0x0280_0000;
138
139impl BusMasterState {
140 fn new() -> Self {
141 Self {
142 cmd_status_reg: DEFAULT_BUS_MASTER_CMD_STATUS_REG,
143 port_addr_reg: DEFAULT_BUS_MASTER_PORT_ADDR_REG,
144 timing_reg: 0x80008000,
150 secondary_timing_reg: 0,
151 dma_ctl_reg: 0,
152 }
153 }
154}
155
156#[derive(Debug, Default, Inspect)]
157struct ChannelBusMasterState {
158 command_reg: BusMasterCommandReg,
159 status_reg: BusMasterStatusReg,
160 #[inspect(hex)]
161 desc_table_ptr: u32,
162 dma_state: Option<DmaState>,
163 dma_error: bool,
164}
165
166impl ChannelBusMasterState {
167 fn dma_io_type(&self) -> DmaType {
168 if self.command_reg.write() {
169 DmaType::Write
170 } else {
171 DmaType::Read
172 }
173 }
174}
175
176pub struct IdeDevice {
178 channels: [Channel; 2],
181 bus_master_state: BusMasterState,
182 bus_master_pio_dynamic: Box<dyn ControlPortIoIntercept>,
183}
184
185impl InspectMut for IdeDevice {
186 fn inspect_mut(&mut self, req: inspect::Request<'_>) {
187 req.respond()
188 .field_mut("primary", &mut self.channels[0])
189 .field_mut("secondary", &mut self.channels[1])
190 .field("bus_master_state", &self.bus_master_state);
191 }
192}
193
194#[derive(Inspect, Debug)]
195struct EnlightenedCdWrite {
196 #[inspect(skip)]
197 deferred: DeferredWrite,
198 old_adapter_control_reg: u8,
199 guest_address: u64,
200 old_features_reg: u8,
201 data_buffer: u32,
202 skip_bytes_head: u16,
203 byte_count: u32,
204 block_count: u16,
205 drive_index: usize,
206}
207
208#[derive(Inspect, Debug)]
209struct EnlightenedHddWrite {
210 #[inspect(skip)]
211 deferred: DeferredWrite,
212 old_adapter_control_reg: u8,
213 guest_address: u64,
214 drive_index: usize,
215}
216
217#[derive(Debug, Inspect)]
218#[inspect(tag = "drive_type")]
219enum EnlightenedWrite {
220 Hard(#[inspect(rename = "write")] EnlightenedHddWrite),
221 Optical(#[inspect(rename = "write")] EnlightenedCdWrite),
222}
223
224#[derive(Debug, Error)]
225pub enum NewDeviceError {
226 #[error("disk too large: {0} bytes")]
227 DiskTooLarge(u64),
228}
229
230impl IdeDevice {
231 pub fn new(
233 guest_memory: GuestMemory,
234 register_pio: &mut dyn RegisterPortIoIntercept,
235 primary_channel_drives: [Option<DriveMedia>; 2],
236 secondary_channel_drives: [Option<DriveMedia>; 2],
237 primary_line_interrupt: LineInterrupt,
238 secondary_line_interrupt: LineInterrupt,
239 ) -> Result<Self, NewDeviceError> {
240 let channels = [
241 Channel::new(
242 primary_channel_drives,
243 ChannelType::Primary,
244 primary_line_interrupt,
245 guest_memory.clone(),
246 )?,
247 Channel::new(
248 secondary_channel_drives,
249 ChannelType::Secondary,
250 secondary_line_interrupt,
251 guest_memory,
252 )?,
253 ];
254
255 Ok(Self {
256 channels,
257 bus_master_state: BusMasterState::new(),
258 bus_master_pio_dynamic: register_pio.new_io_region("ide bus master", 16),
259 })
260 }
261
262 fn parse_port(&self, io_port: u16) -> Option<(Port, usize)> {
263 match IdeIoPort(io_port) {
264 IdeIoPort::PRI_ENLIGHTENED => Some((Port::Enlightened, 0)),
265 IdeIoPort::PRI_DATA => Some((Port::Data, 0)),
266 IdeIoPort::PRI_ERROR_FEATURES => Some((Port::Drive(DriveRegister::ErrorFeatures), 0)),
267 IdeIoPort::PRI_SECTOR_COUNT => Some((Port::Drive(DriveRegister::SectorCount), 0)),
268 IdeIoPort::PRI_SECTOR_NUM => Some((Port::Drive(DriveRegister::LbaLow), 0)),
269 IdeIoPort::PRI_CYLINDER_LSB => Some((Port::Drive(DriveRegister::LbaMid), 0)),
270 IdeIoPort::PRI_CYLINDER_MSB => Some((Port::Drive(DriveRegister::LbaHigh), 0)),
271 IdeIoPort::PRI_DEVICE_HEAD => Some((Port::Drive(DriveRegister::DeviceHead), 0)),
272 IdeIoPort::PRI_STATUS_CMD => Some((Port::Drive(DriveRegister::StatusCmd), 0)),
273 IdeIoPort::PRI_ALT_STATUS_DEVICE_CTL => {
274 Some((Port::Drive(DriveRegister::AlternateStatusDeviceControl), 0))
275 }
276 IdeIoPort::SEC_ENLIGHTENED => Some((Port::Enlightened, 1)),
277 IdeIoPort::SEC_DATA => Some((Port::Data, 1)),
278 IdeIoPort::SEC_ERROR_FEATURES => Some((Port::Drive(DriveRegister::ErrorFeatures), 1)),
279 IdeIoPort::SEC_SECTOR_COUNT => Some((Port::Drive(DriveRegister::SectorCount), 1)),
280 IdeIoPort::SEC_SECTOR_NUM => Some((Port::Drive(DriveRegister::LbaLow), 1)),
281 IdeIoPort::SEC_CYLINDER_LSB => Some((Port::Drive(DriveRegister::LbaMid), 1)),
282 IdeIoPort::SEC_CYLINDER_MSB => Some((Port::Drive(DriveRegister::LbaHigh), 1)),
283 IdeIoPort::SEC_DEVICE_HEAD => Some((Port::Drive(DriveRegister::DeviceHead), 1)),
284 IdeIoPort::SEC_STATUS_CMD => Some((Port::Drive(DriveRegister::StatusCmd), 1)),
285 IdeIoPort::SEC_ALT_STATUS_DEVICE_CTL => {
286 Some((Port::Drive(DriveRegister::AlternateStatusDeviceControl), 1))
287 }
288 io_port
289 if (IdeIoPort::PRI_ENLIGHTENED..=IdeIoPort::PRI_STATUS_CMD).contains(&io_port) =>
290 {
291 None
292 }
293 io_port
294 if (IdeIoPort::SEC_ENLIGHTENED..=IdeIoPort::SEC_STATUS_CMD).contains(&io_port) =>
295 {
296 None
297 }
298 _ => {
299 if self.bus_master_state.cmd_status_reg
300 & protocol::PCI_CONFIG_STATUS_IO_SPACE_ENABLE_MASK
301 == 0
302 {
303 return None;
304 }
305
306 Some((
311 Port::BusMaster(BusMasterReg(io_port & 0x7)),
312 (io_port as usize & 0x8) >> 3,
313 ))
314 }
315 }
316 }
317}
318
319impl Channel {
320 fn enlightened_port_write(
321 &mut self,
322 data: &[u8],
323 bus_master_state: &BusMasterState,
324 ) -> IoResult {
325 if data.len() != 4 {
326 return IoResult::Err(IoError::InvalidAccessSize);
327 }
328
329 if self.enlightened_write.is_some() {
330 tracelimit::error_ratelimited!("enlightened write while one is in progress, ignoring");
331 return IoResult::Ok;
332 }
333
334 let addr = u32::from_ne_bytes(data.try_into().unwrap());
336
337 let eint13_cmd = match self
338 .guest_memory
339 .read_plain::<protocol::EnlightenedInt13Command>(addr as u64)
340 {
341 Ok(cmd) => cmd,
342 Err(err) => {
343 tracelimit::error_ratelimited!(
344 error = &err as &dyn std::error::Error,
345 "failed to read enlightened IO command"
346 );
347 return IoResult::Ok;
348 }
349 };
350
351 self.write_drive_register(
354 DriveRegister::DeviceHead,
355 eint13_cmd.device_head.into(),
356 bus_master_state,
357 );
358
359 if let Some(status) = self.current_drive_status() {
360 if status.err() {
361 tracelimit::warn_ratelimited!(
362 "drive is in error state, ignoring enlightened command",
363 );
364 return IoResult::Ok;
365 } else if status.bsy() || status.drq() {
366 tracelimit::warn_ratelimited!(
367 "command is already pending on this drive, ignoring enlightened command"
368 );
369 return IoResult::Ok;
370 }
371 }
372
373 let result = if let Some(drive_type) = self.current_drive_type() {
374 match drive_type {
375 DriveType::Optical => {
376 self.enlightened_cd_command(addr.into(), eint13_cmd, bus_master_state)
377 }
378 DriveType::Hard => {
379 self.enlightened_hdd_command(addr.into(), eint13_cmd, bus_master_state)
380 }
381 }
382 } else {
383 tracelimit::warn_ratelimited!(
384 eint13_cmd = ?eint13_cmd,
385 drive_idx = self.state.current_drive_idx,
386 "Enlightened IO command: No attached drive"
387 );
388 IoResult::Ok
389 };
390
391 self.post_drive_access(bus_master_state);
392 result
393 }
394
395 fn enlightened_hdd_command(
396 &mut self,
397 guest_address: u64,
398 eint13_cmd: protocol::EnlightenedInt13Command,
399 bus_master_state: &BusMasterState,
400 ) -> IoResult {
401 let mut lba48 = eint13_cmd.lba_high as u64;
402 lba48 <<= 32;
403 lba48 |= eint13_cmd.lba_low as u64;
404
405 tracing::trace!(
406 command = ?eint13_cmd.command,
407 lba = lba48,
408 block_count = eint13_cmd.block_count,
409 buffer = eint13_cmd.data_buffer,
410 guest_address,
411 "enlightened hdd command"
412 );
413
414 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 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 if cmd == IdeCommand::READ_DMA_EXT || cmd == IdeCommand::WRITE_DMA_EXT {
445 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 self.write_drive_register(
467 DriveRegister::SectorCount,
468 (eint13_cmd.block_count >> 8) as u8,
469 bus_master_state,
470 );
471 }
472
473 self.write_drive_register(
475 DriveRegister::SectorCount,
476 eint13_cmd.block_count as u8,
477 bus_master_state,
478 );
479
480 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 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 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 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 self.write_drive_register(DriveRegister::StatusCmd, cmd.0, bus_master_state);
527
528 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 let old_features_reg = self.state.shadow_features_reg;
565 self.write_drive_register(DriveRegister::ErrorFeatures, 0, bus_master_state);
566
567 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 self.write_drive_register(
581 DriveRegister::StatusCmd,
582 IdeCommand::PACKET_COMMAND.0,
583 bus_master_state,
584 );
585
586 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 let mut command = [0; 12];
599 command[..cdb.as_bytes().len()].copy_from_slice(cdb.as_bytes());
600
601 self.write_drive_data(command.as_bytes(), bus_master_state);
604
605 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 self.write_bus_master_reg(BusMasterReg::COMMAND, &[0], bus_master_state)
639 .unwrap();
640
641 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 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 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 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 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 let skip = (write.skip_bytes_head as usize).min(remaining);
700 remaining -= skip;
701 self.skip_drive_data(skip, bus_master_state);
702
703 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 self.skip_drive_data(remaining, bus_master_state);
727 }
728
729 self.write_drive_register(
731 DriveRegister::ErrorFeatures,
732 write.old_features_reg,
733 bus_master_state,
734 );
735
736 self.write_drive_register(
738 DriveRegister::AlternateStatusDeviceControl,
739 write.old_adapter_control_reg,
740 bus_master_state,
741 );
742
743 tracing::trace!("enlightened cd write completed");
744 write.deferred.complete();
745 }
746
747 fn perform_dma_memory_phase(&mut self) {
748 let Some(drive) = &mut self.drives[self.state.current_drive_idx] else {
749 return;
750 };
751
752 if self.bus_master_state.dma_error {
753 if drive.handle_read_dma_descriptor_error() {
754 self.bus_master_state.dma_error = false;
755 }
756 return;
757 }
758
759 let mut dma_avail = match drive.dma_request() {
760 Some((dma_type, avail)) if *dma_type == self.bus_master_state.dma_io_type() => {
761 avail as u32
762 }
763 _ => {
764 return;
766 }
767 };
768
769 let Some(dma) = &mut self.bus_master_state.dma_state else {
770 return;
771 };
772
773 while dma_avail > 0 {
774 if dma.transfer_bytes_left == 0 {
776 assert!(!dma.transfer_complete);
777
778 let descriptor_addr: u64 = self
785 .bus_master_state
786 .desc_table_ptr
787 .wrapping_add(8 * (dma.descriptor_idx as u32))
788 .into();
789
790 let cur_desc_table_entry = match self
791 .guest_memory
792 .read_plain::<protocol::BusMasterDmaDesc>(descriptor_addr)
793 {
794 Ok(cur_desc_table_entry) => cur_desc_table_entry,
795 Err(err) => {
796 self.bus_master_state.dma_state = None;
797 if !drive.handle_read_dma_descriptor_error() {
798 self.bus_master_state.dma_error = true;
799 }
800 tracelimit::error_ratelimited!(
801 error = &err as &dyn std::error::Error,
802 "dma descriptor read error"
803 );
804 return;
805 }
806 };
807
808 tracing::trace!(entry = ?cur_desc_table_entry, "read dma desc");
809
810 dma.transfer_bytes_left = cur_desc_table_entry.byte_count.into();
811 if cur_desc_table_entry.byte_count == 0 {
813 dma.transfer_bytes_left = 0x10000;
814 }
815
816 dma.transfer_base_addr = cur_desc_table_entry.mem_physical_base.into();
817 dma.transfer_complete = (cur_desc_table_entry.end_of_table & 0x80) != 0;
818
819 dma.descriptor_idx += 1;
821 if dma.transfer_complete {
822 dma.descriptor_idx = 0;
823 }
824 }
825
826 let bytes_to_transfer = dma_avail.min(dma.transfer_bytes_left);
828
829 assert!(bytes_to_transfer != 0);
830
831 if let Err(err) = drive.dma_transfer(
832 &self.guest_memory,
833 dma.transfer_base_addr,
834 bytes_to_transfer as usize,
835 ) {
836 self.bus_master_state.dma_state = None;
840 if !drive.handle_read_dma_descriptor_error() {
841 self.bus_master_state.dma_error = true;
842 }
843 tracelimit::error_ratelimited!(
844 error = &err as &dyn std::error::Error,
845 "dma transfer memory access error"
846 );
847 return;
848 }
849
850 dma_avail -= bytes_to_transfer;
851 dma.transfer_base_addr += bytes_to_transfer as u64;
852 dma.transfer_bytes_left -= bytes_to_transfer;
853 if dma.transfer_bytes_left == 0 && dma.transfer_complete {
854 if dma_avail > 0 {
855 drive.set_prd_exhausted();
857 drive.dma_advance_buffer(dma_avail as usize);
858 }
859 tracing::trace!("dma transfer is complete");
860 self.bus_master_state.dma_state = None;
861 break;
862 }
863 }
864 }
865}
866
867impl ChangeDeviceState for IdeDevice {
868 fn start(&mut self) {}
869
870 async fn stop(&mut self) {}
871
872 async fn reset(&mut self) {
873 self.bus_master_pio_dynamic.unmap();
874 self.bus_master_state = BusMasterState::new();
875 for channel in &mut self.channels {
876 channel.reset();
877 }
878 }
879}
880
881impl ChipsetDevice for IdeDevice {
882 fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
883 Some(self)
884 }
885
886 fn supports_pci(&mut self) -> Option<&mut dyn PciConfigSpace> {
887 Some(self)
888 }
889
890 fn supports_poll_device(&mut self) -> Option<&mut dyn PollDevice> {
891 Some(self)
892 }
893}
894
895impl PollDevice for IdeDevice {
896 fn poll_device(&mut self, cx: &mut Context<'_>) {
897 for channel in &mut self.channels {
898 channel.poll_device(cx, &self.bus_master_state);
899 }
900 }
901}
902
903impl PortIoIntercept for IdeDevice {
904 fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
905 match self.parse_port(io_port) {
906 Some((port, index)) => match port {
907 Port::Data => {
908 self.channels[index].read_drive_data(data, &self.bus_master_state);
909 IoResult::Ok
910 }
911 Port::Drive(register) => {
912 data[0] =
913 self.channels[index].read_drive_register(register, &self.bus_master_state);
914 IoResult::Ok
915 }
916 Port::Enlightened => IoResult::Err(IoError::InvalidRegister),
917 Port::BusMaster(offset) => self.channels[index].read_bus_master_reg(offset, data),
918 },
919 None => IoResult::Err(IoError::InvalidRegister),
920 }
921 }
922
923 fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
924 match self.parse_port(io_port) {
925 Some((port, index)) => match port {
926 Port::Data => {
927 self.channels[index].write_drive_data(data, &self.bus_master_state);
928 IoResult::Ok
929 }
930 Port::Drive(register) => {
931 self.channels[index].write_drive_register(
932 register,
933 data[0],
934 &self.bus_master_state,
935 );
936 IoResult::Ok
937 }
938 Port::Enlightened => {
939 self.channels[index].enlightened_port_write(data, &self.bus_master_state)
940 }
941 Port::BusMaster(offset) => {
942 self.channels[index].write_bus_master_reg(offset, data, &self.bus_master_state)
943 }
944 },
945 None => IoResult::Err(IoError::InvalidRegister),
946 }
947 }
948
949 fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u16>)] {
950 &[
951 (
952 "ide primary channel",
953 IdeIoPort::PRI_ENLIGHTENED.0..=IdeIoPort::PRI_STATUS_CMD.0,
954 ),
955 (
956 "ide primary channel control",
957 IdeIoPort::PRI_ALT_STATUS_DEVICE_CTL.0..=IdeIoPort::PRI_ALT_STATUS_DEVICE_CTL.0,
958 ),
959 (
960 "ide secondary channel",
961 IdeIoPort::SEC_ENLIGHTENED.0..=IdeIoPort::SEC_STATUS_CMD.0,
962 ),
963 (
964 "ide secondary channel control",
965 IdeIoPort::SEC_ALT_STATUS_DEVICE_CTL.0..=IdeIoPort::SEC_ALT_STATUS_DEVICE_CTL.0,
966 ),
967 ]
968 }
969}
970
971impl PciConfigSpace for IdeDevice {
972 fn pci_cfg_read(&mut self, offset: u16, mut value: ByteEnabledDwordRead<'_>) -> IoResult {
973 value.set(if offset < HEADER_TYPE_00_SIZE {
974 match HeaderType00(offset) {
975 HeaderType00::DEVICE_VENDOR => protocol::BX_PCI_ISA_BRIDGE_IDE_IDREG_VALUE,
976 HeaderType00::STATUS_COMMAND => self.bus_master_state.cmd_status_reg,
977 HeaderType00::CLASS_REVISION => protocol::BX_PCI_IDE_CLASS_WORD,
978 HeaderType00::BAR4 => self.bus_master_state.port_addr_reg,
979 offset => {
980 tracing::debug!(?offset, "undefined type00 header read");
981 0
982 }
983 }
984 } else {
985 match IdeConfigSpace(offset) {
986 IdeConfigSpace::PRIMARY_TIMING_REG_ADDR => self.bus_master_state.timing_reg,
987 IdeConfigSpace::SECONDARY_TIMING_REG_ADDR => {
988 self.bus_master_state.secondary_timing_reg
989 }
990 IdeConfigSpace::UDMA_CTL_REG_ADDR => self.bus_master_state.dma_ctl_reg,
991 IdeConfigSpace::MANUFACTURE_ID_REG_ADDR => {
992 0x00000F30
995 }
996 offset => {
997 tracing::trace!(?offset, "undefined ide pci config space read");
999 return IoResult::Err(IoError::InvalidRegister);
1000 }
1001 }
1002 });
1003
1004 tracing::trace!(?offset, ?value, "ide pci config space read");
1005 IoResult::Ok
1006 }
1007
1008 fn pci_cfg_write(&mut self, offset: u16, value: ByteEnabledDwordWrite) -> IoResult {
1009 if offset < HEADER_TYPE_00_SIZE {
1010 let offset = HeaderType00(offset);
1011 tracing::trace!(?offset, ?value, "ide pci config space write");
1012
1013 const BUS_MASTER_IO_ENABLE_MASK: u32 = Command::new()
1014 .with_pio_enabled(true)
1015 .with_bus_master(true)
1016 .into_bits() as u32;
1017
1018 match offset {
1019 HeaderType00::STATUS_COMMAND => {
1020 let value = value.merge(self.bus_master_state.cmd_status_reg);
1022 self.bus_master_state.cmd_status_reg &= !(0x38000000 & value);
1023 self.bus_master_state.cmd_status_reg &= !BUS_MASTER_IO_ENABLE_MASK;
1025
1026 self.bus_master_state.cmd_status_reg |= value & BUS_MASTER_IO_ENABLE_MASK;
1027
1028 if (self.bus_master_state.cmd_status_reg
1030 & protocol::CFCS_BUS_MASTER_IO_ENABLE_MASK)
1031 != protocol::CFCS_BUS_MASTER_IO_ENABLE_MASK
1032 {
1033 self.bus_master_pio_dynamic.unmap();
1034 tracing::trace!("disabling bus master io range");
1035 } else {
1036 let first_port = (self.bus_master_state.port_addr_reg as u16) & 0xFFF0;
1039 tracing::trace!(?first_port, "enabling bus master range");
1040
1041 self.bus_master_pio_dynamic.map(first_port);
1044 }
1045 }
1046 HeaderType00::BAR4 => {
1047 let value = value.merge(self.bus_master_state.port_addr_reg);
1049 self.bus_master_state.port_addr_reg =
1050 (value & 0x0000FFF0) | DEFAULT_BUS_MASTER_PORT_ADDR_REG;
1051 }
1052 _ => tracing::debug!(?offset, "undefined type00 header write"),
1053 }
1054 } else {
1055 let offset = IdeConfigSpace(offset);
1056 tracing::trace!(?offset, ?value, "ide pci config space write");
1057
1058 match offset {
1059 IdeConfigSpace::PRIMARY_TIMING_REG_ADDR => {
1060 value.merge_into(&mut self.bus_master_state.timing_reg)
1061 }
1062 IdeConfigSpace::SECONDARY_TIMING_REG_ADDR => {
1063 value.merge_into(&mut self.bus_master_state.secondary_timing_reg)
1064 }
1065 IdeConfigSpace::UDMA_CTL_REG_ADDR => {
1066 value.merge_into(&mut self.bus_master_state.dma_ctl_reg)
1067 }
1068 _ => tracing::trace!(?offset, "undefined ide pci config space write"),
1069 }
1070 }
1071
1072 IoResult::Ok
1073 }
1074
1075 fn suggested_bdf(&mut self) -> Option<(u8, u8, u8)> {
1076 Some((0, 7, 1)) }
1078}
1079
1080enum ChannelType {
1082 Primary,
1083 Secondary,
1084}
1085
1086#[derive(Inspect)]
1087#[inspect(tag = "drive_type")]
1088pub enum DriveMedia {
1089 HardDrive(#[inspect(rename = "backend")] Disk),
1090 OpticalDrive(#[inspect(rename = "backend")] Arc<dyn AsyncScsiDisk>),
1091}
1092
1093impl DriveMedia {
1094 pub fn hard_disk(disk: Disk) -> Self {
1095 DriveMedia::HardDrive(disk)
1096 }
1097
1098 pub fn optical_disk(scsi_disk: Arc<dyn AsyncScsiDisk>) -> Self {
1099 DriveMedia::OpticalDrive(scsi_disk)
1100 }
1101}
1102
1103#[derive(Debug, Default, Inspect)]
1104struct ChannelState {
1105 current_drive_idx: usize,
1106 shadow_adapter_control_reg: u8,
1107 shadow_features_reg: u8,
1108}
1109
1110#[derive(InspectMut)]
1111struct Channel {
1112 #[inspect(mut, with = "inspect_drives")]
1113 drives: [Option<DiskDrive>; 2],
1114 interrupt: LineInterrupt,
1115 state: ChannelState,
1116 bus_master_state: ChannelBusMasterState,
1117 enlightened_write: Option<EnlightenedWrite>,
1118 guest_memory: GuestMemory,
1119 #[inspect(skip)]
1120 channel: u8,
1121}
1122
1123fn inspect_drives(drives: &mut [Option<DiskDrive>]) -> impl '_ + InspectMut {
1124 inspect::adhoc_mut(|req| {
1125 let mut resp = req.respond();
1126 for (i, drive) in drives.iter_mut().enumerate() {
1127 resp.field_mut(&i.to_string(), drive);
1128 }
1129 })
1130}
1131
1132impl Channel {
1133 fn new(
1134 channel_drives: [Option<DriveMedia>; 2],
1135 channel_type: ChannelType,
1136 interrupt: LineInterrupt,
1137 guest_memory: GuestMemory,
1138 ) -> Result<Self, NewDeviceError> {
1139 let [primary_media, secondary_media] = channel_drives;
1140
1141 let channel_number = match channel_type {
1142 ChannelType::Primary => 0,
1143 ChannelType::Secondary => 1,
1144 };
1145
1146 Ok(Self {
1147 drives: [
1148 primary_media
1149 .map(|media| {
1150 DiskDrive::new(
1151 media,
1152 IdePath {
1153 channel: channel_number,
1154 drive: 0,
1155 },
1156 )
1157 })
1158 .transpose()?,
1159 secondary_media
1160 .map(|media| {
1161 DiskDrive::new(
1162 media,
1163 IdePath {
1164 channel: channel_number,
1165 drive: 1,
1166 },
1167 )
1168 })
1169 .transpose()?,
1170 ],
1171 interrupt,
1172 state: ChannelState::default(),
1173 bus_master_state: ChannelBusMasterState::default(),
1174 enlightened_write: None,
1175 guest_memory,
1176 channel: channel_number,
1177 })
1178 }
1179
1180 fn reset(&mut self) {
1181 tracelimit::info_ratelimited!(channel = self.channel, "channel reset");
1182 self.interrupt.set_level(false);
1183 self.state = ChannelState::default();
1184 self.bus_master_state = ChannelBusMasterState::default();
1185 for drive in self.drives.iter_mut().flatten() {
1186 drive.reset();
1187 }
1188 }
1189
1190 fn poll_device(&mut self, cx: &mut Context<'_>, bus_master_state: &BusMasterState) {
1191 for drive in self.drives.iter_mut().flatten() {
1192 drive.poll_device(cx);
1193 }
1194 self.post_drive_access(bus_master_state);
1195 }
1196
1197 fn current_drive_status(&mut self) -> Option<Status> {
1198 if let Some(drive) = &mut self.drives[self.state.current_drive_idx] {
1199 let status = drive.read_register(DriveRegister::AlternateStatusDeviceControl);
1200 Some(Status::from_bits(status))
1201 } else {
1202 None
1203 }
1204 }
1205
1206 fn drive_status(&mut self, drive_index: usize) -> Status {
1207 assert!(self.drives[drive_index].is_some());
1209 let status = self.drives[drive_index]
1210 .as_mut()
1211 .unwrap()
1212 .read_register(DriveRegister::AlternateStatusDeviceControl);
1213 Status::from_bits(status)
1214 }
1215
1216 fn current_drive_type(&self) -> Option<DriveType> {
1217 self.drives[self.state.current_drive_idx]
1218 .as_ref()
1219 .map(|drive| drive.drive_type())
1220 }
1221
1222 fn drive_type(&mut self, drive_index: usize) -> DriveType {
1223 assert!(self.drives[drive_index].is_some());
1224 self.drives[drive_index]
1225 .as_ref()
1226 .map(|drive| drive.drive_type())
1227 .unwrap()
1228 }
1229
1230 fn interrupt_pending(&self) -> bool {
1231 self.drives
1232 .iter()
1233 .flatten()
1234 .any(|drive| drive.interrupt_pending())
1235 }
1236
1237 fn post_drive_access(&mut self, bus_master_state: &BusMasterState) {
1238 self.perform_dma_memory_phase();
1240
1241 if let Some(enlightened_write) = &self.enlightened_write {
1243 let drive_index = match enlightened_write {
1244 EnlightenedWrite::Hard(enlightened_hdd_write) => enlightened_hdd_write.drive_index,
1245 EnlightenedWrite::Optical(enlightened_cd_write) => enlightened_cd_write.drive_index,
1246 };
1247
1248 let status = self.drive_status(drive_index);
1249 let completed = match self.drive_type(drive_index) {
1250 DriveType::Hard => !(status.bsy() || status.drq()),
1251 DriveType::Optical => status.drdy(),
1252 };
1253 if completed {
1254 let write = self.enlightened_write.take().unwrap();
1256 match write {
1257 EnlightenedWrite::Hard(write) => {
1258 self.complete_enlightened_hdd_write(write, bus_master_state)
1259 }
1260 EnlightenedWrite::Optical(write) => {
1261 self.complete_enlightened_cd_write(write, bus_master_state)
1262 }
1263 }
1264 }
1265 }
1266
1267 let interrupt = self.interrupt_pending();
1269 if interrupt {
1270 tracing::trace!(channel = self.channel, interrupt, "post_drive_access");
1271 self.bus_master_state.status_reg.set_interrupt(true);
1272 }
1273 self.interrupt.set_level(interrupt);
1274 }
1275
1276 fn read_drive_register(
1279 &mut self,
1280 port: DriveRegister,
1281 bus_master_state: &BusMasterState,
1282 ) -> u8 {
1283 let mut drive = self.drives[self.state.current_drive_idx].as_mut();
1285 if drive.is_none() {
1286 drive = self.drives[0].as_mut();
1287 }
1288
1289 let data = if let Some(drive) = drive {
1290 drive.read_register(port)
1293 } else {
1294 0x7f
1297 };
1298
1299 tracing::trace!(?port, ?data, channel = self.channel, "io port read");
1300 self.post_drive_access(bus_master_state);
1301 data
1302 }
1303
1304 fn write_drive_register(
1308 &mut self,
1309 port: DriveRegister,
1310 data: u8,
1311 bus_master_state: &BusMasterState,
1312 ) {
1313 tracing::trace!(?port, ?data, channel = self.channel, "io port write");
1314
1315 match port {
1316 DriveRegister::DeviceHead => {
1317 self.state.current_drive_idx = ((data >> 4) & 1) as usize;
1319 }
1320 DriveRegister::AlternateStatusDeviceControl => {
1321 self.state.shadow_adapter_control_reg = data;
1323 let v = DeviceControlReg::from_bits_truncate(data);
1324 if v.reset() && (self.drives[0].is_some() || self.drives[1].is_some()) {
1325 self.state = ChannelState::default();
1326 }
1327 }
1328 DriveRegister::ErrorFeatures => {
1329 self.state.shadow_features_reg = data;
1331 }
1332 _ => {}
1333 }
1334
1335 if let Some(drive) = &mut self.drives[1] {
1337 drive.write_register(port, data);
1338 }
1339 if let Some(drive) = &mut self.drives[0] {
1340 drive.write_register(port, data);
1341 }
1342
1343 self.post_drive_access(bus_master_state);
1344 }
1345
1346 fn read_drive_data(&mut self, data: &mut [u8], bus_master_state: &BusMasterState) {
1347 let mut drive = self.drives[self.state.current_drive_idx].as_mut();
1349 if drive.is_none() {
1350 drive = self.drives[0].as_mut();
1351 }
1352
1353 data.fill(0xff);
1354 data[0] = 0x7f;
1356
1357 if let Some(drive) = drive {
1358 drive.pio_read(data);
1359 };
1360
1361 self.post_drive_access(bus_master_state);
1362 }
1363
1364 fn skip_drive_data(&mut self, mut len: usize, bus_master_state: &BusMasterState) {
1365 let mut buf = [0; 512];
1366 while len > 0 {
1367 let this_len = len.min(buf.len());
1368 let buf = &mut buf[..this_len];
1369 self.read_drive_data(buf, bus_master_state);
1370 len -= buf.len();
1371 }
1372 }
1373
1374 fn write_drive_data(&mut self, data: &[u8], bus_master_state: &BusMasterState) {
1375 if let Some(drive) = &mut self.drives[0] {
1376 drive.pio_write(data);
1377 }
1378 if let Some(drive) = &mut self.drives[1] {
1379 drive.pio_write(data);
1380 }
1381 self.post_drive_access(bus_master_state);
1382 }
1383
1384 fn read_bus_master_reg(&mut self, bus_master_reg: BusMasterReg, data: &mut [u8]) -> IoResult {
1385 let data_len = data.len();
1386 match bus_master_reg {
1387 BusMasterReg::COMMAND => match data_len {
1388 1 | 2 => data.copy_from_slice(
1389 &self.bus_master_state.command_reg.into_bits().to_ne_bytes()[..data_len],
1390 ),
1391 _ => return IoResult::Err(IoError::InvalidAccessSize),
1392 },
1393 BusMasterReg::STATUS => {
1394 let mut status = self.bus_master_state.status_reg;
1395
1396 if self.bus_master_state.dma_state.is_some() {
1397 status.set_active(true);
1398 }
1399
1400 match data_len {
1401 1 | 2 => data.copy_from_slice(&status.into_bits().to_ne_bytes()[..data_len]),
1402 _ => return IoResult::Err(IoError::InvalidAccessSize),
1403 }
1404 }
1405 BusMasterReg::TABLE_PTR => match data_len {
1406 2 | 4 => data.copy_from_slice(
1407 &self.bus_master_state.desc_table_ptr.to_ne_bytes()[..data_len],
1408 ),
1409 _ => return IoResult::Err(IoError::InvalidAccessSize),
1410 },
1411 BusMasterReg::TABLE_PTR2 => {
1412 if data_len == 2 {
1413 data.copy_from_slice(&self.bus_master_state.desc_table_ptr.to_ne_bytes()[2..4]);
1414 } else {
1415 return IoResult::Err(IoError::InvalidAccessSize);
1416 }
1417 }
1418 _ => return IoResult::Err(IoError::InvalidRegister),
1419 }
1420
1421 tracing::trace!(?bus_master_reg, ?data, "bus master register read");
1422 IoResult::Ok
1423 }
1424
1425 fn write_bus_master_reg(
1426 &mut self,
1427 bus_master_reg: BusMasterReg,
1428 data: &[u8],
1429 bus_master_state: &BusMasterState,
1430 ) -> IoResult {
1431 let value: u64 = match data.len() {
1432 1 => u8::from_ne_bytes(data.as_bytes().try_into().unwrap()).into(),
1433 2 => u16::from_ne_bytes(data.as_bytes().try_into().unwrap()).into(),
1434 4 => u32::from_ne_bytes(data.as_bytes().try_into().unwrap()).into(),
1435 _ => return IoResult::Err(IoError::InvalidAccessSize),
1436 };
1437
1438 tracing::trace!(?bus_master_reg, value, "bus master register write");
1439
1440 match bus_master_reg {
1441 BusMasterReg::COMMAND => {
1442 if data.len() > 2 {
1445 return IoResult::Err(IoError::InvalidAccessSize);
1446 }
1447
1448 let old_value = self.bus_master_state.command_reg;
1449 let mut new_value = BusMasterCommandReg::from_bits_truncate(value as u32);
1451
1452 if old_value.start() {
1454 new_value.set_write(old_value.write());
1457 if !new_value.start() {
1458 self.bus_master_state.dma_state = None
1459 }
1460 } else if new_value.start() {
1461 self.bus_master_state.dma_state = Some(Default::default());
1462 };
1463
1464 self.bus_master_state.command_reg = new_value;
1465 }
1466 BusMasterReg::STATUS => {
1467 if data.len() > 2 {
1468 return IoResult::Err(IoError::InvalidAccessSize);
1469 }
1470
1471 let value = BusMasterStatusReg::from_bits_truncate(value as u32);
1472 let old_value = self.bus_master_state.status_reg;
1473 let mut new_value = old_value.with_settable(value.settable());
1474
1475 if value.interrupt() {
1477 new_value.set_interrupt(false);
1478 }
1479 if value.dma_error() {
1480 new_value.set_dma_error(false);
1481 }
1482
1483 tracing::trace!(?old_value, ?new_value, "set bus master status");
1484 self.bus_master_state.status_reg = new_value;
1485 }
1486 BusMasterReg::TABLE_PTR => {
1487 if data.len() < 2 {
1488 return IoResult::Err(IoError::InvalidAccessSize);
1489 }
1490
1491 if data.len() == 4 {
1492 self.bus_master_state.desc_table_ptr = value as u32 & 0xffff_fffc;
1494 } else {
1495 self.bus_master_state.desc_table_ptr = (self.bus_master_state.desc_table_ptr
1498 & 0xffff_0000)
1499 | (value as u32 & 0x0000_fffc);
1500 }
1501 }
1502 BusMasterReg::TABLE_PTR2 => {
1503 self.bus_master_state.desc_table_ptr = (self.bus_master_state.desc_table_ptr
1506 & 0xffff)
1507 | ((value as u32 & 0xffff) << 16);
1508 }
1509 _ => return IoResult::Err(IoError::InvalidRegister),
1510 }
1511
1512 self.post_drive_access(bus_master_state);
1513 IoResult::Ok
1514 }
1515}
1516
1517#[derive(Debug, Copy, Clone, PartialEq)]
1518enum DriveType {
1519 Hard,
1520 Optical,
1521}
1522
1523#[derive(Debug, Default, Inspect)]
1524struct DmaState {
1525 descriptor_idx: u8,
1526 transfer_complete: bool,
1527 transfer_bytes_left: u32,
1528 transfer_base_addr: u64,
1529}
1530
1531mod save_restore {
1532 use super::*;
1533 use vmcore::save_restore::RestoreError;
1534 use vmcore::save_restore::SaveError;
1535 use vmcore::save_restore::SaveRestore;
1536
1537 mod state {
1538 use crate::drive::save_restore::state::SavedDriveState;
1539 use mesh::payload::Protobuf;
1540 use vmcore::save_restore::SavedStateRoot;
1541
1542 #[derive(Protobuf)]
1543 #[mesh(package = "storage.ide.controller")]
1544 pub struct SavedBusMasterState {
1545 #[mesh(1)]
1546 pub cmd_status_reg: u32,
1547 #[mesh(2)]
1548 pub port_addr_reg: u32,
1549 #[mesh(3)]
1550 pub timing_reg: u32,
1551 #[mesh(4)]
1552 pub secondary_timing_reg: u32,
1553 #[mesh(5)]
1554 pub dma_ctl_reg: u32,
1555 }
1556
1557 #[derive(Protobuf, SavedStateRoot)]
1558 #[mesh(package = "storage.ide.controller")]
1559 pub struct SavedState {
1560 #[mesh(1)]
1561 pub bus_master: SavedBusMasterState,
1562 #[mesh(2)]
1563 pub channel0: SavedChannelState,
1564 #[mesh(3)]
1565 pub channel1: SavedChannelState,
1566 }
1567
1568 #[derive(Protobuf)]
1569 #[mesh(package = "storage.ide.controller")]
1570 pub struct SavedDmaState {
1571 #[mesh(1)]
1572 pub descriptor_idx: u8,
1573 #[mesh(2)]
1574 pub transfer_complete: bool,
1575 #[mesh(3)]
1576 pub transfer_bytes_left: u32,
1577 #[mesh(4)]
1578 pub transfer_base_addr: u64,
1579 }
1580
1581 #[derive(Protobuf)]
1582 #[mesh(package = "storage.ide.controller")]
1583 pub struct SavedChannelBusMasterState {
1584 #[mesh(1)]
1585 pub command_reg: u32,
1586 #[mesh(2)]
1587 pub status_reg: u32,
1588 #[mesh(3)]
1589 pub desc_table_ptr: u32,
1590 #[mesh(4)]
1591 pub dma_state: Option<SavedDmaState>,
1592 #[mesh(5)]
1593 pub dma_error: bool,
1594 }
1595
1596 #[derive(Protobuf)]
1597 #[mesh(package = "storage.ide.controller")]
1598 pub struct SavedChannelState {
1599 #[mesh(1)]
1600 pub current_drive_idx: u8,
1601 #[mesh(2)]
1602 pub shadow_adapter_control_reg: u8,
1603 #[mesh(3)]
1604 pub shadow_features_reg: u8,
1605 #[mesh(4)]
1606 pub bus_master: SavedChannelBusMasterState,
1607 #[mesh(5)]
1608 pub drive0: Option<SavedDriveState>,
1609 #[mesh(6)]
1610 pub drive1: Option<SavedDriveState>,
1611 }
1612 }
1613
1614 impl SaveRestore for IdeDevice {
1615 type SavedState = state::SavedState;
1616
1617 fn save(&mut self) -> Result<Self::SavedState, SaveError> {
1618 let BusMasterState {
1619 cmd_status_reg,
1620 port_addr_reg,
1621 timing_reg,
1622 secondary_timing_reg,
1623 dma_ctl_reg,
1624 } = self.bus_master_state;
1625
1626 let bus_master = state::SavedBusMasterState {
1627 cmd_status_reg,
1628 port_addr_reg,
1629 timing_reg,
1630 secondary_timing_reg,
1631 dma_ctl_reg,
1632 };
1633
1634 let saved_state = state::SavedState {
1635 bus_master,
1636 channel0: self.channels[0].save()?,
1637 channel1: self.channels[1].save()?,
1638 };
1639
1640 Ok(saved_state)
1641 }
1642
1643 fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
1644 let state::SavedState {
1645 bus_master:
1646 state::SavedBusMasterState {
1647 cmd_status_reg,
1648 port_addr_reg,
1649 timing_reg,
1650 secondary_timing_reg,
1651 dma_ctl_reg,
1652 },
1653 channel0,
1654 channel1,
1655 } = state;
1656
1657 self.bus_master_state = BusMasterState {
1658 cmd_status_reg,
1659 port_addr_reg,
1660 timing_reg,
1661 secondary_timing_reg,
1662 dma_ctl_reg,
1663 };
1664
1665 self.channels[0].restore(channel0)?;
1666 self.channels[1].restore(channel1)?;
1667
1668 Ok(())
1669 }
1670 }
1671
1672 #[derive(Debug, Error)]
1673 enum ChannelRestoreError {
1674 #[error("missing drive for state")]
1675 MissingDriveForState,
1676 #[error("missing state for drive")]
1677 MissingStateForDrive,
1678 }
1679
1680 impl Channel {
1681 fn save(&mut self) -> Result<state::SavedChannelState, SaveError> {
1682 assert!(self.enlightened_write.is_none());
1684
1685 let ChannelState {
1686 current_drive_idx,
1687 shadow_adapter_control_reg,
1688 shadow_features_reg,
1689 } = self.state;
1690
1691 let ChannelBusMasterState {
1692 command_reg,
1693 status_reg,
1694 desc_table_ptr,
1695 dma_state,
1696 dma_error,
1697 } = &self.bus_master_state;
1698
1699 let saved_state = state::SavedChannelState {
1700 current_drive_idx: current_drive_idx as u8,
1701 shadow_adapter_control_reg,
1702 shadow_features_reg,
1703 bus_master: state::SavedChannelBusMasterState {
1704 command_reg: command_reg.into_bits(),
1705 status_reg: status_reg.into_bits(),
1706 desc_table_ptr: *desc_table_ptr,
1707 dma_state: dma_state.as_ref().map(|dma| {
1708 let DmaState {
1709 descriptor_idx,
1710 transfer_complete,
1711 transfer_bytes_left,
1712 transfer_base_addr,
1713 } = dma;
1714
1715 state::SavedDmaState {
1716 descriptor_idx: *descriptor_idx,
1717 transfer_complete: *transfer_complete,
1718 transfer_bytes_left: *transfer_bytes_left,
1719 transfer_base_addr: *transfer_base_addr,
1720 }
1721 }),
1722 dma_error: *dma_error,
1723 },
1724 drive0: self.drives[0]
1725 .as_mut()
1726 .map(|drive| drive.save())
1727 .transpose()?,
1728 drive1: self.drives[1]
1729 .as_mut()
1730 .map(|drive| drive.save())
1731 .transpose()?,
1732 };
1733
1734 Ok(saved_state)
1735 }
1736
1737 fn restore(&mut self, state: state::SavedChannelState) -> Result<(), RestoreError> {
1738 let state::SavedChannelState {
1739 current_drive_idx,
1740 shadow_adapter_control_reg,
1741 shadow_features_reg,
1742 bus_master:
1743 state::SavedChannelBusMasterState {
1744 command_reg,
1745 status_reg,
1746 desc_table_ptr,
1747 dma_state,
1748 dma_error,
1749 },
1750 drive0,
1751 drive1,
1752 } = state;
1753
1754 self.state = ChannelState {
1755 current_drive_idx: current_drive_idx as usize,
1756 shadow_adapter_control_reg,
1757 shadow_features_reg,
1758 };
1759
1760 self.bus_master_state = ChannelBusMasterState {
1761 command_reg: BusMasterCommandReg::from_bits(command_reg),
1762 status_reg: BusMasterStatusReg::from_bits(status_reg),
1763 desc_table_ptr,
1764 dma_state: dma_state.map(|dma| {
1765 let state::SavedDmaState {
1766 descriptor_idx,
1767 transfer_complete,
1768 transfer_bytes_left,
1769 transfer_base_addr,
1770 } = dma;
1771
1772 DmaState {
1773 descriptor_idx,
1774 transfer_complete,
1775 transfer_bytes_left,
1776 transfer_base_addr,
1777 }
1778 }),
1779 dma_error,
1780 };
1781
1782 for (drive, state) in self.drives.iter_mut().zip([drive0, drive1]) {
1783 match (drive, state) {
1784 (Some(drive), Some(state)) => drive.restore(state)?,
1785 (None, None) => {}
1786 (Some(_), None) => {
1787 return Err(RestoreError::InvalidSavedState(
1788 ChannelRestoreError::MissingStateForDrive.into(),
1789 ));
1790 }
1791 (None, Some(_)) => {
1792 return Err(RestoreError::InvalidSavedState(
1793 ChannelRestoreError::MissingDriveForState.into(),
1794 ));
1795 }
1796 }
1797 }
1798
1799 self.interrupt.set_level(self.interrupt_pending());
1800 Ok(())
1801 }
1802 }
1803}
1804
1805#[cfg(test)]
1806mod tests {
1807 use super::*;
1808 use crate::IdeIoPort;
1809 use crate::protocol::BusMasterDmaDesc;
1810 use crate::protocol::DeviceHeadReg;
1811 use crate::protocol::IdeCommand;
1812 use chipset_device::pio::ExternallyManagedPortIoIntercepts;
1813 use disk_file::FileDisk;
1814 use pal_async::async_test;
1815 use scsidisk::atapi_scsi::AtapiScsiDisk;
1816 use scsidisk::scsidvd::SimpleScsiDvd;
1817 use std::fs::File;
1818 use std::future::poll_fn;
1819 use std::io::Read;
1820 use std::io::Write;
1821 use std::task::Poll;
1822 use tempfile::NamedTempFile;
1823 use test_with_tracing::test;
1824 use zerocopy::FromBytes;
1825 use zerocopy::FromZeros;
1826 use zerocopy::IntoBytes;
1827
1828 #[async_test]
1829 async fn restore_updates_interrupt_without_processing_drive_work() {
1830 use vmcore::line_interrupt::test_helpers::TestLineInterruptTarget;
1831 use vmcore::save_restore::SaveRestore;
1832 use vmcore::save_restore::SavedStateBlob;
1833
1834 fn controller(file: &NamedTempFile, drive: u8, interrupt: LineInterrupt) -> IdeDevice {
1835 let disk = Disk::new(FileDisk::open(file.reopen().unwrap(), false).unwrap()).unwrap();
1836 let mut drives = [None, None];
1837 drives[usize::from(drive)] = Some(DriveMedia::hard_disk(disk));
1838 IdeDevice::new(
1839 GuestMemory::allocate(16 * 1024),
1840 &mut ExternallyManagedPortIoIntercepts,
1841 drives,
1842 [None, None],
1843 interrupt,
1844 LineInterrupt::detached(),
1845 )
1846 .unwrap()
1847 }
1848
1849 for drive in [0, 1] {
1850 for pending in [false, true] {
1851 let file = NamedTempFile::new().unwrap();
1852 file.as_file().set_len(1024 * 1024).unwrap();
1853 let mut source = controller(&file, drive, LineInterrupt::detached());
1854 if pending {
1855 source
1856 .io_write(
1857 IdeIoPort::PRI_DEVICE_HEAD.0,
1858 &[DeviceHeadReg::new().with_dev(drive != 0).into_bits()],
1859 )
1860 .unwrap();
1861 execute_command(
1862 &mut source,
1863 &IdePath { channel: 0, drive },
1864 IdeCommand::IDENTIFY_DEVICE.0,
1865 );
1866 }
1867 source.channels[0]
1870 .bus_master_state
1871 .status_reg
1872 .set_interrupt(false);
1873 let saved = SavedStateBlob::new(source.save().unwrap());
1874 let target = TestLineInterruptTarget::new_arc();
1875 let mut destination = controller(
1876 &file,
1877 drive,
1878 LineInterrupt::new_with_target("IDE", target.clone(), 0),
1879 );
1880 destination.channels[0].interrupt.set_level(!pending);
1881 destination.restore(saved.parse().unwrap()).unwrap();
1882 assert_eq!(target.is_high(0), pending);
1883 assert!(
1884 !destination.channels[0]
1885 .bus_master_state
1886 .status_reg
1887 .interrupt()
1888 );
1889 }
1890 }
1891 }
1892
1893 #[derive(Debug, Inspect)]
1894 struct MediaGeometry {
1895 sectors_per_track: u32,
1896 cylinder_count: u32,
1897 head_count: u32,
1898 total_sectors: u64,
1899 }
1900
1901 impl MediaGeometry {
1902 fn new(total_sectors: u64, sector_size: u32) -> Result<Self, NewDeviceError> {
1903 if total_sectors > protocol::MAX_BYTES_48BIT_LBA / sector_size as u64 {
1904 return Err(NewDeviceError::DiskTooLarge(
1905 total_sectors * sector_size as u64,
1906 ));
1907 }
1908 let hard_drive_sectors = total_sectors.min(protocol::MAX_CHS_SECTORS as u64);
1909 let mut sectors_per_track;
1910 let mut cylinders_times_heads;
1911 let mut head_count;
1912
1913 if hard_drive_sectors > (16 * 63 * 0xFFFF) {
1914 sectors_per_track = 255;
1915 head_count = 16;
1916 cylinders_times_heads = hard_drive_sectors / (sectors_per_track as u64);
1917 } else {
1918 sectors_per_track = 17;
1919 cylinders_times_heads = hard_drive_sectors / (sectors_per_track as u64);
1920
1921 head_count = std::cmp::max((cylinders_times_heads as u32).div_ceil(1024), 4);
1922
1923 if (cylinders_times_heads >= (head_count as u64) * 1024) || head_count > 16 {
1924 head_count = 16;
1926 sectors_per_track = 31;
1927 cylinders_times_heads = hard_drive_sectors / (sectors_per_track as u64);
1928 }
1929
1930 if cylinders_times_heads >= (head_count as u64) * 1024 {
1931 head_count = 16;
1933 sectors_per_track = 63;
1934 cylinders_times_heads = hard_drive_sectors / (sectors_per_track as u64);
1935 }
1936 }
1937 Ok(MediaGeometry {
1938 sectors_per_track,
1939 cylinder_count: (cylinders_times_heads / (head_count as u64)) as u32,
1940 head_count,
1941 total_sectors,
1942 })
1943 }
1944 }
1945
1946 struct CommandParams {
1947 sector_count: u8,
1948 sector_num: u8,
1949 cylinder_lsb: u8,
1950 cylinder_msb: u8,
1951 device_head: u8,
1952 }
1953
1954 #[expect(dead_code)]
1955 enum Addressing {
1956 Chs,
1957 Lba28Bit,
1958 Lba48Bit,
1959 }
1960
1961 fn ide_test_setup(
1962 guest_memory: Option<GuestMemory>,
1963 drive_type: DriveType,
1964 ) -> (IdeDevice, File, Vec<u32>, MediaGeometry) {
1965 let test_guest_mem = match guest_memory {
1966 Some(test_gm) => test_gm,
1967 None => GuestMemory::allocate(16 * 1024),
1968 };
1969
1970 let temp_file = NamedTempFile::new().unwrap();
1972 let mut handle1 = temp_file.reopen().unwrap();
1973 let handle2 = temp_file.reopen().unwrap();
1974 let data = (0..0x100000_u32).collect::<Vec<_>>();
1975 handle1.write_all(data.as_bytes()).unwrap();
1976
1977 let disk = Disk::new(FileDisk::open(handle1, false).unwrap()).unwrap();
1978 let geometry = MediaGeometry::new(disk.sector_count(), disk.sector_size()).unwrap();
1979
1980 let media = match drive_type {
1981 DriveType::Hard => DriveMedia::hard_disk(disk),
1982 DriveType::Optical => DriveMedia::optical_disk(Arc::new(AtapiScsiDisk::new(Arc::new(
1983 SimpleScsiDvd::new(Some(disk)),
1984 )))),
1985 };
1986
1987 let ide_device = IdeDevice::new(
1988 test_guest_mem,
1989 &mut ExternallyManagedPortIoIntercepts,
1990 [Some(media), None],
1991 [None, None],
1992 LineInterrupt::detached(),
1993 LineInterrupt::detached(),
1994 )
1995 .unwrap();
1996
1997 (ide_device, handle2, data, geometry)
1998 }
1999
2000 fn get_status(ide_controller: &mut IdeDevice, dev_path: &IdePath) -> Status {
2002 let mut data = [0_u8; 1];
2003 ide_controller
2004 .io_read(
2005 io_port(IdeIoPort::PRI_STATUS_CMD, dev_path.channel.into()),
2006 &mut data,
2007 )
2008 .unwrap();
2009
2010 Status::from_bits(data[0])
2011 }
2012
2013 async fn check_status_loop(ide_device: &mut IdeDevice, dev_path: &IdePath) -> Status {
2014 wait_for(ide_device, |ide_device| {
2016 let status: Status = get_status(ide_device, dev_path);
2017 (!status.bsy() && !status.drq()).then_some(status)
2018 })
2019 .await
2020 }
2021
2022 async fn check_command_ready(ide_device: &mut IdeDevice, dev_path: &IdePath) -> Status {
2023 wait_for(ide_device, |ide_device| {
2025 let status: Status = get_status(ide_device, dev_path);
2026 (!status.bsy() && status.drdy()).then_some(status)
2027 })
2028 .await
2029 }
2030
2031 fn io_port(io_port: IdeIoPort, channel_idx: usize) -> u16 {
2032 if channel_idx == 0 {
2033 io_port.0
2034 } else {
2035 io_port.0 - IdeIoPort::PRI_DATA.0 + IdeIoPort::SEC_DATA.0
2036 }
2037 }
2038
2039 fn write_command_params(
2042 controller: &mut IdeDevice,
2043 dev_path: &IdePath,
2044 sector: u32,
2045 sector_count: u8,
2046 addr: Addressing,
2047 geometry: &MediaGeometry,
2048 ) {
2049 let channel_idx: usize = dev_path.channel as usize;
2050
2051 let io_params = match addr {
2052 Addressing::Chs => {
2053 let sectors_per_track = geometry.sectors_per_track;
2054 let head_count = geometry.head_count;
2055
2056 let sector_num: u8 = ((sector % sectors_per_track) as u8) + 1;
2057 let cylinders: u16 = (sector / (head_count * sectors_per_track)) as u16;
2058 let cylinder_lsb: u8 = cylinders as u8;
2059 let cylinder_msb: u8 = (cylinders >> 8) as u8;
2060 let device_head: u8 = (sector / sectors_per_track % head_count) as u8;
2061
2062 CommandParams {
2063 sector_count,
2064 sector_num,
2065 cylinder_lsb,
2066 cylinder_msb,
2067 device_head,
2068 }
2069 }
2070 Addressing::Lba28Bit => {
2071 let sector_num = sector as u8;
2072 let cylinder = (sector & 0x00FF_FF00) >> 8;
2073 let cylinder_lsb: u8 = cylinder as u8;
2074 let cylinder_msb: u8 = (cylinder >> 8) as u8;
2075 let device_head = DeviceHeadReg::new()
2076 .with_head((sector >> 24) as u8)
2077 .with_lba(true)
2078 .into();
2079
2080 CommandParams {
2081 sector_count,
2082 sector_num,
2083 cylinder_lsb,
2084 cylinder_msb,
2085 device_head,
2086 }
2087 }
2088 Addressing::Lba48Bit => todo!(),
2089 };
2090
2091 controller
2092 .io_write(
2093 io_port(IdeIoPort::PRI_SECTOR_COUNT, channel_idx),
2094 &[io_params.sector_count],
2095 )
2096 .unwrap();
2097 controller
2098 .io_write(
2099 io_port(IdeIoPort::PRI_SECTOR_NUM, channel_idx),
2100 &[io_params.sector_num],
2101 )
2102 .unwrap();
2103 controller
2104 .io_write(
2105 io_port(IdeIoPort::PRI_CYLINDER_LSB, channel_idx),
2106 &[io_params.cylinder_lsb],
2107 )
2108 .unwrap();
2109 controller
2110 .io_write(
2111 io_port(IdeIoPort::PRI_CYLINDER_MSB, channel_idx),
2112 &[io_params.cylinder_msb],
2113 )
2114 .unwrap();
2115 controller
2116 .io_write(
2117 io_port(IdeIoPort::PRI_DEVICE_HEAD, channel_idx),
2118 &[io_params.device_head],
2119 )
2120 .unwrap();
2121 }
2122
2123 async fn device_select(ide_controller: &mut IdeDevice, dev_path: &IdePath) {
2125 check_status_loop(ide_controller, dev_path).await;
2126
2127 let dev_idx: u8 = dev_path.drive;
2128 ide_controller
2129 .io_write(
2130 io_port(IdeIoPort::PRI_DEVICE_HEAD, dev_path.channel.into()),
2131 &[dev_idx],
2132 )
2133 .unwrap();
2134
2135 check_status_loop(ide_controller, dev_path).await;
2136 }
2137
2138 fn execute_command(ide_controller: &mut IdeDevice, dev_path: &IdePath, command: u8) {
2140 ide_controller
2141 .io_write(
2142 io_port(IdeIoPort::PRI_STATUS_CMD, dev_path.channel.into()),
2143 &[command],
2144 )
2145 .unwrap();
2146 }
2147
2148 fn execute_soft_reset_command(ide_controller: &mut IdeDevice, dev_path: &IdePath, command: u8) {
2149 ide_controller
2150 .io_write(
2151 io_port(
2152 IdeIoPort::PRI_ALT_STATUS_DEVICE_CTL,
2153 dev_path.channel.into(),
2154 ),
2155 &[command],
2156 )
2157 .unwrap();
2158 }
2159
2160 fn get_dma_state(ide_controller: &mut IdeDevice, dev_path: &IdePath) -> bool {
2161 ide_controller.channels[dev_path.channel as usize]
2163 .bus_master_state
2164 .dma_state
2165 .is_some()
2166 }
2167
2168 fn prep_ide_channel(ide_controller: &mut IdeDevice, drive_type: DriveType, dev_path: &IdePath) {
2169 match drive_type {
2170 DriveType::Hard => {
2171 execute_command(ide_controller, dev_path, IdeCommand::SET_MULTI_BLOCK_MODE.0);
2174 }
2175 DriveType::Optical => {
2176 }
2178 }
2179 }
2180
2181 async fn wait_for<T>(
2183 ide_device: &mut IdeDevice,
2184 mut f: impl FnMut(&mut IdeDevice) -> Option<T>,
2185 ) -> T {
2186 poll_fn(|cx| {
2187 ide_device.poll_device(cx);
2188 let r = f(ide_device);
2189 if let Some(r) = r {
2190 Poll::Ready(r)
2191 } else {
2192 Poll::Pending
2193 }
2194 })
2195 .await
2196 }
2197
2198 #[async_test]
2201 async fn write_sectors_test() {
2202 const START_SECTOR: u32 = 0;
2203 const SECTOR_COUNT: u8 = 4;
2204
2205 let dev_path = IdePath::default();
2206 let (mut ide_device, mut disk, _file_contents, geometry) =
2207 ide_test_setup(None, DriveType::Hard);
2208
2209 device_select(&mut ide_device, &dev_path).await;
2211 prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2212
2213 write_command_params(
2215 &mut ide_device,
2216 &dev_path,
2217 START_SECTOR,
2218 SECTOR_COUNT,
2219 Addressing::Lba28Bit,
2220 &geometry,
2221 );
2222
2223 execute_command(&mut ide_device, &dev_path, IdeCommand::WRITE_SECTORS.0);
2224
2225 let status = get_status(&mut ide_device, &dev_path);
2227 assert!(status.drq() && !status.bsy());
2228
2229 let data = &[0xFF_u8; 2][..];
2231 for _ in 0..SECTOR_COUNT {
2232 let status = check_command_ready(&mut ide_device, &dev_path).await;
2233 assert!(status.drq());
2234 assert!(!status.err());
2235 for _ in 0..protocol::HARD_DRIVE_SECTOR_BYTES / 2 {
2236 ide_device.io_write(IdeIoPort::PRI_DATA.0, data).unwrap();
2237 }
2238 }
2239
2240 let status = check_command_ready(&mut ide_device, &dev_path).await;
2241 assert!(!status.err());
2242 assert!(!status.drq());
2243
2244 let buffer =
2245 &mut [0_u8; (protocol::HARD_DRIVE_SECTOR_BYTES * SECTOR_COUNT as u32) as usize][..];
2246 disk.read_exact(buffer).unwrap();
2247 for byte in buffer {
2248 assert_eq!(*byte, 0xFF);
2249 }
2250 }
2251
2252 #[async_test]
2253 async fn software_reset_test() {
2254 const START_SECTOR: u32 = 0;
2255 const SECTOR_COUNT: u8 = 4;
2256
2257 let dev_path = IdePath::default();
2258 let (mut ide_device, _disk, _file_contents, geometry) =
2259 ide_test_setup(None, DriveType::Hard);
2260
2261 device_select(&mut ide_device, &dev_path).await;
2263 prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2264
2265 write_command_params(
2267 &mut ide_device,
2268 &dev_path,
2269 START_SECTOR,
2270 SECTOR_COUNT,
2271 Addressing::Lba28Bit,
2272 &geometry,
2273 );
2274
2275 execute_command(&mut ide_device, &dev_path, IdeCommand::WRITE_SECTORS.0);
2276 let status = get_status(&mut ide_device, &dev_path);
2278 assert!(status.drq() && !status.bsy());
2279
2280 execute_soft_reset_command(&mut ide_device, &dev_path, IdeCommand::SOFT_RESET.0);
2281 let status = get_status(&mut ide_device, &dev_path);
2282 assert!(status.bsy());
2283 }
2284
2285 #[async_test]
2287 async fn read_sectors_test() {
2288 const START_SECTOR: u32 = 0;
2289 const SECTOR_COUNT: u8 = 4;
2290
2291 let dev_path = IdePath::default();
2292 let (mut ide_device, _disk, file_contents, geometry) =
2293 ide_test_setup(None, DriveType::Hard);
2294
2295 device_select(&mut ide_device, &dev_path).await;
2297 prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2298
2299 write_command_params(
2301 &mut ide_device,
2302 &dev_path,
2303 START_SECTOR,
2304 SECTOR_COUNT,
2305 Addressing::Lba28Bit,
2306 &geometry,
2307 );
2308
2309 execute_command(&mut ide_device, &dev_path, IdeCommand::READ_SECTORS.0);
2311
2312 let status = check_command_ready(&mut ide_device, &dev_path).await;
2313 assert!(status.drq());
2314 assert!(!status.err());
2315
2316 let content_bytes = file_contents.as_bytes();
2318 for sector in 0..SECTOR_COUNT {
2319 let status = check_command_ready(&mut ide_device, &dev_path).await;
2320 assert!(status.drq());
2321 assert!(!status.err());
2322 for word in 0..protocol::HARD_DRIVE_SECTOR_BYTES / 2 {
2323 let data = &mut [0, 0][..];
2324 ide_device.io_read(IdeIoPort::PRI_DATA.0, data).unwrap();
2325
2326 let i = sector as usize * protocol::HARD_DRIVE_SECTOR_BYTES as usize / 2
2327 + word as usize;
2328 assert_eq!(data[0], content_bytes[i * 2]);
2329 assert_eq!(data[1], content_bytes[i * 2 + 1]);
2330 }
2331 }
2332 }
2333
2334 async fn enlightened_cmd_test(drive_type: DriveType) {
2336 const SECTOR_COUNT: u16 = 4;
2337 const BYTE_COUNT: u16 = SECTOR_COUNT * protocol::HARD_DRIVE_SECTOR_BYTES as u16;
2338
2339 let test_guest_mem = GuestMemory::allocate(16384);
2340
2341 let table_gpa = 0x1000;
2342 let data_gpa = 0x2000;
2343 test_guest_mem
2344 .write_plain(
2345 table_gpa,
2346 &BusMasterDmaDesc {
2347 mem_physical_base: data_gpa,
2348 byte_count: BYTE_COUNT,
2349 unused: 0,
2350 end_of_table: 0x80,
2351 },
2352 )
2353 .unwrap();
2354
2355 let (data_buffer, byte_count) = match drive_type {
2356 DriveType::Hard => (table_gpa as u32, 0),
2357 DriveType::Optical => (data_gpa, BYTE_COUNT.into()),
2358 };
2359
2360 let eint13_command = protocol::EnlightenedInt13Command {
2361 command: IdeCommand::READ_DMA_EXT,
2362 device_head: DeviceHeadReg::new().with_lba(true),
2363 flags: 0,
2364 result_status: 0,
2365 lba_low: 0,
2366 lba_high: 0,
2367 block_count: SECTOR_COUNT,
2368 byte_count,
2369 data_buffer,
2370 skip_bytes_head: 0,
2371 skip_bytes_tail: 0,
2372 };
2373 test_guest_mem.write_plain(0, &eint13_command).unwrap();
2374
2375 let dev_path = IdePath::default();
2376 let (mut ide_device, _disk, file_contents, _geometry) =
2377 ide_test_setup(Some(test_guest_mem.clone()), drive_type);
2378
2379 device_select(&mut ide_device, &dev_path).await;
2381 prep_ide_channel(&mut ide_device, drive_type, &dev_path);
2382
2383 let r = ide_device.io_write(IdeIoPort::PRI_ENLIGHTENED.0, 0_u32.as_bytes()); match r {
2387 IoResult::Defer(mut deferred) => {
2388 poll_fn(|cx| {
2389 ide_device.poll_device(cx);
2390 deferred.poll_write(cx)
2391 })
2392 .await
2393 .unwrap();
2394 }
2395 _ => panic!("{:?}", r),
2396 }
2397
2398 let mut buffer = vec![0u8; BYTE_COUNT as usize];
2399 test_guest_mem
2400 .read_at(data_gpa.into(), &mut buffer)
2401 .unwrap();
2402 assert_eq!(buffer, file_contents.as_bytes()[..buffer.len()]);
2403 }
2404
2405 #[async_test]
2407 async fn enlightened_cd_cmd_test() {
2408 enlightened_cmd_test(DriveType::Optical).await
2409 }
2410
2411 #[async_test]
2412 async fn enlightened_hdd_cmd_test() {
2413 enlightened_cmd_test(DriveType::Hard).await
2414 }
2415
2416 #[async_test]
2419 async fn enlightened_cmd_test_incomplete_prd() {
2420 const SECTOR_COUNT: u16 = 8;
2421 const BYTE_COUNT: u16 = SECTOR_COUNT * protocol::HARD_DRIVE_SECTOR_BYTES as u16;
2422
2423 let test_guest_mem = GuestMemory::allocate(16384);
2424
2425 let table_gpa = 0x1000;
2426 let data_gpa = 0x2000;
2427 test_guest_mem
2428 .write_plain(
2429 table_gpa,
2430 &BusMasterDmaDesc {
2431 mem_physical_base: data_gpa,
2432 byte_count: BYTE_COUNT / 2,
2433 unused: 0,
2434 end_of_table: 0x80, },
2436 )
2437 .unwrap();
2438 test_guest_mem
2439 .write_plain(
2440 table_gpa + size_of::<BusMasterDmaDesc>() as u64,
2441 &BusMasterDmaDesc {
2442 mem_physical_base: data_gpa,
2443 byte_count: BYTE_COUNT / 2,
2444 unused: 0,
2445 end_of_table: 0x80,
2446 },
2447 )
2448 .unwrap();
2449
2450 let data_buffer = table_gpa as u32;
2451 let byte_count = 0;
2452
2453 let eint13_command = protocol::EnlightenedInt13Command {
2454 command: IdeCommand::READ_DMA_EXT,
2455 device_head: DeviceHeadReg::new().with_lba(true),
2456 flags: 0,
2457 result_status: 0,
2458 lba_low: 0,
2459 lba_high: 0,
2460 block_count: SECTOR_COUNT,
2461 byte_count,
2462 data_buffer,
2463 skip_bytes_head: 0,
2464 skip_bytes_tail: 0,
2465 };
2466 test_guest_mem.write_plain(0, &eint13_command).unwrap();
2467
2468 let dev_path = IdePath::default();
2469 let (mut ide_device, _disk, file_contents, _geometry) =
2470 ide_test_setup(Some(test_guest_mem.clone()), DriveType::Hard);
2471
2472 device_select(&mut ide_device, &dev_path).await;
2474 prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2475
2476 let r = ide_device.io_write(IdeIoPort::PRI_ENLIGHTENED.0, 0_u32.as_bytes()); match r {
2480 IoResult::Defer(mut deferred) => {
2481 poll_fn(|cx| {
2482 ide_device.poll_device(cx);
2483 deferred.poll_write(cx)
2484 })
2485 .await
2486 .unwrap();
2487 }
2488 _ => panic!("{:?}", r),
2489 }
2490
2491 let mut buffer = vec![0u8; BYTE_COUNT as usize / 2];
2492 test_guest_mem
2493 .read_at(data_gpa.into(), &mut buffer)
2494 .unwrap();
2495 assert_eq!(buffer, file_contents.as_bytes()[..buffer.len()]);
2496 }
2497
2498 #[async_test]
2499 async fn enlightened_cmd_test_dma_boundary_overflow() {
2500 const SECTOR_COUNT: u16 = 16; const BYTE_COUNT: u16 = SECTOR_COUNT * protocol::HARD_DRIVE_SECTOR_BYTES as u16;
2506
2507 let test_guest_mem = GuestMemory::allocate(16384);
2508
2509 let table_gpa = 0x1000;
2510 let data_gpa = 0x3000; test_guest_mem
2512 .write_plain(
2513 table_gpa,
2514 &BusMasterDmaDesc {
2515 mem_physical_base: data_gpa,
2516 byte_count: BYTE_COUNT, unused: 0,
2518 end_of_table: 0x80,
2519 },
2520 )
2521 .unwrap();
2522
2523 let data_buffer = table_gpa as u32;
2524 let byte_count = 0;
2525
2526 let eint13_command = protocol::EnlightenedInt13Command {
2527 command: IdeCommand::READ_DMA_ALT,
2528 device_head: DeviceHeadReg::new().with_lba(true),
2529 flags: 0,
2530 result_status: 0,
2531 lba_low: 0,
2532 lba_high: 0,
2533 block_count: SECTOR_COUNT,
2534 byte_count,
2535 data_buffer,
2536 skip_bytes_head: 0,
2537 skip_bytes_tail: 0,
2538 };
2539 test_guest_mem.write_plain(0, &eint13_command).unwrap();
2540
2541 let dev_path = IdePath::default();
2542 let (mut ide_device, _disk, _file_contents, _geometry) =
2543 ide_test_setup(Some(test_guest_mem.clone()), DriveType::Hard);
2544
2545 device_select(&mut ide_device, &dev_path).await;
2546 prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2547
2548 let r = ide_device.io_write(IdeIoPort::PRI_ENLIGHTENED.0, 0_u32.as_bytes());
2549
2550 match r {
2551 IoResult::Defer(mut deferred) => {
2552 poll_fn(|cx| {
2553 ide_device.poll_device(cx);
2554 deferred.poll_write(cx)
2555 })
2556 .await
2557 .unwrap();
2558 }
2559 _ => panic!("{:?}", r),
2560 }
2561
2562 let dma_state = get_dma_state(&mut ide_device, &dev_path);
2563 assert!(
2564 !dma_state,
2565 "Expected DMA state cleared - transfer from 0x{:x} with {} bytes overflows valid range 0x0-0x3FFF",
2566 data_gpa, BYTE_COUNT
2567 );
2568 }
2569
2570 #[async_test]
2571 async fn enlightened_cmd_test_dma_exact_boundary() {
2572 const BYTE_COUNT: u16 = 512;
2577
2578 let test_guest_mem = GuestMemory::allocate(16384);
2579
2580 let table_gpa = 0x1000;
2581 let data_gpa = 0x3E00; test_guest_mem
2583 .write_plain(
2584 table_gpa,
2585 &BusMasterDmaDesc {
2586 mem_physical_base: data_gpa,
2587 byte_count: BYTE_COUNT, unused: 0,
2589 end_of_table: 0x80,
2590 },
2591 )
2592 .unwrap();
2593
2594 let data_buffer = table_gpa as u32;
2595 let byte_count = 0;
2596
2597 let eint13_command = protocol::EnlightenedInt13Command {
2598 command: IdeCommand::READ_DMA_ALT,
2599 device_head: DeviceHeadReg::new().with_lba(true),
2600 flags: 0,
2601 result_status: 0,
2602 lba_low: 0,
2603 lba_high: 0,
2604 block_count: 1,
2605 byte_count,
2606 data_buffer,
2607 skip_bytes_head: 0,
2608 skip_bytes_tail: 0,
2609 };
2610 test_guest_mem.write_plain(0, &eint13_command).unwrap();
2611
2612 let dev_path = IdePath::default();
2613 let (mut ide_device, _disk, _file_contents, _geometry) =
2614 ide_test_setup(Some(test_guest_mem.clone()), DriveType::Hard);
2615
2616 device_select(&mut ide_device, &dev_path).await;
2617 prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2618
2619 let r = ide_device.io_write(IdeIoPort::PRI_ENLIGHTENED.0, 0_u32.as_bytes());
2620
2621 match r {
2622 IoResult::Defer(mut deferred) => {
2623 poll_fn(|cx| {
2624 ide_device.poll_device(cx);
2625 deferred.poll_write(cx)
2626 })
2627 .await
2628 .unwrap();
2629 }
2630 _ => panic!("{:?}", r),
2631 }
2632
2633 let dma_state = get_dma_state(&mut ide_device, &dev_path);
2634 assert!(
2635 !dma_state,
2636 "Expected DMA state cleared - transfer ending at 0x{:x} exceeds valid range",
2637 data_gpa + BYTE_COUNT as u32
2638 );
2639 }
2640
2641 #[async_test]
2642 async fn enlightened_cmd_test_dma_integer_overflow() {
2643 let test_guest_mem = GuestMemory::allocate(16384);
2649
2650 let table_gpa = 0x1000;
2651 let data_gpa = 0x2000;
2652 test_guest_mem
2653 .write_plain(
2654 table_gpa,
2655 &BusMasterDmaDesc {
2656 mem_physical_base: data_gpa,
2657 byte_count: 0xFFFF, unused: 0,
2659 end_of_table: 0x80,
2660 },
2661 )
2662 .unwrap();
2663
2664 let data_buffer = table_gpa as u32;
2665 let byte_count = 0;
2666
2667 let eint13_command = protocol::EnlightenedInt13Command {
2668 command: IdeCommand::READ_DMA_ALT,
2669 device_head: DeviceHeadReg::new().with_lba(true),
2670 flags: 0,
2671 result_status: 0,
2672 lba_low: 0,
2673 lba_high: 0,
2674 block_count: 128, byte_count,
2676 data_buffer,
2677 skip_bytes_head: 0,
2678 skip_bytes_tail: 0,
2679 };
2680 test_guest_mem.write_plain(0, &eint13_command).unwrap();
2681
2682 let dev_path = IdePath::default();
2683 let (mut ide_device, _disk, _file_contents, _geometry) =
2684 ide_test_setup(Some(test_guest_mem.clone()), DriveType::Hard);
2685
2686 device_select(&mut ide_device, &dev_path).await;
2687 prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2688
2689 let r = ide_device.io_write(IdeIoPort::PRI_ENLIGHTENED.0, 0_u32.as_bytes());
2690
2691 match r {
2692 IoResult::Defer(mut deferred) => {
2693 poll_fn(|cx| {
2694 ide_device.poll_device(cx);
2695 deferred.poll_write(cx)
2696 })
2697 .await
2698 .unwrap();
2699 }
2700 _ => panic!("{:?}", r),
2701 }
2702
2703 let dma_state = get_dma_state(&mut ide_device, &dev_path);
2704 assert!(
2705 !dma_state,
2706 "Expected DMA state cleared - large byte_count 0xFFFF should be rejected"
2707 );
2708 }
2709
2710 #[async_test]
2711 async fn enlightened_cmd_test_dma_u32_max_overflow() {
2712 let test_guest_mem = GuestMemory::allocate(16384);
2716
2717 let table_gpa = 0x1000;
2718 let data_gpa = 0xFFFF_F000_u32; test_guest_mem
2720 .write_plain(
2721 table_gpa,
2722 &BusMasterDmaDesc {
2723 mem_physical_base: data_gpa,
2724 byte_count: 0x2000, unused: 0,
2726 end_of_table: 0x80,
2727 },
2728 )
2729 .unwrap();
2730
2731 let data_buffer = table_gpa as u32;
2732 let byte_count = 0;
2733
2734 let eint13_command = protocol::EnlightenedInt13Command {
2735 command: IdeCommand::READ_DMA_ALT,
2736 device_head: DeviceHeadReg::new().with_lba(true),
2737 flags: 0,
2738 result_status: 0,
2739 lba_low: 0,
2740 lba_high: 0,
2741 block_count: 16,
2742 byte_count,
2743 data_buffer,
2744 skip_bytes_head: 0,
2745 skip_bytes_tail: 0,
2746 };
2747 test_guest_mem.write_plain(0, &eint13_command).unwrap();
2748
2749 let dev_path = IdePath::default();
2750 let (mut ide_device, _disk, _file_contents, _geometry) =
2751 ide_test_setup(Some(test_guest_mem.clone()), DriveType::Hard);
2752
2753 device_select(&mut ide_device, &dev_path).await;
2754 prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2755
2756 let r = ide_device.io_write(IdeIoPort::PRI_ENLIGHTENED.0, 0_u32.as_bytes());
2757
2758 match r {
2759 IoResult::Defer(mut deferred) => {
2760 poll_fn(|cx| {
2761 ide_device.poll_device(cx);
2762 deferred.poll_write(cx)
2763 })
2764 .await
2765 .unwrap();
2766 }
2767 _ => panic!("{:?}", r),
2768 }
2769
2770 let dma_state = get_dma_state(&mut ide_device, &dev_path);
2771 assert!(
2772 !dma_state,
2773 "Expected DMA state cleared - checked_add should catch u32 overflow from 0x{:x} + 0x2000",
2774 data_gpa
2775 );
2776 }
2777
2778 #[async_test]
2779 async fn enlightened_cmd_test_dma_zero_byte_count() {
2780 let test_guest_mem = GuestMemory::allocate(16384);
2784
2785 let table_gpa = 0x1000;
2786 let data_gpa = 0x1000;
2787 test_guest_mem
2788 .write_plain(
2789 table_gpa,
2790 &BusMasterDmaDesc {
2791 mem_physical_base: data_gpa,
2792 byte_count: 0, unused: 0,
2794 end_of_table: 0x80,
2795 },
2796 )
2797 .unwrap();
2798
2799 let data_buffer = table_gpa as u32;
2800 let byte_count = 0;
2801
2802 let eint13_command = protocol::EnlightenedInt13Command {
2803 command: IdeCommand::READ_DMA_ALT,
2804 device_head: DeviceHeadReg::new().with_lba(true),
2805 flags: 0,
2806 result_status: 0,
2807 lba_low: 0,
2808 lba_high: 0,
2809 block_count: 128, byte_count,
2811 data_buffer,
2812 skip_bytes_head: 0,
2813 skip_bytes_tail: 0,
2814 };
2815 test_guest_mem.write_plain(0, &eint13_command).unwrap();
2816
2817 let dev_path = IdePath::default();
2818 let (mut ide_device, _disk, _file_contents, _geometry) =
2819 ide_test_setup(Some(test_guest_mem.clone()), DriveType::Hard);
2820
2821 device_select(&mut ide_device, &dev_path).await;
2822 prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
2823
2824 let r = ide_device.io_write(IdeIoPort::PRI_ENLIGHTENED.0, 0_u32.as_bytes());
2825
2826 match r {
2827 IoResult::Defer(mut deferred) => {
2828 poll_fn(|cx| {
2829 ide_device.poll_device(cx);
2830 deferred.poll_write(cx)
2831 })
2832 .await
2833 .unwrap();
2834 }
2835 _ => panic!("{:?}", r),
2836 }
2837
2838 let dma_state = get_dma_state(&mut ide_device, &dev_path);
2839 assert!(
2840 !dma_state,
2841 "Expected DMA state cleared - byte_count=0 implies 64KB which exceeds guest memory"
2842 );
2843 }
2844
2845 fn medium_removal_cdb(prevent: bool) -> [u8; 12] {
2847 let cdb = scsi::CdbMediaRemoval {
2848 operation_code: ScsiOp::MEDIUM_REMOVAL,
2849 lun: 0,
2850 reserved: [0; 2],
2851 flags: scsi::MediaRemovalFlags::new()
2852 .with_prevent(prevent)
2853 .with_persistent(prevent),
2854 };
2855 let mut command = [0; 12];
2856 command[..cdb.as_bytes().len()].copy_from_slice(cdb.as_bytes());
2857 command
2858 }
2859
2860 fn unsupported_cdb() -> [u8; 12] {
2863 let mut command = [0; 12];
2864 command[0] = ScsiOp::WRITE.0;
2865 command
2866 }
2867
2868 fn eject_cdb() -> [u8; 12] {
2870 let cdb = scsi::StartStop {
2871 operation_code: ScsiOp::START_STOP_UNIT,
2872 immediate: 0,
2873 reserved2: [0; 2],
2874 flag: scsi::StartStopFlags::new()
2875 .with_start(false)
2876 .with_load_eject(true),
2877 control: 0,
2878 };
2879 let mut command = [0; 12];
2880 command[..cdb.as_bytes().len()].copy_from_slice(cdb.as_bytes());
2881 command
2882 }
2883
2884 async fn atapi_packet_command(
2887 ide_device: &mut IdeDevice,
2888 dev_path: &IdePath,
2889 cdb: &[u8; 12],
2890 ) -> Status {
2891 execute_command(ide_device, dev_path, IdeCommand::PACKET_COMMAND.0);
2892 let status = check_command_ready(ide_device, dev_path).await;
2893 assert!(status.drq(), "the drive should be waiting for the CDB");
2894 for chunk in cdb.chunks(2) {
2895 ide_device.io_write(IdeIoPort::PRI_DATA.0, chunk).unwrap();
2896 }
2897 check_command_ready(ide_device, dev_path).await
2898 }
2899
2900 #[async_test]
2910 async fn atapi_reset_ends_the_nexus() {
2911 let dev_path = IdePath::default();
2912 let (mut ide_device, _disk, _file_contents, _geometry) =
2913 ide_test_setup(None, DriveType::Optical);
2914
2915 device_select(&mut ide_device, &dev_path).await;
2916
2917 let status =
2918 atapi_packet_command(&mut ide_device, &dev_path, &medium_removal_cdb(true)).await;
2919 assert!(!status.err(), "locking the tray should succeed");
2920
2921 let status = atapi_packet_command(&mut ide_device, &dev_path, &eject_cdb()).await;
2923 assert!(status.err(), "a locked tray must refuse the eject");
2924
2925 ide_device.reset().await;
2926 device_select(&mut ide_device, &dev_path).await;
2927
2928 let status = atapi_packet_command(&mut ide_device, &dev_path, &eject_cdb()).await;
2931 assert!(!status.err(), "the tray lock outlived the drive reset");
2932 }
2933
2934 #[async_test]
2946 async fn atapi_reset_drops_the_command_left_in_flight() {
2947 let dev_path = IdePath::default();
2948 let (mut ide_device, _disk, _file_contents, _geometry) =
2949 ide_test_setup(None, DriveType::Optical);
2950
2951 device_select(&mut ide_device, &dev_path).await;
2952
2953 let status = atapi_packet_command(&mut ide_device, &dev_path, &unsupported_cdb()).await;
2957 assert!(status.err(), "an unsupported command must be refused");
2958
2959 execute_command(&mut ide_device, &dev_path, IdeCommand::PACKET_COMMAND.0);
2961 let status = check_command_ready(&mut ide_device, &dev_path).await;
2962 assert!(status.drq(), "the drive should be waiting for the CDB");
2963 for chunk in unsupported_cdb().chunks(2) {
2964 ide_device.io_write(IdeIoPort::PRI_DATA.0, chunk).unwrap();
2965 }
2966
2967 ide_device.reset().await;
2968
2969 poll_fn(|cx| {
2971 ide_device.poll_device(cx);
2972 Poll::Ready(())
2973 })
2974 .await;
2975
2976 let status = get_status(&mut ide_device, &dev_path);
2977 assert!(
2978 !status.err(),
2979 "a command left in flight by the reset completed afterwards"
2980 );
2981 }
2982
2983 #[async_test]
2989 async fn atapi_device_reset_command_ends_the_nexus() {
2990 let dev_path = IdePath::default();
2991 let (mut ide_device, _disk, _file_contents, _geometry) =
2992 ide_test_setup(None, DriveType::Optical);
2993
2994 device_select(&mut ide_device, &dev_path).await;
2995
2996 let status =
2997 atapi_packet_command(&mut ide_device, &dev_path, &medium_removal_cdb(true)).await;
2998 assert!(!status.err(), "locking the tray should succeed");
2999
3000 let status = atapi_packet_command(&mut ide_device, &dev_path, &eject_cdb()).await;
3001 assert!(status.err(), "a locked tray must refuse the eject");
3002
3003 execute_command(&mut ide_device, &dev_path, IdeCommand::DEVICE_RESET.0);
3007
3008 let status = atapi_packet_command(&mut ide_device, &dev_path, &eject_cdb()).await;
3009 assert!(
3010 !status.err(),
3011 "the tray lock outlived the DEVICE RESET command"
3012 );
3013 }
3014
3015 #[async_test]
3018 async fn atapi_software_reset_ends_the_nexus() {
3019 let dev_path = IdePath::default();
3020 let (mut ide_device, _disk, _file_contents, _geometry) =
3021 ide_test_setup(None, DriveType::Optical);
3022
3023 device_select(&mut ide_device, &dev_path).await;
3024
3025 let status =
3026 atapi_packet_command(&mut ide_device, &dev_path, &medium_removal_cdb(true)).await;
3027 assert!(!status.err(), "locking the tray should succeed");
3028
3029 let status = atapi_packet_command(&mut ide_device, &dev_path, &eject_cdb()).await;
3030 assert!(status.err(), "a locked tray must refuse the eject");
3031
3032 execute_soft_reset_command(&mut ide_device, &dev_path, IdeCommand::SOFT_RESET.0);
3033
3034 let status = atapi_packet_command(&mut ide_device, &dev_path, &eject_cdb()).await;
3035 assert!(!status.err(), "the tray lock outlived the software reset");
3036 }
3037
3038 #[async_test]
3039 async fn identify_test_cd() {
3040 let dev_path = IdePath::default();
3041 let (mut ide_device, _disk, _file_contents, _geometry) =
3042 ide_test_setup(None, DriveType::Optical);
3043
3044 device_select(&mut ide_device, &dev_path).await;
3046 prep_ide_channel(&mut ide_device, DriveType::Optical, &dev_path);
3047
3048 execute_command(
3050 &mut ide_device,
3051 &dev_path,
3052 IdeCommand::IDENTIFY_PACKET_DEVICE.0,
3053 );
3054
3055 let status = check_command_ready(&mut ide_device, &dev_path).await;
3056 assert!(status.drq());
3057 assert!(!status.err());
3058
3059 let data = &mut [0_u8; protocol::IDENTIFY_DEVICE_BYTES];
3061 ide_device.io_read(IdeIoPort::PRI_DATA.0, data).unwrap();
3062 let features = protocol::IdeFeatures::read_from_prefix(&data[..])
3063 .unwrap()
3064 .0; let ex_features = protocol::IdeFeatures {
3066 config_bits: 0x85C0,
3067 serial_no: *b" ",
3068 buffer_size: 0x0080,
3069 firmware_revision: *b" ",
3070 model_number: "iVtrau lDC ".as_bytes()[..]
3071 .try_into()
3072 .unwrap(),
3073 capabilities: 0x0300,
3074 pio_cycle_times: 0x0200, dma_cycle_times: 0x0200, new_words_valid_flags: 0x0003, multi_sector_capabilities: 0x0100_u16 | protocol::MAX_SECTORS_MULT_TRANSFER_DEFAULT,
3078 single_word_dma_mode: 0x0007, multi_word_dma_mode: 0x0407, enhanced_pio_mode: 0x0003, min_multi_dma_time: 0x0078,
3082 recommended_multi_dma_time: 0x0078,
3083 min_pio_cycle_time_no_flow: 0x01FC, min_pio_cycle_time_flow: 0x00B4, ..FromZeros::new_zeroed()
3086 };
3087 assert_eq!(features.as_bytes(), ex_features.as_bytes());
3088 }
3089
3090 #[async_test]
3091 async fn identify_test_hdd() {
3092 let dev_path = IdePath::default();
3093 let (mut ide_device, _disk, _file_contents, geometry) =
3094 ide_test_setup(None, DriveType::Hard);
3095 device_select(&mut ide_device, &dev_path).await;
3097 prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
3098 execute_command(&mut ide_device, &dev_path, IdeCommand::IDENTIFY_DEVICE.0);
3100
3101 let status = check_command_ready(&mut ide_device, &dev_path).await;
3102 assert!(status.drq());
3103 assert!(!status.err());
3104
3105 let data = &mut [0_u8; protocol::IDENTIFY_DEVICE_BYTES];
3107 ide_device.io_read(IdeIoPort::PRI_DATA.0, data).unwrap();
3108 let features = protocol::IdeFeatures::read_from_prefix(&data[..])
3109 .unwrap()
3110 .0; let total_chs_sectors: u32 =
3113 geometry.sectors_per_track * geometry.cylinder_count * geometry.head_count;
3114 let (cylinders, heads, sectors_per_track) = if total_chs_sectors < protocol::MAX_CHS_SECTORS
3115 {
3116 (
3117 geometry.cylinder_count as u16,
3118 geometry.head_count as u16,
3119 geometry.sectors_per_track as u16,
3120 )
3121 } else {
3122 (0x3FFF, 16, 63)
3123 };
3124
3125 let firmware_revision = if dev_path.channel == 0 {
3126 ".1.1 0 "
3127 } else {
3128 ".1.1 1 "
3129 }
3130 .as_bytes()[..]
3131 .try_into()
3132 .unwrap();
3133
3134 let user_addressable_sectors =
3135 if geometry.total_sectors > (protocol::LBA_28BIT_MAX_SECTORS as u64) {
3136 protocol::LBA_28BIT_MAX_SECTORS
3137 } else {
3138 geometry.total_sectors as u32
3139 };
3140
3141 let ex_features = protocol::IdeFeatures {
3142 config_bits: 0x045A,
3143 cylinders,
3144 heads,
3145 unformatted_sectors_per_track: (protocol::HARD_DRIVE_SECTOR_BYTES
3146 * geometry.sectors_per_track) as u16,
3147 unformatted_bytes_per_sector: protocol::HARD_DRIVE_SECTOR_BYTES as u16,
3148 sectors_per_track,
3149 compact_flash: [0xABCD, 0xDCBA],
3150 vendor0: 0x0123,
3151 serial_no: *b" ",
3152 buffer_type: 3,
3153 buffer_size: 0x0080,
3154 firmware_revision,
3155 model_number: "iVtrau lDH ".as_bytes()[..]
3156 .try_into()
3157 .unwrap(),
3158 max_sectors_mult_transfer: (0x8000 | protocol::MAX_SECTORS_MULT_TRANSFER_DEFAULT),
3159 capabilities: 0x0F00, pio_cycle_times: 0x0200, dma_cycle_times: 0x0200, new_words_valid_flags: 0x0003, log_cylinders: geometry.cylinder_count as u16,
3164 log_heads: geometry.head_count as u16,
3165 log_sectors_per_track: geometry.sectors_per_track as u16,
3166 log_total_sectors: total_chs_sectors.into(),
3167 multi_sector_capabilities: 0x0100_u16 | protocol::MAX_SECTORS_MULT_TRANSFER_DEFAULT,
3168 user_addressable_sectors: user_addressable_sectors.into(),
3169 single_word_dma_mode: 0x0007, multi_word_dma_mode: 0x0407, enhanced_pio_mode: 0x0003, min_multi_dma_time: 0x0078,
3173 recommended_multi_dma_time: 0x0078,
3174 min_pio_cycle_time_no_flow: 0x014D,
3175 min_pio_cycle_time_flow: 0x0078,
3176 major_version_number: 0x01F0, minor_version_number: 0,
3178 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(),
3185 default_sector_size_config: 0x4000, logical_block_alignment: 0x4000, ..FromZeros::new_zeroed()
3188 };
3189 assert_eq!(features.as_bytes(), ex_features.as_bytes());
3190 }
3191
3192 #[async_test]
3197 async fn enlightened_hdd_non_dma_cmd_completes() {
3198 let test_guest_mem = GuestMemory::allocate(16384);
3199
3200 let table_gpa: u64 = 0x1000;
3203 let data_gpa: u32 = 0x2000;
3204 test_guest_mem
3205 .write_plain(
3206 table_gpa,
3207 &BusMasterDmaDesc {
3208 mem_physical_base: data_gpa,
3209 byte_count: 512,
3210 unused: 0,
3211 end_of_table: 0x80,
3212 },
3213 )
3214 .unwrap();
3215
3216 let eint13_command = protocol::EnlightenedInt13Command {
3221 command: IdeCommand::READ_SECTORS,
3222 device_head: DeviceHeadReg::new().with_lba(true),
3223 flags: 0,
3224 result_status: 0,
3225 lba_low: 0,
3226 lba_high: 0,
3227 block_count: 1,
3228 byte_count: 0,
3229 data_buffer: table_gpa as u32,
3230 skip_bytes_head: 0,
3231 skip_bytes_tail: 0,
3232 };
3233 test_guest_mem.write_plain(0, &eint13_command).unwrap();
3234
3235 let dev_path = IdePath::default();
3236 let (mut ide_device, _disk, _, _) =
3237 ide_test_setup(Some(test_guest_mem.clone()), DriveType::Hard);
3238
3239 device_select(&mut ide_device, &dev_path).await;
3240 prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path);
3241
3242 assert!(
3246 matches!(
3247 ide_device.io_write(IdeIoPort::PRI_ENLIGHTENED.0, 0_u32.as_bytes()),
3248 IoResult::Ok
3249 ),
3250 "non-DMA command (READ_SECTORS) via enlightened path should return Ok, not Defer"
3251 );
3252 }
3253
3254 #[async_test]
3272 async fn enlightened_wrong_drive_head_toctou() {
3273 const SECTOR_COUNT: u16 = 4;
3274 const BYTE_COUNT: u16 = SECTOR_COUNT * protocol::HARD_DRIVE_SECTOR_BYTES as u16;
3275
3276 let test_guest_mem = GuestMemory::allocate(16384);
3277
3278 let table_gpa = 0x1000;
3280 let data_gpa = 0x2000;
3281 test_guest_mem
3282 .write_plain(
3283 table_gpa,
3284 &BusMasterDmaDesc {
3285 mem_physical_base: data_gpa,
3286 byte_count: BYTE_COUNT,
3287 unused: 0,
3288 end_of_table: 0x80,
3289 },
3290 )
3291 .unwrap();
3292
3293 let eint13_command = protocol::EnlightenedInt13Command {
3294 command: IdeCommand::READ_DMA_EXT,
3295 device_head: DeviceHeadReg::new().with_lba(true).with_dev(true),
3297 flags: 0,
3298 result_status: 0,
3299 lba_low: 0,
3300 lba_high: 0,
3301 block_count: SECTOR_COUNT,
3302 byte_count: 0,
3303 data_buffer: table_gpa as u32,
3304 skip_bytes_head: 0,
3305 skip_bytes_tail: 0,
3306 };
3307 test_guest_mem.write_plain(0, &eint13_command).unwrap();
3308
3309 let temp_file = NamedTempFile::new().unwrap();
3311 let mut handle = temp_file.reopen().unwrap();
3312 let file_contents = (0..0x100000_u32).collect::<Vec<_>>();
3313 handle.write_all(file_contents.as_bytes()).unwrap();
3314 let hard_disk = Disk::new(FileDisk::open(handle, false).unwrap()).unwrap();
3315
3316 let optical = DriveMedia::optical_disk(Arc::new(AtapiScsiDisk::new(Arc::new(
3318 SimpleScsiDvd::new(None),
3319 ))));
3320 let hard = DriveMedia::hard_disk(hard_disk);
3321
3322 let mut ide_device = IdeDevice::new(
3323 test_guest_mem.clone(),
3324 &mut ExternallyManagedPortIoIntercepts,
3325 [Some(optical), Some(hard)],
3326 [None, None],
3327 LineInterrupt::detached(),
3328 LineInterrupt::detached(),
3329 )
3330 .unwrap();
3331
3332 let dev_path = IdePath::default();
3334
3335 device_select(&mut ide_device, &dev_path).await;
3339 execute_command(&mut ide_device, &dev_path, IdeCommand::PACKET_COMMAND.0);
3340
3341 let status = get_status(&mut ide_device, &dev_path);
3342 assert!(
3343 status.drq(),
3344 "expected optical master to be pending (DRQ) before the enlightened command"
3345 );
3346
3347 let r = ide_device.io_write(IdeIoPort::PRI_ENLIGHTENED.0, 0_u32.as_bytes());
3353
3354 let mut deferred = match r {
3355 IoResult::Defer(deferred) => deferred,
3356 other => panic!(
3357 "enlightened command targeting the slave was dropped ({other:?}); \
3358 the drive-head TOCTOU guard checked the previously-selected master"
3359 ),
3360 };
3361
3362 poll_fn(|cx| {
3363 ide_device.poll_device(cx);
3364 deferred.poll_write(cx)
3365 })
3366 .await
3367 .unwrap();
3368
3369 let mut buffer = vec![0u8; BYTE_COUNT as usize];
3371 test_guest_mem
3372 .read_at(data_gpa.into(), &mut buffer)
3373 .unwrap();
3374 assert_eq!(buffer, file_contents.as_bytes()[..buffer.len()]);
3375 }
3376}