nvme: proper handling for PRPs
This commit is contained in:
parent
f9ab1bece3
commit
93c143fa58
@ -247,11 +247,12 @@ impl<const N: usize> fmt::Debug for String<N> {
|
||||
impl Command for IoRead {
|
||||
fn fill_sqe(&self, sqe: &mut SubmissionQueueEntry) {
|
||||
assert!(self.count < 65536);
|
||||
assert_ne!(self.count, 0);
|
||||
|
||||
sqe.command.set_opcode(0x02);
|
||||
sqe.command_specific[0] = self.lba as u32;
|
||||
sqe.command_specific[1] = (self.lba >> 32) as u32;
|
||||
sqe.command_specific[2] = self.count;
|
||||
sqe.command_specific[2] = self.count - 1;
|
||||
sqe.nsid = self.nsid;
|
||||
}
|
||||
}
|
||||
@ -259,11 +260,12 @@ impl Command for IoRead {
|
||||
impl Command for IoWrite {
|
||||
fn fill_sqe(&self, sqe: &mut SubmissionQueueEntry) {
|
||||
assert!(self.count < 65536);
|
||||
assert_ne!(self.count, 0);
|
||||
|
||||
sqe.command.set_opcode(0x01);
|
||||
sqe.command_specific[0] = self.lba as u32;
|
||||
sqe.command_specific[1] = (self.lba >> 32) as u32;
|
||||
sqe.command_specific[2] = self.count;
|
||||
sqe.command_specific[2] = self.count - 1;
|
||||
sqe.nsid = self.nsid;
|
||||
}
|
||||
}
|
||||
|
@ -16,12 +16,14 @@ pub struct NvmeDrive {
|
||||
nsid: u32,
|
||||
total_lba_count: u64,
|
||||
lba_size: u64,
|
||||
max_lba_per_request: usize,
|
||||
}
|
||||
|
||||
impl NvmeDrive {
|
||||
pub async fn create(
|
||||
controller: &'static NvmeController,
|
||||
nsid: u32,
|
||||
max_transfer_size: usize,
|
||||
) -> Result<&'static NvmeDrive, NvmeError> {
|
||||
let admin_q = controller.admin_q.get();
|
||||
let identify = admin_q.request(IdentifyNamespaceRequest { nsid }).await?;
|
||||
@ -30,12 +32,14 @@ impl NvmeDrive {
|
||||
let current_lba_format = identify.lba_fmt(current_lba_format_idx).unwrap();
|
||||
let lba_size = current_lba_format.lba_data_size().unwrap();
|
||||
let total_lba_count = identify.total_lba_count();
|
||||
let max_lba_per_request = (max_transfer_size / lba_size as usize).min(512);
|
||||
|
||||
log::debug!(
|
||||
"ns = {}, lba = {}B, size = {}M",
|
||||
"ns = {}, lba = {}B, size = {}M, max lba/req = {}",
|
||||
nsid,
|
||||
lba_size,
|
||||
(total_lba_count * lba_size) / (1024 * 1024)
|
||||
(total_lba_count * lba_size) / (1024 * 1024),
|
||||
max_lba_per_request,
|
||||
);
|
||||
|
||||
let dev = Box::leak(Box::new(NvmeDrive {
|
||||
@ -43,6 +47,7 @@ impl NvmeDrive {
|
||||
nsid,
|
||||
total_lba_count,
|
||||
lba_size,
|
||||
max_lba_per_request,
|
||||
}));
|
||||
|
||||
let node_name = format!("nvme{}n{}", controller.controller_id.get(), nsid);
|
||||
@ -73,10 +78,17 @@ impl NgBlockDevice for NvmeDrive {
|
||||
|
||||
let result = self
|
||||
.controller
|
||||
.perform_io(self.nsid, lba, lba_count, buffer_address, IoDirection::Read)
|
||||
.perform_io(
|
||||
self.nsid,
|
||||
lba,
|
||||
lba_count,
|
||||
buffer_address,
|
||||
buffer.len(),
|
||||
IoDirection::Read,
|
||||
)
|
||||
.await;
|
||||
|
||||
//log::info!(target: "io", "read #{lba}, {lba_count} blocks -> {result:?} @ {buffer_address:#x}");
|
||||
log::info!(target: "io", "read #{lba}, {lba_count} blocks -> {result:?} @ {buffer_address:#x}");
|
||||
|
||||
result
|
||||
}
|
||||
@ -93,6 +105,7 @@ impl NgBlockDevice for NvmeDrive {
|
||||
lba,
|
||||
lba_count,
|
||||
buffer_address,
|
||||
buffer.len(),
|
||||
IoDirection::Write,
|
||||
)
|
||||
.await;
|
||||
@ -110,7 +123,6 @@ impl NgBlockDevice for NvmeDrive {
|
||||
}
|
||||
|
||||
fn max_blocks_per_request(&self) -> usize {
|
||||
// TODO get from device
|
||||
8
|
||||
self.max_lba_per_request
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
use libk_mm::address::PhysicalAddress;
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
use super::queue::CommandError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NvmeError {
|
||||
InvalidBuffer(PhysicalAddress, usize),
|
||||
RequestTooLarge(usize),
|
||||
MemoryError(Error),
|
||||
CommandError(CommandError),
|
||||
}
|
||||
@ -17,6 +20,8 @@ impl From<CommandError> for NvmeError {
|
||||
impl From<NvmeError> for Error {
|
||||
fn from(value: NvmeError) -> Self {
|
||||
match value {
|
||||
NvmeError::RequestTooLarge(_) => Error::InvalidArgument,
|
||||
NvmeError::InvalidBuffer(_, _) => Error::InvalidArgument,
|
||||
NvmeError::MemoryError(err) => err,
|
||||
// TODO Error::DeviceError
|
||||
NvmeError::CommandError(_err) => Error::InvalidArgument,
|
||||
|
@ -18,11 +18,12 @@ use device_api::{
|
||||
};
|
||||
use drive::NvmeDrive;
|
||||
use libk::task::{cpu_count, cpu_index, runtime};
|
||||
use libk_mm::{address::PhysicalAddress, device::DeviceMemoryIo};
|
||||
use libk_mm::{address::PhysicalAddress, device::DeviceMemoryIo, L3_PAGE_SIZE};
|
||||
use libk_util::{
|
||||
sync::{IrqGuard, IrqSafeSpinlock},
|
||||
OneTimeInit,
|
||||
};
|
||||
use queue::PrpList;
|
||||
use tock_registers::{
|
||||
interfaces::{ReadWriteable, Readable, Writeable},
|
||||
register_bitfields, register_structs,
|
||||
@ -50,6 +51,10 @@ mod drive;
|
||||
mod error;
|
||||
mod queue;
|
||||
|
||||
pub const MAX_PAGES_PER_REQUEST: usize = 256;
|
||||
// Use host page
|
||||
pub const PAGE_SIZE: usize = L3_PAGE_SIZE;
|
||||
|
||||
register_bitfields! {
|
||||
u32,
|
||||
CC [
|
||||
@ -127,6 +132,7 @@ pub struct NvmeController {
|
||||
pci: PciDeviceInfo,
|
||||
|
||||
doorbell_shift: usize,
|
||||
min_page_size: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
@ -220,19 +226,27 @@ impl NvmeController {
|
||||
let admin_q = self.admin_q.get();
|
||||
|
||||
// Identify the controller
|
||||
let _identify = admin_q.request(IdentifyControllerRequest).await?;
|
||||
let identify = admin_q.request(IdentifyControllerRequest).await?;
|
||||
|
||||
// TODO do something with identify_controller
|
||||
let max_transfer_size = if identify.mdts == 0 {
|
||||
// Pick some sane default value
|
||||
256 * PAGE_SIZE
|
||||
} else {
|
||||
(1 << identify.mdts) * PAGE_SIZE
|
||||
};
|
||||
|
||||
self.create_queues().await?;
|
||||
|
||||
// Identify namespaces
|
||||
self.enumerate_namespaces().await?;
|
||||
self.enumerate_namespaces(max_transfer_size).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn enumerate_namespaces(&'static self) -> Result<(), NvmeError> {
|
||||
async fn enumerate_namespaces(
|
||||
&'static self,
|
||||
max_transfer_size: usize,
|
||||
) -> Result<(), NvmeError> {
|
||||
let admin_q = self.admin_q.get();
|
||||
|
||||
let namespaces = admin_q
|
||||
@ -243,7 +257,7 @@ impl NvmeController {
|
||||
let list = &namespaces.entries[..count];
|
||||
|
||||
for &nsid in list {
|
||||
match NvmeDrive::create(self, nsid).await {
|
||||
match NvmeDrive::create(self, nsid, max_transfer_size).await {
|
||||
Ok(drive) => {
|
||||
self.drive_table.lock().insert(nsid, drive);
|
||||
}
|
||||
@ -262,20 +276,15 @@ impl NvmeController {
|
||||
lba: u64,
|
||||
lba_count: usize,
|
||||
buffer_address: PhysicalAddress,
|
||||
transfer_size: usize,
|
||||
direction: IoDirection,
|
||||
) -> Result<(), NvmeError> {
|
||||
let prp_list = PrpList::from_buffer(buffer_address, transfer_size)?;
|
||||
|
||||
let _guard = IrqGuard::acquire();
|
||||
let cpu_index = cpu_index();
|
||||
let ioq = &self.ioqs.get()[cpu_index as usize];
|
||||
|
||||
// log::debug!(
|
||||
// "{:?} ioq #{}, nsid={}, lba={:#x}",
|
||||
// direction,
|
||||
// cpu_index,
|
||||
// nsid,
|
||||
// lba
|
||||
// );
|
||||
|
||||
let cmd_id = match direction {
|
||||
IoDirection::Read => ioq.submit(
|
||||
IoRead {
|
||||
@ -283,21 +292,21 @@ impl NvmeController {
|
||||
lba,
|
||||
count: lba_count as _,
|
||||
},
|
||||
&[buffer_address],
|
||||
&prp_list,
|
||||
true,
|
||||
),
|
||||
)?,
|
||||
IoDirection::Write => ioq.submit(
|
||||
IoWrite {
|
||||
nsid,
|
||||
lba,
|
||||
count: lba_count as _,
|
||||
},
|
||||
&[buffer_address],
|
||||
&prp_list,
|
||||
true,
|
||||
),
|
||||
)?,
|
||||
};
|
||||
|
||||
ioq.wait_for_completion(cmd_id, ()).await?;
|
||||
ioq.wait_for_completion(cmd_id, &prp_list, ()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -330,12 +339,6 @@ impl Device for NvmeController {
|
||||
unsafe fn init(&'static self) -> Result<(), Error> {
|
||||
let regs = self.regs.lock();
|
||||
|
||||
let min_page_size = 1usize << (12 + regs.CAP.read(CAP::MPSMIN));
|
||||
|
||||
if min_page_size > 4096 {
|
||||
panic!();
|
||||
}
|
||||
|
||||
let timeout = Duration::from_millis(regs.CAP.read(CAP::TO) * 500);
|
||||
log::debug!("Worst-case timeout: {:?}", timeout);
|
||||
|
||||
@ -434,6 +437,12 @@ pub fn probe(info: &PciDeviceInfo) -> Result<&'static dyn Device, Error> {
|
||||
regs.CC.modify(CC::ENABLE::CLEAR);
|
||||
|
||||
let doorbell_shift = regs.CAP.read(CAP::DSTRD) as usize + 1;
|
||||
let min_page_size = 1 << (regs.CAP.read(CAP::MPSMIN) + 12);
|
||||
|
||||
if min_page_size > PAGE_SIZE {
|
||||
log::error!("Cannot support NVMe HC: min page size ({min_page_size}) > host page size ({PAGE_SIZE})");
|
||||
return Err(Error::InvalidArgument);
|
||||
}
|
||||
|
||||
Ok(Box::leak(Box::new(NvmeController {
|
||||
regs: IrqSafeSpinlock::new(regs),
|
||||
@ -446,6 +455,7 @@ pub fn probe(info: &PciDeviceInfo) -> Result<&'static dyn Device, Error> {
|
||||
|
||||
io_queue_count: AtomicUsize::new(1),
|
||||
doorbell_shift,
|
||||
min_page_size,
|
||||
})))
|
||||
}
|
||||
|
||||
|
@ -94,6 +94,37 @@ pub struct QueuePair {
|
||||
inner: IrqSafeSpinlock<Inner>,
|
||||
}
|
||||
|
||||
pub enum PrpList {
|
||||
None,
|
||||
One(PhysicalAddress),
|
||||
Two(PhysicalAddress, PhysicalAddress),
|
||||
List(PhysicalAddress, PageBox<[PhysicalAddress]>),
|
||||
}
|
||||
|
||||
impl PrpList {
|
||||
pub fn from_buffer(base: PhysicalAddress, size: usize) -> Result<Self, NvmeError> {
|
||||
// TODO hardcoded page size
|
||||
if base.into_u64() % 0x1000 != 0 {
|
||||
todo!();
|
||||
}
|
||||
match size {
|
||||
0 => Ok(Self::None),
|
||||
_ if size <= 0x1000 => Ok(Self::One(base)),
|
||||
_ if size <= 0x2000 => Ok(Self::Two(base, base.add(0x1000))),
|
||||
_ => {
|
||||
let count = (size + 0xFFF) / 0x1000;
|
||||
let list = PageBox::new_slice_with(|i| base.add((i + 1) * 0x1000), count - 1)
|
||||
.map_err(NvmeError::MemoryError)?;
|
||||
Ok(Self::List(base, list))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn none() -> Self {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
|
||||
const_assert!(size_of::<CompletionQueueEntry>().is_power_of_two());
|
||||
|
||||
impl PhysicalRegionPage {
|
||||
@ -280,9 +311,12 @@ impl QueuePair {
|
||||
pub async fn wait_for_completion<T: Unpin>(
|
||||
&self,
|
||||
command_id: u32,
|
||||
list: &PrpList,
|
||||
result: T,
|
||||
) -> Result<T, CommandError> {
|
||||
let mut response = Some(result);
|
||||
// NOTE: for multiple blocks supplied via the PRP list, the NVMe controller will signal
|
||||
// a completion per each block
|
||||
poll_fn(|cx| {
|
||||
let mut inner = self.inner.lock();
|
||||
|
||||
@ -304,20 +338,33 @@ impl QueuePair {
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn submit<C: Command>(&self, cmd: C, ranges: &[PhysicalAddress], set_pending: bool) -> u32 {
|
||||
pub fn submit<C: Command>(
|
||||
&self,
|
||||
cmd: C,
|
||||
ranges: &PrpList,
|
||||
set_pending: bool,
|
||||
) -> Result<u32, NvmeError> {
|
||||
let mut inner = self.inner.lock();
|
||||
let mut sqe = SubmissionQueueEntry::zeroed();
|
||||
|
||||
match ranges.len() {
|
||||
1 => {
|
||||
sqe.data_pointer[0] = PhysicalRegionPage::with_addr(ranges[0]);
|
||||
sqe.data_pointer[1] = PhysicalRegionPage::null();
|
||||
}
|
||||
0 => {
|
||||
match ranges {
|
||||
PrpList::None => {
|
||||
sqe.data_pointer[0] = PhysicalRegionPage::null();
|
||||
sqe.data_pointer[1] = PhysicalRegionPage::null();
|
||||
}
|
||||
_ => todo!(),
|
||||
&PrpList::One(buf0) => {
|
||||
sqe.data_pointer[0] = PhysicalRegionPage::with_addr(buf0);
|
||||
sqe.data_pointer[1] = PhysicalRegionPage::null();
|
||||
}
|
||||
&PrpList::Two(buf0, buf1) => {
|
||||
sqe.data_pointer[0] = PhysicalRegionPage::with_addr(buf0);
|
||||
sqe.data_pointer[1] = PhysicalRegionPage::with_addr(buf1);
|
||||
}
|
||||
PrpList::List(buf0, list) => {
|
||||
sqe.data_pointer[0] = PhysicalRegionPage::with_addr(*buf0);
|
||||
sqe.data_pointer[1] =
|
||||
PhysicalRegionPage::with_addr(unsafe { list.as_physical_address() });
|
||||
}
|
||||
}
|
||||
|
||||
cmd.fill_sqe(&mut sqe);
|
||||
@ -331,15 +378,15 @@ impl QueuePair {
|
||||
|
||||
inner.sq.enqueue(sqe);
|
||||
|
||||
command_id
|
||||
Ok(command_id)
|
||||
}
|
||||
|
||||
pub fn request_no_data<C: Command>(
|
||||
&self,
|
||||
req: C,
|
||||
) -> impl Future<Output = Result<(), CommandError>> + '_ {
|
||||
let command_id = self.submit(req, &[], true);
|
||||
self.wait_for_completion(command_id, ())
|
||||
pub async fn request_no_data<C: Command>(&self, req: C) -> Result<(), NvmeError> {
|
||||
let list = PrpList::None;
|
||||
let command_id = self.submit(req, &list, true)?;
|
||||
self.wait_for_completion(command_id, &list, ())
|
||||
.await
|
||||
.map_err(NvmeError::CommandError)
|
||||
}
|
||||
|
||||
pub async fn request<'r, R: Request>(
|
||||
@ -350,8 +397,11 @@ impl QueuePair {
|
||||
R::Response: 'r,
|
||||
{
|
||||
let response = PageBox::new_uninit().map_err(NvmeError::MemoryError)?;
|
||||
let command_id = self.submit(req, &[unsafe { response.as_physical_address() }], true);
|
||||
let result = self.wait_for_completion(command_id, response).await?;
|
||||
let list = PrpList::from_buffer(unsafe { response.as_physical_address() }, size_of::<R>())?;
|
||||
let command_id = self.submit(req, &list, true)?;
|
||||
let result = self
|
||||
.wait_for_completion(command_id, &list, response)
|
||||
.await?;
|
||||
Ok(unsafe { result.assume_init() })
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ pub struct BlockCache<
|
||||
> {
|
||||
device: &'static dyn BlockDevice,
|
||||
block_size: usize,
|
||||
cache: AsyncMutex<LruCache<u64, Arc<IrqSafeRwLock<CachedBlock<A>>>>>,
|
||||
_pd: PhantomData<A>,
|
||||
}
|
||||
|
||||
impl DeviceMapper {
|
||||
@ -64,8 +64,9 @@ impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> DeviceMapper<A> {
|
||||
block_size: usize,
|
||||
bucket_capacity: usize,
|
||||
) -> DeviceMapper<A> {
|
||||
let cache = BlockCache::<A>::with_capacity_in(device, block_size, bucket_capacity);
|
||||
DeviceMapper::<A>::Cached(cache)
|
||||
todo!()
|
||||
// let cache = BlockCache::<A>::with_capacity_in(device, block_size, bucket_capacity);
|
||||
// DeviceMapper::<A>::Cached(cache)
|
||||
}
|
||||
|
||||
pub fn uncached_in(device: &'static dyn BlockDevice, block_size: usize) -> DeviceMapper<A> {
|
||||
@ -83,7 +84,7 @@ impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> DeviceMapper<A> {
|
||||
pub fn device(&self) -> &'static dyn BlockDevice {
|
||||
match self {
|
||||
Self::Uncached(uncache) => uncache.device(),
|
||||
Self::Cached(cache) => cache.device(),
|
||||
Self::Cached(_cache) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +95,7 @@ impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> DeviceMapper<A> {
|
||||
) -> Result<T, Error> {
|
||||
match self {
|
||||
Self::Uncached(uncache) => uncache.try_with(pos, mapper).await,
|
||||
Self::Cached(cache) => cache.try_with(pos, mapper).await,
|
||||
Self::Cached(_cache) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,17 +106,14 @@ impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> DeviceMapper<A> {
|
||||
) -> Result<T, Error> {
|
||||
match self {
|
||||
Self::Uncached(uncache) => uncache.try_with_mut(pos, mapper).await,
|
||||
Self::Cached(cache) => cache.try_with_mut(pos, mapper).await,
|
||||
Self::Cached(_cache) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn flush(&self) -> Result<(), Error> {
|
||||
match self {
|
||||
Self::Uncached(_) => Ok(()),
|
||||
Self::Cached(cache) => {
|
||||
cache.flush().await;
|
||||
Ok(())
|
||||
}
|
||||
Self::Cached(_cache) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -150,97 +148,6 @@ impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> UncachedCache<A> {
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockCache {
|
||||
pub fn with_capacity(
|
||||
device: &'static dyn BlockDevice,
|
||||
block_size: usize,
|
||||
bucket_capacity: usize,
|
||||
) -> BlockCache {
|
||||
BlockCache::with_capacity_in(device, block_size, bucket_capacity)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> BlockCache<A> {
|
||||
pub fn with_capacity_in(
|
||||
device: &'static dyn BlockDevice,
|
||||
block_size: usize,
|
||||
bucket_capacity: usize,
|
||||
) -> BlockCache<A> {
|
||||
if block_size % device.block_size() != 0 {
|
||||
panic!("Cache block size is not multiple of device block size");
|
||||
}
|
||||
BlockCache {
|
||||
device,
|
||||
block_size,
|
||||
cache: AsyncMutex::new(LruCache::with_capacity(bucket_capacity, 8)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn device(&self) -> &'static dyn BlockDevice {
|
||||
self.device
|
||||
}
|
||||
|
||||
async fn evict_block(&self, pos: u64, block: Arc<IrqSafeRwLock<CachedBlock<A>>>) {
|
||||
let read = block.read();
|
||||
if read.dirty {
|
||||
if let Err(err) = self.device.write_aligned(pos, read.data.as_slice()).await {
|
||||
log::error!("Disk error: flushing block {}: {:?}", pos, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_block(&self, pos: u64) -> Result<Arc<IrqSafeRwLock<CachedBlock<A>>>, Error> {
|
||||
let mut data = PageBox::new_uninit_slice_in(self.block_size)?;
|
||||
self.device.read_aligned(pos, data.as_slice_mut()).await?;
|
||||
let data = unsafe { data.assume_init_slice() };
|
||||
Ok(Arc::new(IrqSafeRwLock::new(CachedBlock {
|
||||
data,
|
||||
dirty: false,
|
||||
})))
|
||||
}
|
||||
|
||||
async fn entry(&self, pos: u64) -> Result<Arc<IrqSafeRwLock<CachedBlock<A>>>, Error> {
|
||||
let mut lock = self.cache.lock().await;
|
||||
let (value, evicted) = lock
|
||||
.try_get_or_insert_with_async(pos, || self.fetch_block(pos))
|
||||
.await?;
|
||||
|
||||
if let Some((pos, block)) = evicted {
|
||||
self.evict_block(pos, block).await;
|
||||
}
|
||||
|
||||
Ok(value.clone())
|
||||
}
|
||||
|
||||
pub async fn try_with<T, F: FnOnce(&[u8]) -> Result<T, Error>>(
|
||||
&self,
|
||||
pos: u64,
|
||||
mapper: F,
|
||||
) -> Result<T, Error> {
|
||||
let block = self.entry(pos).await?;
|
||||
let result = mapper(&block.read()[..])?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn try_with_mut<T, F: FnOnce(&mut [u8]) -> Result<T, Error>>(
|
||||
&self,
|
||||
pos: u64,
|
||||
mapper: F,
|
||||
) -> Result<T, Error> {
|
||||
let block = self.entry(pos).await?;
|
||||
let mut block = block.write();
|
||||
let result = mapper(&mut block[..])?;
|
||||
block.dirty = true;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn flush(&self) {
|
||||
for (pos, block) in self.cache.lock().await.flush() {
|
||||
self.evict_block(pos, block).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> CachedBlock<A> {
|
||||
pub fn set_dirty(&mut self) {
|
||||
self.dirty = true;
|
||||
|
@ -125,31 +125,48 @@ impl<'a, D: NgBlockDevice + 'a> BlockDevice for NgBlockDeviceWrapper<'a, D> {
|
||||
}
|
||||
|
||||
async fn read(&self, mut pos: u64, mut buf: &mut [u8]) -> Result<usize, Error> {
|
||||
let len = buf.len();
|
||||
let start_lba = pos / self.block_size as u64;
|
||||
let end_lba = (pos + buf.len() as u64)
|
||||
.div_ceil(self.block_size as u64)
|
||||
.min(self.block_count as u64);
|
||||
|
||||
if start_lba >= end_lba {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let mut remaining = buf.len();
|
||||
let mut remaining_lba = (end_lba - start_lba) as usize;
|
||||
let mut lba = start_lba;
|
||||
let mut offset = 0;
|
||||
|
||||
let max_lba_count = remaining_lba.min(self.max_blocks_per_request);
|
||||
let mut buffer = PageBox::new_uninit_slice(max_lba_count * self.block_size)?;
|
||||
|
||||
while remaining != 0 {
|
||||
let mut block = PageBox::new_uninit_slice(self.block_size)?;
|
||||
|
||||
let block_offset = pos as usize % self.block_size;
|
||||
let lba = pos / self.block_size as u64;
|
||||
|
||||
let amount = core::cmp::min(self.block_size - block_offset, buf.len());
|
||||
let block_count = remaining_lba.min(self.max_blocks_per_request);
|
||||
let block_offset = (pos % self.block_size as u64) as usize;
|
||||
let amount = (block_count * self.block_size - block_offset).min(remaining);
|
||||
|
||||
self.device
|
||||
.read(lba, block.as_slice_mut())
|
||||
.read(lba, buffer.as_slice_mut())
|
||||
.await
|
||||
.map_err(Self::handle_drive_error)?;
|
||||
|
||||
let block = unsafe { block.assume_init_slice() };
|
||||
let data = unsafe {
|
||||
MaybeUninit::slice_assume_init_ref(&buffer[block_offset..block_offset + amount])
|
||||
};
|
||||
|
||||
buf[..amount].copy_from_slice(&block[block_offset..block_offset + amount]);
|
||||
buf = &mut buf[amount..];
|
||||
buf[offset..offset + amount].copy_from_slice(data);
|
||||
|
||||
lba += block_count as u64;
|
||||
remaining_lba -= block_count;
|
||||
remaining -= amount;
|
||||
pos += amount as u64;
|
||||
offset += amount;
|
||||
}
|
||||
|
||||
Ok(len)
|
||||
assert_eq!(remaining_lba, 0);
|
||||
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
async fn write(&self, mut pos: u64, mut buf: &[u8]) -> Result<usize, Error> {
|
||||
|
@ -185,6 +185,12 @@ pub(crate) async fn probe_gpt<D: NgBlockDevice + 'static>(
|
||||
break;
|
||||
}
|
||||
|
||||
log::debug!(
|
||||
"Found partition: guid={}, start={}, end={}",
|
||||
pt_entry.type_guid,
|
||||
pt_entry.lba_start,
|
||||
pt_entry.lba_end
|
||||
);
|
||||
partitions.push(Partition {
|
||||
device: dev,
|
||||
lba_start: pt_entry.lba_start,
|
||||
|
@ -6,8 +6,8 @@ use abi::path::Path;
|
||||
use ext2::Ext2Fs;
|
||||
use kernel_fs::devfs;
|
||||
use libk::{
|
||||
block, random,
|
||||
vfs::{self, impls::read_fn_node, register_root, IoContext, NodeRef},
|
||||
block,
|
||||
vfs::{self, register_root, IoContext, NodeRef},
|
||||
};
|
||||
use libk_mm::{
|
||||
address::{PhysicalAddress, Virtualize},
|
||||
@ -18,7 +18,10 @@ use memfs::block::{self, BlockAllocator};
|
||||
use static_assertions::const_assert_eq;
|
||||
use yggdrasil_abi::{error::Error, io::MountOptions};
|
||||
|
||||
pub use pseudo::add_pseudo_devices;
|
||||
|
||||
// pub mod devfs;
|
||||
pub mod pseudo;
|
||||
pub mod sysfs;
|
||||
|
||||
/// Describes in-memory filesystem image used as initial root
|
||||
@ -83,16 +86,3 @@ pub fn create_filesystem(ioctx: &mut IoContext, options: &MountOptions) -> Resul
|
||||
|
||||
Ok(root)
|
||||
}
|
||||
|
||||
/// Adds "pseudo"-devices to the filesystem (i.e. /dev/random)
|
||||
pub fn add_pseudo_devices() -> Result<(), Error> {
|
||||
let random = read_fn_node(move |_, buf| {
|
||||
random::read(buf);
|
||||
Ok(buf.len())
|
||||
});
|
||||
|
||||
let root = devfs::root();
|
||||
root.add_child("random", random)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
85
kernel/src/fs/pseudo.rs
Normal file
85
kernel/src/fs/pseudo.rs
Normal file
@ -0,0 +1,85 @@
|
||||
//! Pseudo-devices
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
use abi::error::Error;
|
||||
use alloc::boxed::Box;
|
||||
use async_trait::async_trait;
|
||||
use kernel_fs::devfs;
|
||||
use libk::{
|
||||
random,
|
||||
vfs::{impls::read_fn_node, CharDevice, FileReadiness},
|
||||
};
|
||||
|
||||
struct Null;
|
||||
struct Zero;
|
||||
|
||||
impl FileReadiness for Null {
|
||||
fn poll_read(&self, _cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CharDevice for Null {
|
||||
async fn read(&'static self, _data: &mut [u8]) -> Result<usize, Error> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
async fn write(&'static self, data: &[u8]) -> Result<usize, Error> {
|
||||
Ok(data.len())
|
||||
}
|
||||
|
||||
fn read_nonblocking(&self, _data: &mut [u8]) -> Result<usize, Error> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn write_nonblocking(&self, data: &[u8]) -> Result<usize, Error> {
|
||||
Ok(data.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl FileReadiness for Zero {
|
||||
fn poll_read(&self, _cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CharDevice for Zero {
|
||||
async fn read(&'static self, data: &mut [u8]) -> Result<usize, Error> {
|
||||
data.fill(0);
|
||||
Ok(data.len())
|
||||
}
|
||||
|
||||
async fn write(&'static self, _data: &[u8]) -> Result<usize, Error> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn read_nonblocking(&self, data: &mut [u8]) -> Result<usize, Error> {
|
||||
data.fill(0);
|
||||
Ok(data.len())
|
||||
}
|
||||
|
||||
fn write_nonblocking(&self, _data: &[u8]) -> Result<usize, Error> {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
static NULL: Null = Null;
|
||||
static ZERO: Zero = Zero;
|
||||
|
||||
/// Adds "pseudo"-devices to the filesystem (i.e. /dev/random)
|
||||
pub fn add_pseudo_devices() -> Result<(), Error> {
|
||||
let random = read_fn_node(move |_, buf| {
|
||||
random::read(buf);
|
||||
Ok(buf.len())
|
||||
});
|
||||
|
||||
let root = devfs::root();
|
||||
root.add_child("random", random)?;
|
||||
|
||||
devfs::add_named_char_device(&NULL, "null".into())?;
|
||||
devfs::add_named_char_device(&ZERO, "zero".into())?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -18,6 +18,7 @@ use abi::{
|
||||
};
|
||||
use device_api::timer::RealTimeProviderDevice;
|
||||
use libk::{module, random, task::thread::Thread};
|
||||
use libk_device::monotonic_timestamp_provider;
|
||||
use libk_mm::phys;
|
||||
|
||||
use crate::{device::timer::GLOBAL_TIME, fs};
|
||||
@ -57,7 +58,13 @@ pub(crate) fn unmount(_options: &UnmountOptions) -> Result<(), Error> {
|
||||
pub(crate) fn get_clock(ty: ClockType, value: &mut MaybeUninit<SystemTime>) -> Result<(), Error> {
|
||||
let time = match ty {
|
||||
ClockType::RealTime => GLOBAL_TIME.real_timestamp()?,
|
||||
ClockType::Monotonic => todo!(),
|
||||
ClockType::Monotonic => {
|
||||
let time = monotonic_timestamp_provider().monotonic_timestamp()?;
|
||||
SystemTime {
|
||||
seconds: time.as_secs(),
|
||||
nanoseconds: time.subsec_nanos() as _,
|
||||
}
|
||||
}
|
||||
};
|
||||
value.write(time);
|
||||
Ok(())
|
||||
|
@ -5,9 +5,22 @@ use core::mem::MaybeUninit;
|
||||
use abi::error::Error;
|
||||
pub use abi::time::{ClockType, SystemTime};
|
||||
|
||||
/// Returns the "real world" time.
|
||||
pub fn get_real_time() -> Result<SystemTime, Error> {
|
||||
/// Returns the [SystemTime] structure representing time as described by a given [ClockType].
|
||||
#[inline]
|
||||
pub fn get_clock(clock_type: ClockType) -> Result<SystemTime, Error> {
|
||||
let mut time = MaybeUninit::uninit();
|
||||
unsafe { crate::sys::get_clock(ClockType::RealTime, &mut time) }?;
|
||||
unsafe { crate::sys::get_clock(clock_type, &mut time) }?;
|
||||
Ok(unsafe { time.assume_init() })
|
||||
}
|
||||
|
||||
/// Returns the "real world" time.
|
||||
#[inline]
|
||||
pub fn get_real_time() -> Result<SystemTime, Error> {
|
||||
get_clock(ClockType::RealTime)
|
||||
}
|
||||
|
||||
/// Returns the monotonic time since boot.
|
||||
#[inline]
|
||||
pub fn get_monotonic_time() -> Result<SystemTime, Error> {
|
||||
get_clock(ClockType::Monotonic)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
#![feature(yggdrasil_os)]
|
||||
use std::{
|
||||
io::{self, Read, Seek, SeekFrom, Write},
|
||||
process::ExitCode,
|
||||
process::ExitCode, time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
@ -20,6 +20,8 @@ struct Args {
|
||||
src_bs: u64,
|
||||
#[arg(short, long, default_value_t = usize::MAX)]
|
||||
count: usize,
|
||||
#[arg(short)]
|
||||
throughput: bool,
|
||||
|
||||
// TODO: remove this when pipes are a thing
|
||||
#[arg(short = 'x', long)]
|
||||
@ -64,27 +66,56 @@ fn dump_block(offset: u64, data: &[u8]) {
|
||||
}
|
||||
}
|
||||
|
||||
fn print_throughput(duration: Duration, bytes_read: usize) {
|
||||
let read_total = humansize::format_size(bytes_read as u64, humansize::FormatSizeOptions::default());
|
||||
let read_per_second = bytes_read as u64 / duration.as_secs();
|
||||
let read_speed = humansize::format_size(read_per_second, humansize::FormatSizeOptions::default());
|
||||
|
||||
eprintln!("{read_speed}/s ({read_total} in {duration:?})");
|
||||
}
|
||||
|
||||
fn run<I: Read + Seek, O: Write>(
|
||||
mut input: I,
|
||||
mut output: O,
|
||||
src_position: u64,
|
||||
src_block_size: u64,
|
||||
mut count: usize,
|
||||
as_hex: bool,
|
||||
opts: &Args
|
||||
) -> io::Result<()> {
|
||||
const STATS_INTERVAL: Duration = Duration::from_secs(3);
|
||||
|
||||
let mut block = vec![0; src_block_size as usize];
|
||||
let mut offset = 0;
|
||||
let mut bytes_read = 0;
|
||||
|
||||
input.seek(SeekFrom::Start(src_position * src_block_size))?;
|
||||
// input.seek(SeekFrom::Start(src_position * src_block_size))?;
|
||||
|
||||
let mut total_delta = Duration::ZERO;
|
||||
|
||||
while count != 0 {
|
||||
let start = Instant::now();
|
||||
let read_count = input.read(&mut block)?;
|
||||
let read_time = Instant::now() - start;
|
||||
|
||||
bytes_read += read_count;
|
||||
total_delta += read_time;
|
||||
|
||||
if opts.throughput && total_delta > STATS_INTERVAL {
|
||||
print_throughput(total_delta, bytes_read);
|
||||
total_delta = Duration::ZERO;
|
||||
bytes_read = 0;
|
||||
}
|
||||
|
||||
if read_count == 0 {
|
||||
if opts.throughput {
|
||||
if bytes_read != 0 {
|
||||
print_throughput(total_delta, bytes_read);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if as_hex {
|
||||
if opts.as_hex {
|
||||
dump_block(
|
||||
(src_position + offset) * src_block_size,
|
||||
&block[..read_count],
|
||||
@ -127,7 +158,7 @@ fn main() -> ExitCode {
|
||||
src_position,
|
||||
args.src_bs,
|
||||
args.count,
|
||||
args.as_hex,
|
||||
&args
|
||||
) {
|
||||
Ok(_) => ExitCode::SUCCESS,
|
||||
Err(e) => {
|
||||
|
@ -9,7 +9,7 @@ use std::{
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
fn do_digest<P: AsRef<Path>>(path: P) -> Result<(), io::Error> {
|
||||
let mut buf = [0; 4096];
|
||||
let mut buf = [0; 32768];
|
||||
let mut file = File::open(path)?;
|
||||
let mut hasher = Sha256::new();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user