Implement ided using PIO and code from ahci
This commit is contained in:
Generated
+2
@@ -445,7 +445,9 @@ dependencies = [
|
||||
name = "ided"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"block-io-wrapper",
|
||||
"log",
|
||||
"partitionlib",
|
||||
"pcid",
|
||||
"redox-daemon",
|
||||
"redox-log",
|
||||
|
||||
@@ -4,7 +4,9 @@ version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
block-io-wrapper = { path = "../block-io-wrapper" }
|
||||
log = "0.4"
|
||||
partitionlib = { git = "https://gitlab.redox-os.org/redox-os/partitionlib.git" }
|
||||
pcid = { path = "../pcid" }
|
||||
redox-daemon = "0.1"
|
||||
redox-log = "0.1"
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#[repr(u8)]
|
||||
pub enum AtaCommand {
|
||||
ReadPio = 0x20,
|
||||
ReadPioExt = 0x24,
|
||||
ReadDma = 0xC8,
|
||||
ReadDmaExt = 0x25,
|
||||
WritePio = 0x30,
|
||||
WritePioExt = 0x34,
|
||||
WriteDma = 0xCA,
|
||||
WriteDmaExt = 0x35,
|
||||
CacheFlush = 0xE7,
|
||||
CacheFlushExt = 0xEA,
|
||||
Packet = 0xA0,
|
||||
IdentifyPacket = 0xA1,
|
||||
Identify = 0xEC,
|
||||
}
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
};
|
||||
use syscall::{
|
||||
error::{Error, Result, EIO},
|
||||
io::{Io, Pio, ReadOnly, WriteOnly},
|
||||
};
|
||||
|
||||
use crate::ata::AtaCommand;
|
||||
|
||||
pub struct Channel {
|
||||
pub data8: Pio<u8>,
|
||||
pub data32: Pio<u32>,
|
||||
pub error: ReadOnly<Pio<u8>>,
|
||||
pub features: WriteOnly<Pio<u8>>,
|
||||
pub sector_count: Pio<u8>,
|
||||
pub lba_0: Pio<u8>,
|
||||
pub lba_1: Pio<u8>,
|
||||
pub lba_2: Pio<u8>,
|
||||
pub device_select: Pio<u8>,
|
||||
pub status: ReadOnly<Pio<u8>>,
|
||||
pub command: WriteOnly<Pio<u8>>,
|
||||
pub alt_status: ReadOnly<Pio<u8>>,
|
||||
pub control: WriteOnly<Pio<u8>>,
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
pub fn new(base: u16, control_base: u16) -> Self {
|
||||
Self {
|
||||
data8: Pio::new(base + 0),
|
||||
data32: Pio::new(base + 0),
|
||||
error: ReadOnly::new(Pio::new(base + 1)),
|
||||
features: WriteOnly::new(Pio::new(base + 1)),
|
||||
sector_count: Pio::new(base + 2),
|
||||
lba_0: Pio::new(base + 3),
|
||||
lba_1: Pio::new(base + 4),
|
||||
lba_2: Pio::new(base + 5),
|
||||
device_select: Pio::new(base + 6),
|
||||
status: ReadOnly::new(Pio::new(base + 7)),
|
||||
command: WriteOnly::new(Pio::new(base + 7)),
|
||||
alt_status: ReadOnly::new(Pio::new(control_base + 2)),
|
||||
control: WriteOnly::new(Pio::new(control_base + 2)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn primary_compat() -> Self {
|
||||
Self::new(0x1F0, 0x3F6)
|
||||
}
|
||||
|
||||
pub fn secondary_compat() -> Self {
|
||||
Self::new(0x170, 0x376)
|
||||
}
|
||||
|
||||
fn polling(&mut self, check: bool) -> Result<()> {
|
||||
/*
|
||||
#define ATA_SR_BSY 0x80 // Busy
|
||||
#define ATA_SR_DRDY 0x40 // Drive ready
|
||||
#define ATA_SR_DF 0x20 // Drive write fault
|
||||
#define ATA_SR_DSC 0x10 // Drive seek complete
|
||||
#define ATA_SR_DRQ 0x08 // Data request ready
|
||||
#define ATA_SR_CORR 0x04 // Corrected data
|
||||
#define ATA_SR_IDX 0x02 // Index
|
||||
#define ATA_SR_ERR 0x01 // Error
|
||||
*/
|
||||
|
||||
for _ in 0..4 {
|
||||
// Doing this 4 times creates a 400ns delay
|
||||
self.alt_status.read();
|
||||
}
|
||||
|
||||
while self.status.readf(0x80) {
|
||||
thread::yield_now();
|
||||
}
|
||||
|
||||
if check {
|
||||
let status = self.status.read();
|
||||
|
||||
if status & 0x01 != 0 {
|
||||
log::error!("IDE error");
|
||||
return Err(Error::new(EIO));
|
||||
}
|
||||
|
||||
if status & 0x20 != 0 {
|
||||
log::error!("IDE device write fault");
|
||||
return Err(Error::new(EIO));
|
||||
}
|
||||
|
||||
if status & 0x08 == 0 {
|
||||
log::error!("IDE data not ready");
|
||||
return Err(Error::new(EIO));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Disk {
|
||||
fn id(&self) -> usize;
|
||||
fn size(&mut self) -> u64;
|
||||
fn read(&mut self, block: u64, buffer: &mut [u8]) -> Result<Option<usize>>;
|
||||
fn write(&mut self, block: u64, buffer: &[u8]) -> Result<Option<usize>>;
|
||||
fn block_length(&mut self) -> Result<u32>;
|
||||
}
|
||||
|
||||
pub struct AtaDisk {
|
||||
pub chan: Arc<Mutex<Channel>>,
|
||||
pub chan_i: usize,
|
||||
pub dev: u8,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
impl Disk for AtaDisk {
|
||||
fn id(&self) -> usize {
|
||||
self.chan_i << 1 | self.dev as usize
|
||||
}
|
||||
|
||||
fn size(&mut self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
|
||||
fn read(&mut self, block: u64, buffer: &mut [u8]) -> Result<Option<usize>> {
|
||||
//TODO: support other LBA modes
|
||||
assert!(block < 0x1_0000_0000_0000);
|
||||
|
||||
let sectors = buffer.len() / 512;
|
||||
assert!(sectors < 0x1_0000);
|
||||
|
||||
let mut chan = self.chan.lock().unwrap();
|
||||
|
||||
// Select drive
|
||||
chan.device_select.write(0xE0 | (self.dev << 4));
|
||||
|
||||
// Set high sector count and LBA
|
||||
//TODO: only if LBA mode is 48-bit
|
||||
chan.control.writef(0x80, true);
|
||||
chan.sector_count.write((sectors >> 8) as u8);
|
||||
chan.lba_0.write((block >> 24) as u8);
|
||||
chan.lba_1.write((block >> 32) as u8);
|
||||
chan.lba_2.write((block >> 40) as u8);
|
||||
chan.control.writef(0x80, false);
|
||||
|
||||
// Set low sector count and LBA
|
||||
chan.sector_count.write(sectors as u8);
|
||||
chan.lba_0.write(block as u8);
|
||||
chan.lba_1.write((block >> 8) as u8);
|
||||
chan.lba_2.write((block >> 16) as u8);
|
||||
|
||||
// Send command
|
||||
//TODO: use DMA
|
||||
chan.command.write(AtaCommand::ReadPioExt as u8);
|
||||
|
||||
// Read data
|
||||
for sector in 0..sectors {
|
||||
chan.polling(true)?;
|
||||
|
||||
for i in 0..128 {
|
||||
let data = chan.data32.read();
|
||||
buffer[sector * 512 + i * 4 + 0] = (data >> 0) as u8;
|
||||
buffer[sector * 512 + i * 4 + 1] = (data >> 8) as u8;
|
||||
buffer[sector * 512 + i * 4 + 2] = (data >> 16) as u8;
|
||||
buffer[sector * 512 + i * 4 + 3] = (data >> 24) as u8;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(sectors * 512))
|
||||
}
|
||||
|
||||
fn write(&mut self, block: u64, buffer: &[u8]) -> Result<Option<usize>> {
|
||||
//TODO: support other LBA modes
|
||||
assert!(block < 0x1_0000_0000_0000);
|
||||
|
||||
let sectors = buffer.len() / 512;
|
||||
assert!(sectors < 0x1_0000);
|
||||
|
||||
let mut chan = self.chan.lock().unwrap();
|
||||
|
||||
// Select drive
|
||||
chan.device_select.write(0xE0 | (self.dev << 4));
|
||||
|
||||
// Set high sector count and LBA
|
||||
//TODO: only if LBA mode is 48-bit
|
||||
chan.control.writef(0x80, true);
|
||||
chan.sector_count.write((sectors >> 8) as u8);
|
||||
chan.lba_0.write((block >> 24) as u8);
|
||||
chan.lba_1.write((block >> 32) as u8);
|
||||
chan.lba_2.write((block >> 40) as u8);
|
||||
chan.control.writef(0x80, false);
|
||||
|
||||
// Set low sector count and LBA
|
||||
chan.sector_count.write(sectors as u8);
|
||||
chan.lba_0.write(block as u8);
|
||||
chan.lba_1.write((block >> 8) as u8);
|
||||
chan.lba_2.write((block >> 16) as u8);
|
||||
|
||||
// Send command
|
||||
//TODO: use DMA
|
||||
chan.command.write(AtaCommand::WritePioExt as u8);
|
||||
|
||||
// Write data
|
||||
for sector in 0..sectors {
|
||||
chan.polling(false)?;
|
||||
|
||||
for i in 0..128 {
|
||||
chan.data32.write(
|
||||
((buffer[sector * 512 + i * 4 + 0] as u32) << 0) |
|
||||
((buffer[sector * 512 + i * 4 + 1] as u32) << 8) |
|
||||
((buffer[sector * 512 + i * 4 + 2] as u32) << 16) |
|
||||
((buffer[sector * 512 + i * 4 + 3] as u32) << 24)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
chan.command.write(AtaCommand::CacheFlushExt as u8);
|
||||
chan.polling(false)?;
|
||||
|
||||
Ok(Some(sectors * 512))
|
||||
}
|
||||
|
||||
fn block_length(&mut self) -> Result<u32> {
|
||||
Ok(512)
|
||||
}
|
||||
}
|
||||
+315
-10
@@ -1,9 +1,33 @@
|
||||
use pcid_interface::PcidServerHandle;
|
||||
|
||||
use log::{error, info};
|
||||
use pcid_interface::PcidServerHandle;
|
||||
use redox_log::{OutputBuilder, RedoxLogger};
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{ErrorKind, Read, Write},
|
||||
os::unix::io::{FromRawFd, RawFd},
|
||||
sync::{Arc, Mutex},
|
||||
thread::sleep,
|
||||
time::Duration,
|
||||
};
|
||||
use syscall::{
|
||||
data::{Event, Packet},
|
||||
error::{Error, ENODEV},
|
||||
flag::{EVENT_READ},
|
||||
io::Io,
|
||||
scheme::SchemeBlockMut,
|
||||
};
|
||||
|
||||
fn setup_logging() -> Option<&'static RedoxLogger> {
|
||||
use crate::{
|
||||
ata::AtaCommand,
|
||||
ide::{AtaDisk, Channel, Disk},
|
||||
scheme::DiskScheme,
|
||||
};
|
||||
|
||||
pub mod ata;
|
||||
pub mod ide;
|
||||
pub mod scheme;
|
||||
|
||||
fn setup_logging(name: &str) -> Option<&'static RedoxLogger> {
|
||||
let mut logger = RedoxLogger::new()
|
||||
.with_output(
|
||||
OutputBuilder::stderr()
|
||||
@@ -14,25 +38,25 @@ fn setup_logging() -> Option<&'static RedoxLogger> {
|
||||
);
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
match OutputBuilder::in_redox_logging_scheme("disk", "pcie", "ide.log") {
|
||||
match OutputBuilder::in_redox_logging_scheme("disk", "pcie", &format!("{}.log", name)) {
|
||||
Ok(b) => logger = logger.with_output(
|
||||
// TODO: Add a configuration file for this
|
||||
b.with_filter(log::LevelFilter::Info)
|
||||
.flush_on_newline(true)
|
||||
.build()
|
||||
),
|
||||
Err(error) => eprintln!("ided: failed to create ide.log: {}", error),
|
||||
Err(error) => eprintln!("ided: failed to create log: {}", error),
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
match OutputBuilder::in_redox_logging_scheme("disk", "pcie", "ide.ansi.log") {
|
||||
match OutputBuilder::in_redox_logging_scheme("disk", "pcie", &format!("{}.ansi.log", name)) {
|
||||
Ok(b) => logger = logger.with_output(
|
||||
b.with_filter(log::LevelFilter::Info)
|
||||
.with_ansi_escape_codes()
|
||||
.flush_on_newline(true)
|
||||
.build()
|
||||
),
|
||||
Err(error) => eprintln!("ided: failed to create ide.ansi.log: {}", error),
|
||||
Err(error) => eprintln!("ided: failed to create ansi log: {}", error),
|
||||
}
|
||||
|
||||
match logger.enable() {
|
||||
@@ -52,12 +76,293 @@ fn main() {
|
||||
}
|
||||
|
||||
fn daemon(daemon: redox_daemon::Daemon) -> ! {
|
||||
let _logger_ref = setup_logging();
|
||||
|
||||
let mut pcid_handle =
|
||||
PcidServerHandle::connect_default().expect("ided: failed to setup channel to pcid");
|
||||
|
||||
println!("IDE {:#x?}", pcid_handle.fetch_header());
|
||||
let pci_config = pcid_handle.fetch_config().expect("ided: failed to fetch config");
|
||||
let mut name = pci_config.func.name();
|
||||
name.push_str("_ide");
|
||||
|
||||
let _logger_ref = setup_logging(&name);
|
||||
|
||||
info!("IDE {:?}", pci_config);
|
||||
|
||||
let pci_header = pcid_handle.fetch_header().expect("ided: failed to fetch PCI header");
|
||||
let (primary, primary_irq) = if pci_header.interface() & 1 != 0 {
|
||||
panic!("TODO: IDE primary channel is PCI native");
|
||||
} else {
|
||||
(Channel::primary_compat(), 14)
|
||||
};
|
||||
let (secondary, secondary_irq) = if pci_header.interface() & 1 != 0 {
|
||||
panic!("TODO: IDE secondary channel is PCI native");
|
||||
} else {
|
||||
(Channel::secondary_compat(), 15)
|
||||
};
|
||||
|
||||
unsafe { syscall::iopl(3).expect("ided: failed to get I/O privilege") };
|
||||
|
||||
//TODO: move this to ide.rs?
|
||||
let chans = vec![
|
||||
Arc::new(Mutex::new(primary)),
|
||||
Arc::new(Mutex::new(secondary)),
|
||||
];
|
||||
let mut disks: Vec<Box<dyn Disk>> = Vec::new();
|
||||
for (chan_i, chan_lock) in chans.iter().enumerate() {
|
||||
let mut chan = chan_lock.lock().unwrap();
|
||||
|
||||
println!(" - channel {}", chan_i);
|
||||
|
||||
// Disable IRQs
|
||||
chan.control.write(2);
|
||||
|
||||
for dev in 0..=1 {
|
||||
println!(" - device {}", dev);
|
||||
|
||||
// Select device
|
||||
chan.device_select.write(0xA0 | (dev << 4));
|
||||
sleep(Duration::from_millis(1));
|
||||
|
||||
// ATA identify command
|
||||
chan.command.write(AtaCommand::Identify as u8);
|
||||
sleep(Duration::from_millis(1));
|
||||
|
||||
// Check if device exists
|
||||
if chan.status.read() == 0 {
|
||||
println!(" not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Poll for status
|
||||
let error = loop {
|
||||
let status = chan.status.read();
|
||||
if status & 1 != 0 {
|
||||
// Error
|
||||
break true;
|
||||
}
|
||||
if status & 0x80 == 0 && status & 0x08 != 0 {
|
||||
// Not busy and data ready
|
||||
break false;
|
||||
}
|
||||
};
|
||||
|
||||
//TODO: probe ATAPI
|
||||
if error {
|
||||
println!(" error");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read and print identity
|
||||
{
|
||||
let mut dest = [0u16; 256];
|
||||
for chunk in dest.chunks_mut(2) {
|
||||
let data = chan.data32.read();
|
||||
chunk[0] = data as u16;
|
||||
chunk[1] = (data >> 16) as u16;
|
||||
}
|
||||
|
||||
let mut serial = String::new();
|
||||
for word in 10..20 {
|
||||
let d = dest[word];
|
||||
let a = ((d >> 8) as u8) as char;
|
||||
if a != '\0' {
|
||||
serial.push(a);
|
||||
}
|
||||
let b = (d as u8) as char;
|
||||
if b != '\0' {
|
||||
serial.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
let mut firmware = String::new();
|
||||
for word in 23..27 {
|
||||
let d = dest[word];
|
||||
let a = ((d >> 8) as u8) as char;
|
||||
if a != '\0' {
|
||||
firmware.push(a);
|
||||
}
|
||||
let b = (d as u8) as char;
|
||||
if b != '\0' {
|
||||
firmware.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
let mut model = String::new();
|
||||
for word in 27..47 {
|
||||
let d = dest[word];
|
||||
let a = ((d >> 8) as u8) as char;
|
||||
if a != '\0' {
|
||||
model.push(a);
|
||||
}
|
||||
let b = (d as u8) as char;
|
||||
if b != '\0' {
|
||||
model.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
let mut sectors = (dest[100] as u64) |
|
||||
((dest[101] as u64) << 16) |
|
||||
((dest[102] as u64) << 32) |
|
||||
((dest[103] as u64) << 48);
|
||||
|
||||
let lba_bits = if sectors == 0 {
|
||||
sectors = (dest[60] as u64) | ((dest[61] as u64) << 16);
|
||||
28
|
||||
} else {
|
||||
48
|
||||
};
|
||||
|
||||
println!(" Serial: {}", serial.trim());
|
||||
println!(" Firmware: {}", firmware.trim());
|
||||
println!(" Model: {}", model.trim());
|
||||
println!(" {}-bit LBA", lba_bits);
|
||||
println!(" Size: {} MB", sectors / 2048);
|
||||
|
||||
disks.push(Box::new(AtaDisk {
|
||||
chan: chan_lock.clone(),
|
||||
chan_i,
|
||||
dev,
|
||||
size: sectors * 512,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let scheme_name = format!("disk/{}", name);
|
||||
let socket_fd = syscall::open(
|
||||
&format!(":{}", scheme_name),
|
||||
syscall::O_RDWR | syscall::O_CREAT | syscall::O_NONBLOCK
|
||||
).expect("ided: failed to create disk scheme");
|
||||
let mut socket = unsafe { File::from_raw_fd(socket_fd as RawFd) };
|
||||
|
||||
let primary_irq_fd = syscall::open(
|
||||
&format!("irq:{}", primary_irq),
|
||||
syscall::O_RDWR | syscall::O_NONBLOCK
|
||||
).expect("ided: failed to open irq file");
|
||||
let mut primary_irq_file = unsafe { File::from_raw_fd(primary_irq_fd as RawFd) };
|
||||
|
||||
let secondary_irq_fd = syscall::open(
|
||||
&format!("irq:{}", secondary_irq),
|
||||
syscall::O_RDWR | syscall::O_NONBLOCK
|
||||
).expect("ided: failed to open irq file");
|
||||
let mut secondary_irq_file = unsafe { File::from_raw_fd(secondary_irq_fd as RawFd) };
|
||||
|
||||
let mut event_file = File::open("event:").expect("ided: failed to open event file");
|
||||
|
||||
syscall::setrens(0, 0).expect("ided: failed to enter null namespace");
|
||||
|
||||
daemon.ready().expect("ided: failed to notify parent");
|
||||
|
||||
event_file.write(&Event {
|
||||
id: socket_fd,
|
||||
flags: EVENT_READ,
|
||||
data: 0
|
||||
}).expect("ided: failed to event disk scheme");
|
||||
|
||||
event_file.write(&Event {
|
||||
id: primary_irq_fd,
|
||||
flags: EVENT_READ,
|
||||
data: 0
|
||||
}).expect("ided: failed to event irq scheme");
|
||||
|
||||
event_file.write(&Event {
|
||||
id: secondary_irq_fd,
|
||||
flags: EVENT_READ,
|
||||
data: 0
|
||||
}).expect("ided: failed to event irq scheme");
|
||||
|
||||
let mut scheme = DiskScheme::new(scheme_name, chans, disks);
|
||||
|
||||
let mut mounted = true;
|
||||
let mut todo = Vec::new();
|
||||
while mounted {
|
||||
let mut event = Event::default();
|
||||
if event_file.read(&mut event).expect("ided: failed to read event file") == 0 {
|
||||
break;
|
||||
}
|
||||
if event.id == socket_fd {
|
||||
loop {
|
||||
let mut packet = Packet::default();
|
||||
match socket.read(&mut packet) {
|
||||
Ok(0) => {
|
||||
mounted = false;
|
||||
break;
|
||||
},
|
||||
Ok(_) => (),
|
||||
Err(err) => if err.kind() == ErrorKind::WouldBlock {
|
||||
break;
|
||||
} else {
|
||||
panic!("ided: failed to read disk scheme: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(a) = scheme.handle(&packet) {
|
||||
packet.a = a;
|
||||
socket.write(&mut packet).expect("ided: failed to write disk scheme");
|
||||
} else {
|
||||
todo.push(packet);
|
||||
}
|
||||
}
|
||||
} else if event.id == primary_irq_fd {
|
||||
let mut irq = [0; 8];
|
||||
if primary_irq_file.read(&mut irq).expect("ided: failed to read irq file") >= irq.len() {
|
||||
if scheme.irq(0) {
|
||||
primary_irq_file.write(&irq).expect("ided: failed to write irq file");
|
||||
|
||||
// Handle todos in order to finish previous packets if possible
|
||||
let mut i = 0;
|
||||
while i < todo.len() {
|
||||
if let Some(a) = scheme.handle(&todo[i]) {
|
||||
let mut packet = todo.remove(i);
|
||||
packet.a = a;
|
||||
socket.write(&mut packet).expect("ided: failed to write disk scheme");
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if event.id == secondary_irq_fd {
|
||||
let mut irq = [0; 8];
|
||||
if secondary_irq_file.read(&mut irq).expect("ided: failed to read irq file") >= irq.len() {
|
||||
if scheme.irq(1) {
|
||||
secondary_irq_file.write(&irq).expect("ided: failed to write irq file");
|
||||
|
||||
// Handle todos in order to finish previous packets if possible
|
||||
let mut i = 0;
|
||||
while i < todo.len() {
|
||||
if let Some(a) = scheme.handle(&todo[i]) {
|
||||
let mut packet = todo.remove(i);
|
||||
packet.a = a;
|
||||
socket.write(&mut packet).expect("ided: failed to write disk scheme");
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!("Unknown event {}", event.id);
|
||||
}
|
||||
|
||||
// Handle todos to start new packets if possible
|
||||
let mut i = 0;
|
||||
while i < todo.len() {
|
||||
if let Some(a) = scheme.handle(&todo[i]) {
|
||||
let mut packet = todo.remove(i);
|
||||
packet.a = a;
|
||||
socket.write(&packet).expect("ided: failed to write disk scheme");
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ! mounted {
|
||||
for mut packet in todo.drain(..) {
|
||||
packet.a = Error::mux(Err(Error::new(ENODEV)));
|
||||
socket.write(&packet).expect("ided: failed to write disk scheme");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::{cmp, str};
|
||||
use std::convert::{TryFrom};
|
||||
use std::fmt::Write;
|
||||
use std::io::prelude::*;
|
||||
use std::io::SeekFrom;
|
||||
use std::io;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use syscall::{
|
||||
Error, EACCES, EBADF, EINVAL, EISDIR, ENOENT, EOVERFLOW, Result,
|
||||
Io, SchemeBlockMut, Stat, MODE_DIR, MODE_FILE, O_DIRECTORY,
|
||||
O_STAT, SEEK_CUR, SEEK_END, SEEK_SET};
|
||||
|
||||
use crate::ide::{Channel, Disk};
|
||||
|
||||
use partitionlib::{LogicalBlockSize, PartitionTable};
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Handle {
|
||||
List(Vec<u8>, usize), // Dir contents buffer, position
|
||||
Disk(usize, usize), // Disk index, position
|
||||
Partition(usize, u32, usize), // Disk index, partition index, position
|
||||
}
|
||||
|
||||
pub struct DiskWrapper {
|
||||
disk: Box<dyn Disk>,
|
||||
pt: Option<PartitionTable>,
|
||||
}
|
||||
|
||||
impl DiskWrapper {
|
||||
fn pt(disk: &mut dyn Disk) -> Option<PartitionTable> {
|
||||
let bs = match disk.block_length() {
|
||||
Ok(512) => LogicalBlockSize::Lb512,
|
||||
Ok(4096) => LogicalBlockSize::Lb4096,
|
||||
_ => return None,
|
||||
};
|
||||
struct Device<'a, 'b> { disk: &'a mut dyn Disk, offset: u64, block_bytes: &'b mut [u8] }
|
||||
|
||||
impl<'a, 'b> Seek for Device<'a, 'b> {
|
||||
fn seek(&mut self, from: SeekFrom) -> io::Result<u64> {
|
||||
let size = i64::try_from(self.disk.size()).or(Err(io::Error::new(io::ErrorKind::Other, "Disk larger than 2^63 - 1 bytes")))?;
|
||||
|
||||
self.offset = match from {
|
||||
SeekFrom::Start(new_pos) => cmp::min(self.disk.size(), new_pos),
|
||||
SeekFrom::Current(new_pos) => cmp::max(0, cmp::min(size, self.offset as i64 + new_pos)) as u64,
|
||||
SeekFrom::End(new_pos) => cmp::max(0, cmp::min(size + new_pos, size)) as u64,
|
||||
};
|
||||
|
||||
Ok(self.offset)
|
||||
}
|
||||
}
|
||||
// TODO: Perhaps this impl should be used in the rest of the scheme.
|
||||
impl<'a, 'b> Read for Device<'a, 'b> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let blksize = self.disk.block_length().map_err(|err| io::Error::from_raw_os_error(err.errno))?;
|
||||
let size_in_blocks = self.disk.size() / u64::from(blksize);
|
||||
|
||||
let disk = &mut self.disk;
|
||||
|
||||
let read_block = |block: u64, block_bytes: &mut [u8]| {
|
||||
if block >= size_in_blocks {
|
||||
return Err(io::Error::from_raw_os_error(syscall::EOVERFLOW));
|
||||
}
|
||||
loop {
|
||||
match disk.read(block, block_bytes) {
|
||||
Ok(Some(bytes)) => {
|
||||
assert_eq!(bytes, block_bytes.len());
|
||||
assert_eq!(bytes, blksize as usize);
|
||||
return Ok(());
|
||||
}
|
||||
Ok(None) => { std::thread::yield_now(); continue }
|
||||
Err(err) => return Err(io::Error::from_raw_os_error(err.errno)),
|
||||
}
|
||||
}
|
||||
};
|
||||
let bytes_read = block_io_wrapper::read(self.offset, blksize, buf, self.block_bytes, read_block)?;
|
||||
|
||||
self.offset += bytes_read as u64;
|
||||
Ok(bytes_read)
|
||||
}
|
||||
}
|
||||
|
||||
let mut block_bytes = [0u8; 4096];
|
||||
|
||||
partitionlib::get_partitions(&mut Device { disk, offset: 0, block_bytes: &mut block_bytes[..bs.into()] }, bs).ok().flatten()
|
||||
}
|
||||
fn new(mut disk: Box<dyn Disk>) -> Self {
|
||||
Self {
|
||||
pt: Self::pt(&mut *disk),
|
||||
disk,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for DiskWrapper {
|
||||
type Target = dyn Disk;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&*self.disk
|
||||
}
|
||||
}
|
||||
impl std::ops::DerefMut for DiskWrapper {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut *self.disk
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DiskScheme {
|
||||
scheme_name: String,
|
||||
chans: Box<[Arc<Mutex<Channel>>]>,
|
||||
disks: Box<[DiskWrapper]>,
|
||||
handles: BTreeMap<usize, Handle>,
|
||||
next_id: usize
|
||||
}
|
||||
|
||||
impl DiskScheme {
|
||||
pub fn new(scheme_name: String, chans: Vec<Arc<Mutex<Channel>>>, disks: Vec<Box<dyn Disk>>) -> DiskScheme {
|
||||
DiskScheme {
|
||||
scheme_name: scheme_name,
|
||||
chans: chans.into_boxed_slice(),
|
||||
disks: disks.into_iter().map(DiskWrapper::new).collect::<Vec<_>>().into_boxed_slice(),
|
||||
handles: BTreeMap::new(),
|
||||
next_id: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskScheme {
|
||||
pub fn irq(&mut self, chan_i: usize) -> bool {
|
||||
let mut chan = self.chans[chan_i].lock().unwrap();
|
||||
//TODO: check chan for irq
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemeBlockMut for DiskScheme {
|
||||
fn open(&mut self, path: &str, flags: usize, uid: u32, _gid: u32) -> Result<Option<usize>> {
|
||||
if uid == 0 {
|
||||
let path_str = path.trim_matches('/');
|
||||
if path_str.is_empty() {
|
||||
if flags & O_DIRECTORY == O_DIRECTORY || flags & O_STAT == O_STAT {
|
||||
let mut list = String::new();
|
||||
|
||||
for (disk_index, disk) in self.disks.iter().enumerate() {
|
||||
write!(list, "{}\n", disk_index).unwrap();
|
||||
|
||||
if disk.pt.is_none() {
|
||||
continue
|
||||
}
|
||||
for part_index in 0..disk.pt.as_ref().unwrap().partitions.len() {
|
||||
write!(list, "{}p{}\n", disk_index, part_index).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
self.handles.insert(id, Handle::List(list.into_bytes(), 0));
|
||||
Ok(Some(id))
|
||||
} else {
|
||||
Err(Error::new(EISDIR))
|
||||
}
|
||||
} else if let Some(p_pos) = path_str.chars().position(|c| c == 'p') {
|
||||
let disk_id_str = &path_str[..p_pos];
|
||||
if p_pos + 1 >= path_str.len() {
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
let part_id_str = &path_str[p_pos + 1..];
|
||||
let i = disk_id_str.parse::<usize>().or(Err(Error::new(ENOENT)))?;
|
||||
let p = part_id_str.parse::<u32>().or(Err(Error::new(ENOENT)))?;
|
||||
|
||||
if let Some(disk) = self.disks.get(i) {
|
||||
if disk.pt.is_none() || disk.pt.as_ref().unwrap().partitions.get(p as usize).is_none() {
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
|
||||
self.handles.insert(id, Handle::Partition(i, p, 0));
|
||||
|
||||
Ok(Some(id))
|
||||
} else {
|
||||
Err(Error::new(ENOENT))
|
||||
}
|
||||
} else {
|
||||
let i = path_str.parse::<usize>().or(Err(Error::new(ENOENT)))?;
|
||||
|
||||
if self.disks.get(i).is_some() {
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
self.handles.insert(id, Handle::Disk(i, 0));
|
||||
Ok(Some(id))
|
||||
} else {
|
||||
Err(Error::new(ENOENT))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(Error::new(EACCES))
|
||||
}
|
||||
}
|
||||
|
||||
fn dup(&mut self, id: usize, buf: &[u8]) -> Result<Option<usize>> {
|
||||
if ! buf.is_empty() {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
|
||||
let new_handle = {
|
||||
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
|
||||
handle.clone()
|
||||
};
|
||||
|
||||
let new_id = self.next_id;
|
||||
self.next_id += 1;
|
||||
self.handles.insert(new_id, new_handle);
|
||||
Ok(Some(new_id))
|
||||
}
|
||||
|
||||
fn fstat(&mut self, id: usize, stat: &mut Stat) -> Result<Option<usize>> {
|
||||
match *self.handles.get(&id).ok_or(Error::new(EBADF))? {
|
||||
Handle::List(ref data, _) => {
|
||||
stat.st_mode = MODE_DIR;
|
||||
stat.st_size = data.len() as u64;
|
||||
Ok(Some(0))
|
||||
},
|
||||
Handle::Disk(number, _) => {
|
||||
let disk = self.disks.get_mut(number).ok_or(Error::new(EBADF))?;
|
||||
stat.st_mode = MODE_FILE;
|
||||
stat.st_size = disk.size();
|
||||
stat.st_blksize = disk.block_length()?;
|
||||
Ok(Some(0))
|
||||
}
|
||||
Handle::Partition(disk_id, part_num, _) => {
|
||||
let disk = self.disks.get_mut(disk_id).ok_or(Error::new(EBADF))?;
|
||||
let size = {
|
||||
let pt = disk.pt.as_ref().ok_or(Error::new(EBADF))?;
|
||||
let partition = pt.partitions.get(part_num as usize).ok_or(Error::new(EBADF))?;
|
||||
partition.size
|
||||
};
|
||||
|
||||
stat.st_mode = MODE_FILE; // TODO: Block device?
|
||||
stat.st_size = size * u64::from(disk.block_length()?);
|
||||
stat.st_blksize = disk.block_length()?;
|
||||
stat.st_blocks = size;
|
||||
Ok(Some(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fpath(&mut self, id: usize, buf: &mut [u8]) -> Result<Option<usize>> {
|
||||
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
|
||||
|
||||
let mut i = 0;
|
||||
|
||||
let scheme_name = self.scheme_name.as_bytes();
|
||||
let mut j = 0;
|
||||
while i < buf.len() && j < scheme_name.len() {
|
||||
buf[i] = scheme_name[j];
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
|
||||
if i < buf.len() {
|
||||
buf[i] = b':';
|
||||
i += 1;
|
||||
}
|
||||
|
||||
match *handle {
|
||||
Handle::List(_, _) => (),
|
||||
Handle::Disk(number, _) => {
|
||||
let number_str = format!("{}", number);
|
||||
let number_bytes = number_str.as_bytes();
|
||||
j = 0;
|
||||
while i < buf.len() && j < number_bytes.len() {
|
||||
buf[i] = number_bytes[j];
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
Handle::Partition(disk_num, part_num, _) => {
|
||||
let path = format!("{}p{}", disk_num, part_num);
|
||||
let path_bytes = path.as_bytes();
|
||||
j = 0;
|
||||
while i < buf.len() && j < path_bytes.len() {
|
||||
buf[i] = path_bytes[j];
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(i))
|
||||
}
|
||||
|
||||
fn read(&mut self, id: usize, buf: &mut [u8]) -> Result<Option<usize>> {
|
||||
match *self.handles.get_mut(&id).ok_or(Error::new(EBADF))? {
|
||||
Handle::List(ref handle, ref mut size) => {
|
||||
let count = (&handle[*size..]).read(buf).unwrap();
|
||||
*size += count;
|
||||
Ok(Some(count))
|
||||
},
|
||||
Handle::Disk(number, ref mut size) => {
|
||||
let disk = self.disks.get_mut(number).ok_or(Error::new(EBADF))?;
|
||||
let blk_len = disk.block_length()?;
|
||||
if let Some(count) = disk.read((*size as u64)/(blk_len as u64), buf)? {
|
||||
*size += count;
|
||||
Ok(Some(count))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Handle::Partition(disk_num, part_num, ref mut position) => {
|
||||
let disk = self.disks.get_mut(disk_num).ok_or(Error::new(EBADF))?;
|
||||
let blksize = disk.block_length()?;
|
||||
|
||||
// validate that we're actually reading within the bounds of the partition
|
||||
let rel_block = *position as u64 / blksize as u64;
|
||||
|
||||
let abs_block = {
|
||||
let pt = disk.pt.as_ref().ok_or(Error::new(EBADF))?;
|
||||
let partition = pt.partitions.get(part_num as usize).ok_or(Error::new(EBADF))?;
|
||||
|
||||
let abs_block = partition.start_lba + rel_block;
|
||||
if rel_block >= partition.size {
|
||||
return Err(Error::new(EOVERFLOW));
|
||||
}
|
||||
abs_block
|
||||
};
|
||||
|
||||
if let Some(count) = disk.read(abs_block, buf)? {
|
||||
Ok(Some(count))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, id: usize, buf: &[u8]) -> Result<Option<usize>> {
|
||||
match *self.handles.get_mut(&id).ok_or(Error::new(EBADF))? {
|
||||
Handle::List(_, _) => {
|
||||
Err(Error::new(EBADF))
|
||||
},
|
||||
Handle::Disk(number, ref mut size) => {
|
||||
let disk = self.disks.get_mut(number).ok_or(Error::new(EBADF))?;
|
||||
let blk_len = disk.block_length()?;
|
||||
if let Some(count) = disk.write((*size as u64)/(blk_len as u64), buf)? {
|
||||
*size += count;
|
||||
Ok(Some(count))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Handle::Partition(disk_num, part_num, ref mut position) => {
|
||||
let disk = self.disks.get_mut(disk_num).ok_or(Error::new(EBADF))?;
|
||||
let blksize = disk.block_length()?;
|
||||
|
||||
// validate that we're actually reading within the bounds of the partition
|
||||
let rel_block = *position as u64 / blksize as u64;
|
||||
|
||||
let abs_block = {
|
||||
let pt = disk.pt.as_ref().ok_or(Error::new(EBADF))?;
|
||||
let partition = pt.partitions.get(part_num as usize).ok_or(Error::new(EBADF))?;
|
||||
|
||||
let abs_block = partition.start_lba + rel_block;
|
||||
if rel_block >= partition.size {
|
||||
return Err(Error::new(EOVERFLOW));
|
||||
}
|
||||
abs_block
|
||||
};
|
||||
|
||||
if let Some(count) = disk.write(abs_block, buf)? {
|
||||
Ok(Some(count))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn seek(&mut self, id: usize, pos: isize, whence: usize) -> Result<Option<isize>> {
|
||||
let pos = pos as usize;
|
||||
|
||||
match *self.handles.get_mut(&id).ok_or(Error::new(EBADF))? {
|
||||
Handle::List(ref mut handle, ref mut size) => {
|
||||
let len = handle.len() as usize;
|
||||
*size = match whence {
|
||||
SEEK_SET => cmp::min(len, pos),
|
||||
SEEK_CUR => cmp::max(0, cmp::min(len as isize, *size as isize + pos as isize)) as usize,
|
||||
SEEK_END => cmp::max(0, cmp::min(len as isize, len as isize + pos as isize)) as usize,
|
||||
_ => return Err(Error::new(EINVAL))
|
||||
};
|
||||
|
||||
Ok(Some(*size as isize))
|
||||
},
|
||||
Handle::Disk(number, ref mut size) => {
|
||||
let disk = self.disks.get_mut(number).ok_or(Error::new(EBADF))?;
|
||||
let len = disk.size() as usize;
|
||||
*size = match whence {
|
||||
SEEK_SET => cmp::min(len, pos),
|
||||
SEEK_CUR => cmp::max(0, cmp::min(len as isize, *size as isize + pos as isize)) as usize,
|
||||
SEEK_END => cmp::max(0, cmp::min(len as isize, len as isize + pos as isize)) as usize,
|
||||
_ => return Err(Error::new(EINVAL))
|
||||
};
|
||||
|
||||
Ok(Some(*size as isize))
|
||||
}
|
||||
Handle::Partition(disk_num, part_num, ref mut position) => {
|
||||
let disk = self.disks.get_mut(disk_num).ok_or(Error::new(EBADF))?;
|
||||
let block_count = disk.pt.as_ref().ok_or(Error::new(EBADF))?.partitions.get(part_num as usize).ok_or(Error::new(EBADF))?.size;
|
||||
let len = u64::from(disk.block_length()?) * block_count;
|
||||
|
||||
*position = match whence {
|
||||
SEEK_SET => cmp::min(len as usize, pos) as usize, // Why isn't pos u64?
|
||||
SEEK_CUR => cmp::max(0, cmp::min(len as isize, *position as isize + pos as isize)) as usize,
|
||||
SEEK_END => cmp::max(0, cmp::min(len as isize, len as isize + pos as isize)) as usize,
|
||||
_ => return Err(Error::new(EINVAL)),
|
||||
};
|
||||
Ok(Some(*position as isize))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&mut self, id: usize) -> Result<Option<usize>> {
|
||||
self.handles.remove(&id).ok_or(Error::new(EBADF)).and(Ok(Some(0)))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user