Implement ided using PIO and code from ahci

This commit is contained in:
Jeremy Soller
2022-09-07 15:28:04 -06:00
parent 41217ad6fa
commit 08a1595f54
6 changed files with 985 additions and 10 deletions
Generated
+2
View File
@@ -445,7 +445,9 @@ dependencies = [
name = "ided"
version = "0.1.0"
dependencies = [
"block-io-wrapper",
"log",
"partitionlib",
"pcid",
"redox-daemon",
"redox-log",
+2
View File
@@ -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"
+16
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
+426
View File
@@ -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)))
}
}