Skip to content

Commit

Permalink
rough pass at an IDE DMA implementation
Browse files Browse the repository at this point in the history
oh lord that's some _stinky code_. It's _really_ ugly, and barely works.
Hopefully, it'll blow-up loudly if things stop working at some point.
Seems to work okay for now though...

Why the fuck does the PP5020's EIDE controller have it's _own_ DMA
engine??? Like, what the hecc?? I was so confused why none of the code
seemed to poke the main DMA controller until I happened to stumble
across a tiny blurb in the product briefing that mentioned this fun
little factoid. ugh.

I have a sneaking suspicion that the project is gonna get a bit more
grind-y moving forward, since I think I'm over the hump of "implementing
devices" for the most part. I mean, there's plenty of stubbed code I
could look into cleaning up (e.g: getting the RTC working, battery
working, etc..), but that's _boooooring_.

Maybe I'll take a detour and try and port clicky to wasm, and get some
sort of web-ui for it :P. I mean, I'm finally at a point in the project
where there's something graphical to play/interact with, so why not
bring it to a broad audience?
  • Loading branch information
daniel5151 committed Aug 2, 2020
1 parent 8b85662 commit 92b6b8e
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 35 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Expand Up @@ -3,7 +3,7 @@

.gdb_history
sysdump.log
ipodhd.img
*.img*

# I use a custom config to speed up link times
.cargo/
94 changes: 82 additions & 12 deletions src/devices/generic/ide/mod.rs
Expand Up @@ -7,7 +7,6 @@ use futures::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
use num_enum::TryFromPrimitive;

use crate::block::BlockDev;
use crate::signal::irq;

// TODO?: make num heads / num sectors configurable?
const NUM_HEADS: usize = 16;
Expand Down Expand Up @@ -83,6 +82,13 @@ enum IdeCmd {
WriteSectorsNoRetry = 0x31,
SetMultipleMode = 0xc6,
SetFeatures = 0xef,
ReadDMA = 0xc8,
ReadDMANoRetry = 0xc9,
WriteDMA = 0xca,
WriteDMANoRetry = 0xcb,

Sleep = 0x99,
SleepAlt = 0xe6,

// not strictly ATA-2, but the iPod flash ROM seems to use this cmd...
FlushCache = 0xe7,
Expand Down Expand Up @@ -186,6 +192,11 @@ impl IdeTransferMode {
_ => unreachable!(),
}
}

fn is_dma(&self) -> bool {
use self::IdeTransferMode::*;
matches!(self, DMASingleWord(..) | DMAMultiWord(..))
}
}

#[derive(Debug, Default)]
Expand Down Expand Up @@ -219,7 +230,8 @@ struct IdeDriveConfig {
#[derive(Debug)]
struct IdeDrive {
blockdev: Box<dyn BlockDev>,
irq: irq::Sender, // shared between both drives
irq: irq::Sender, // shared between both drives
dmarq: irq::Sender, // shared between both drives

state: IdeDriveState,
remaining_sectors: usize,
Expand All @@ -230,8 +242,12 @@ struct IdeDrive {
}

impl IdeDrive {
fn new(irq: irq::Sender, blockdev: Box<dyn BlockDev>) -> IdeDrive {
fn new(irq: irq::Sender, dmarq: irq::Sender, blockdev: Box<dyn BlockDev>) -> IdeDrive {
IdeDrive {
blockdev,
irq,
dmarq,

state: IdeDriveState::Idle,
remaining_sectors: 0,

Expand All @@ -240,10 +256,6 @@ impl IdeDrive {
status: *0u8.set_bit(reg::STATUS::DRDY, true),
..IdeRegs::default()
},

blockdev,
irq,

cfg: IdeDriveConfig {
eightbit: false,
multi_sect: 0,
Expand Down Expand Up @@ -307,7 +319,8 @@ impl IdeDrive {
.set_bit(reg::STATUS::DRQ, false)
.set_bit(reg::STATUS::BSY, false);

self.irq.assert()
self.irq.assert();
self.dmarq.clear();
} else {
// the next sector needs to be loaded
(self.reg.status)
Expand All @@ -329,7 +342,10 @@ impl IdeDrive {
.set_bit(reg::STATUS::DRQ, true)
.set_bit(reg::STATUS::BSY, false);

self.irq.assert();
// DMA only fires a single IRQ at the end of the transfer
if !self.cfg.transfer_mode.is_dma() {
self.irq.assert();
}

Ok(())
})?;
Expand Down Expand Up @@ -378,7 +394,10 @@ impl IdeDrive {
.set_bit(reg::STATUS::DRQ, true)
.set_bit(reg::STATUS::BSY, false);

self.irq.assert();
// DMA only fires a single IRQ at the end of the transfer
if !self.cfg.transfer_mode.is_dma() {
self.irq.assert();
}

// check if there are no more sectors remaining
self.remaining_sectors -= 1; // FIXME: this varies under `WriteMultiple`
Expand All @@ -387,6 +406,9 @@ impl IdeDrive {
(self.reg.status)
.set_bit(reg::STATUS::DRDY, true)
.set_bit(reg::STATUS::DRQ, false);

self.irq.assert();
self.dmarq.clear();
}

Ok(())
Expand Down Expand Up @@ -468,6 +490,22 @@ impl IdeDrive {
self.exec_cmd(ReadSectors as u8)
}
}
ReadDMA | ReadDMANoRetry => {
if !self.cfg.transfer_mode.is_dma() {
// TODO?: use the ATA abort mechanism instead of loudly failing
return Err(ContractViolation {
msg: "Called ReadDMA without setting DMA transfer mode".into(),
severity: Error,
stub_val: None,
});
}

// basically just ReadSectors, except it only fires a _single_
// IRQ at the end of the transfer, and asserts dmarq
self.dmarq.assert();
(self.reg.status).set_bit(reg::STATUS::BSY, false);
self.exec_cmd(ReadSectors as u8)
}
ReadSectors | ReadSectorsNoRetry => {
let offset = match self.get_sector_offset() {
Some(offset) => offset,
Expand Down Expand Up @@ -539,6 +577,22 @@ impl IdeDrive {
self.exec_cmd(WriteSectors as u8)
}
}
WriteDMA | WriteDMANoRetry => {
if !self.cfg.transfer_mode.is_dma() {
// TODO?: use the ATA abort mechanism instead of loudly failing
return Err(ContractViolation {
msg: "Called WriteDMA without setting DMA transfer mode".into(),
severity: Error,
stub_val: None,
});
}

// basically just WriteSectors, except it only fires a _single_
// IRQ at the end of the transfer, and asserts dmarq
self.dmarq.assert();
(self.reg.status).set_bit(reg::STATUS::BSY, false);
self.exec_cmd(WriteSectors as u8)
}
WriteSectors | WriteSectorsNoRetry => {
// NOTE: this code is somewhat UNTESTED

Expand Down Expand Up @@ -624,6 +678,15 @@ impl IdeDrive {

Ok(())
}

Sleep | SleepAlt => {
// uhh, it's an emulated drive.
// just assert the irq and go on our merry way
(self.reg.status).set_bit(reg::STATUS::BSY, false);

self.irq.assert();
Ok(())
}
}
}
}
Expand All @@ -633,6 +696,8 @@ impl IdeDrive {
#[derive(Debug)]
pub struct IdeController {
common_irq_line: irq::Sender,
dmarq: irq::Sender,

selected_device: IdeIdx,
ide0: Option<IdeDrive>,
ide1: Option<IdeDrive>,
Expand Down Expand Up @@ -662,9 +727,10 @@ macro_rules! selected_ide {
}

impl IdeController {
pub fn new(irq: irq::Sender) -> IdeController {
pub fn new(irq: irq::Sender, dmarq: irq::Sender) -> IdeController {
IdeController {
common_irq_line: irq,
dmarq,
selected_device: IdeIdx::IDE0,
ide0: None,
ide1: None,
Expand All @@ -685,7 +751,11 @@ impl IdeController {
IdeIdx::IDE1 => &mut self.ide1,
};

*ide = Some(IdeDrive::new(self.common_irq_line.clone(), blockdev));
*ide = Some(IdeDrive::new(
self.common_irq_line.clone(),
self.dmarq.clone(),
blockdev,
));
old_drive
}

Expand Down
17 changes: 16 additions & 1 deletion src/devices/platform/pp/dma.rs
Expand Up @@ -43,15 +43,25 @@ pub struct DmaCon {
master_control: u32,
master_status: u32,
req_status: u32,

// HACK: IDE DMA doesn't actually go through the DMA controller
// that said, to keep things simple in the emulator, we route IDE DMA through the main DMA
// engine...
//
// As per the pp5020 spec sheet: "A dedicated, high-performance ATA-66IDE controller with its
// own DMA engine frees the processors from mundane management tasks."
ide_dmarq: irq::Reciever,
}

impl DmaCon {
pub fn new() -> DmaCon {
pub fn new(ide_dmarq: irq::Reciever) -> DmaCon {
let mut dma = DmaCon {
dma: Default::default(),
master_control: 0,
master_status: 0,
req_status: 0,

ide_dmarq,
};

dma.dma[0].label = Some("0");
Expand All @@ -65,6 +75,11 @@ impl DmaCon {

dma
}

/// XXX: remove this once DMA is properly sorted out
pub fn do_ide_dma(&self) -> bool {
self.ide_dmarq.asserted()
}
}

impl Device for DmaCon {
Expand Down
58 changes: 44 additions & 14 deletions src/devices/platform/pp/eide.rs
@@ -1,14 +1,18 @@
use crate::devices::prelude::*;

use crate::devices::generic::ide::{IdeController, IdeIdx, IdeReg};
use crate::signal::irq;

#[derive(Debug, Default)]
struct IdeDriveCfg {
primary_timing: [u32; 2],
secondary_timing: [u32; 2],
// bit 4: ide0 interrupt status (write 1 to clear)
// bit 5: ide1 interrupt status (write 1 to clear)
// bit 15: start DMA? (1 = active, 0 = stop)
// bit 28: cpu > 65MHz
// bit 29: cpu > 50MHz
// bit 31: reset device
config: u32,
controller_status: u32,
}

/// PP5020 EIDE Controller
Expand All @@ -18,20 +22,25 @@ pub struct EIDECon {
ide1_cfg: IdeDriveCfg,
ide: IdeController,

// not sure if these are here, or under the generic IDE interface. we'll find out when I get
// around to implementing DMA I guess ¯\_(ツ)_/¯
// NOTE: since no pp devices ever used two IDE devices at once, I have no idea if the DMA is
// per disk or per controller...

// bit 0: active (1 = disabled)
// bit 1: ?? (specify which IDE drive maybe? total wild guess tho)
// bit 3: read/write (1 = read)
// bit 31: unset at the end of the transfer
dma_control: u32,
dma_length: u32,
dma_addr: u32,
unknown: u32,
}

impl EIDECon {
pub fn new(irq: irq::Sender) -> EIDECon {
pub fn new(irq: irq::Sender, dmarq: irq::Sender) -> EIDECon {
EIDECon {
ide0_cfg: Default::default(),
ide1_cfg: Default::default(),
ide: IdeController::new(irq),
ide: IdeController::new(irq, dmarq),

dma_control: 0,
dma_length: 0,
Expand All @@ -43,6 +52,23 @@ impl EIDECon {
pub fn as_ide(&mut self) -> &mut IdeController {
&mut self.ide
}

pub fn do_dma(&mut self) -> Result<(crate::memory::MemAccessKind, u32), ()> {
if self.dma_length == 0 {
return Err(());
}

let op = match self.dma_control.get_bit(3) {
true => (crate::memory::MemAccessKind::Read, self.dma_addr),
false => (crate::memory::MemAccessKind::Write, self.dma_addr),
};

// 16 bit transfers
self.dma_addr += 2;
self.dma_length -= 2;

Ok(op)
}
}

impl Device for EIDECon {
Expand Down Expand Up @@ -100,9 +126,12 @@ impl Memory for EIDECon {
0x01c => Ok(self.ide1_cfg.secondary_timing[1]),
0x028 => {
let val = *0u32
// rockbox seems to use bit 3 to check for IDE0 irq when
// waiting for a DMA transfer to finish
.set_bit(3, self.ide.irq_state(IdeIdx::IDE0))
.set_bit(4, self.ide.irq_state(IdeIdx::IDE0))
.set_bit(5, self.ide.irq_state(IdeIdx::IDE1));
Err(StubRead(Info, val))
Err(StubRead(Debug, val))
}
0x02c => Err(Unimplemented),

Expand All @@ -118,9 +147,9 @@ impl Memory for EIDECon {
0x3f8 => self.ide.read8(IdeReg::AltStatus).map(|v| v as u32),
0x3fc => self.ide.read8(IdeReg::DataLatch).map(|v| v as u32),

0x400 => Err(StubRead(Error, self.dma_control)),
0x408 => Err(StubRead(Error, self.dma_length)),
0x40c => Err(StubRead(Error, self.dma_addr)),
0x400 => Err(StubRead(Debug, self.dma_control)),
0x408 => Err(StubRead(Info, self.dma_length)),
0x40c => Err(StubRead(Info, self.dma_addr)),
0x410 => Err(StubRead(Error, self.unknown)),
_ => Err(Unexpected),
}
Expand All @@ -143,7 +172,7 @@ impl Memory for EIDECon {
if val.get_bit(5) {
self.ide.clear_irq(IdeIdx::IDE1)
}
Err(StubWrite(Info, ()))
Err(StubWrite(Debug, ()))
}
0x02c => Err(Unimplemented),

Expand All @@ -159,9 +188,10 @@ impl Memory for EIDECon {
0x3f8 => self.ide.write8(IdeReg::DevControl, val as u8),
0x3fc => self.ide.write8(IdeReg::DataLatch, val as u8),

0x400 => Err(StubWrite(Error, self.dma_control = val)),
0x408 => Err(StubWrite(Error, self.dma_length = val)),
0x40c => Err(StubWrite(Error, self.dma_addr = val)),
0x400 => Err(StubWrite(Debug, self.dma_control = val)),
// HACK: why the hecc does Rockbox's pp5020 driver write `len - 4`??
0x408 => Ok(self.dma_length = val + 4),
0x40c => Ok(self.dma_addr = val),
0x410 => Err(StubWrite(Error, self.unknown = val)),

_ => Err(Unexpected),
Expand Down
2 changes: 1 addition & 1 deletion src/devices/prelude.rs
Expand Up @@ -11,4 +11,4 @@ pub use crate::memory::{
MemException::{self, *},
MemResult, Memory,
};
pub use crate::signal::irq;
pub use crate::signal::{self, irq};

0 comments on commit 92b6b8e

Please sign in to comment.