From 1813db5105bdcfeea97f2995ffdf1a96e7c9daa2 Mon Sep 17 00:00:00 2001 From: Thog Date: Thu, 13 Jun 2019 19:13:12 +0200 Subject: [PATCH 01/16] Make array buffer work in libuser --- libuser/src/ipc/buffer.rs | 155 ++++---------------------------------- libuser/src/ipc/mod.rs | 37 ++++++++- 2 files changed, 50 insertions(+), 142 deletions(-) diff --git a/libuser/src/ipc/buffer.rs b/libuser/src/ipc/buffer.rs index 12c617124..b215cecb0 100644 --- a/libuser/src/ipc/buffer.rs +++ b/libuser/src/ipc/buffer.rs @@ -23,6 +23,7 @@ use core::marker::PhantomData; use crate::ipc::IPCBuffer; use crate::error::{Error, LibuserError}; use core::mem::{size_of, align_of}; +use crate::ipc::SizedIPCBuffer; // TODO: Use plain to ensure T is a valid POD type // BODY: Plain would give us two benefits: it would do the alignment and size @@ -44,7 +45,7 @@ pub struct InPointer<'a, T: ?Sized> { phantom: PhantomData<&'a T> } -impl<'a, T> InPointer<'a, T> { +impl<'a, T: ?Sized + SizedIPCBuffer> InPointer<'a, T> { /// Creates a new InPointer from an underlying [IPCBuffer]. /// /// # Panics @@ -59,8 +60,7 @@ impl<'a, T> InPointer<'a, T> { /// Returns an InvalidIpcBuffer error if the address is not properly aligned. pub fn new(buf: IPCBuffer) -> Result, Error> { assert!(buf.buftype().is_type_x()); - if buf.size != size_of::() as u64 || - buf.addr % (align_of::() as u64) != 0 { + if T::is_cool(buf.addr as usize, buf.size as usize) { Err(LibuserError::InvalidIpcBuffer.into()) } else { Ok(InPointer { @@ -72,11 +72,11 @@ impl<'a, T> InPointer<'a, T> { } } -impl<'a, T> core::ops::Deref for InPointer<'a, T> { +impl<'a, T: ?Sized + SizedIPCBuffer> core::ops::Deref for InPointer<'a, T> { type Target = T; fn deref(&self) -> &T { unsafe { - (self.addr as usize as *const T).as_ref().unwrap() + T::from_raw_parts(self.addr as usize, self.size as usize) } } } @@ -89,42 +89,6 @@ impl<'a, T: ?Sized + core::fmt::Debug> core::fmt::Debug for InPointer<'a, T> { } } -impl<'a, T> InPointer<'a, [T]> { - /// Creates a new InPointer from an underlying [IPCBuffer]. - /// - /// # Panics - /// - /// Panics if the passed buffer is not a Type-X buffer. - /// - /// # Errors - /// - /// Returns a InvalidIpcBuffer error if the size does not match what was - /// expected - pub fn new(buf: IPCBuffer) -> Result, Error> { - assert!(buf.buftype().is_type_x()); - if buf.size % size_of::() as u64 != 0 || buf.size == 0 || - buf.addr % (align_of::() as u64) != 0 { - Err(LibuserError::InvalidIpcBuffer.into()) - } else { - Ok(InPointer { - addr: buf.addr, - size: buf.size, - phantom: PhantomData - }) - } - } -} - -impl<'a, T> core::ops::Deref for InPointer<'a, [T]> { - type Target = [T]; - fn deref(&self) -> &[T] { - unsafe { - core::slice::from_raw_parts(self.addr as usize as *const T, - self.size as usize / size_of::()) - } - } -} - /// An incoming Buffer, also known as a Type-A Buffer. /// /// Note that `T` should be a POD type (in other word, it should be defined for @@ -140,8 +104,7 @@ pub struct InBuffer<'a, T: ?Sized> { phantom: PhantomData<&'a T> } - -impl<'a, T> InBuffer<'a, T> { +impl<'a, T: ?Sized + SizedIPCBuffer> InBuffer<'a, T> { /// Creates a new InBuffer from an underlying [IPCBuffer]. /// /// # Panics @@ -154,8 +117,7 @@ impl<'a, T> InBuffer<'a, T> { /// expected pub fn new(buf: IPCBuffer) -> Result, Error> { assert!(buf.buftype().is_type_a()); - if buf.size != size_of::() as u64 || - buf.addr % (align_of::() as u64) != 0 { + if T::is_cool(buf.addr as usize, buf.size as usize) { Err(LibuserError::InvalidIpcBuffer.into()) } else { Ok(InBuffer { @@ -167,11 +129,11 @@ impl<'a, T> InBuffer<'a, T> { } } -impl<'a, T> core::ops::Deref for InBuffer<'a, T> { +impl<'a, T: ?Sized + SizedIPCBuffer> core::ops::Deref for InBuffer<'a, T> { type Target = T; fn deref(&self) -> &T { unsafe { - (self.addr as usize as *const T).as_ref().unwrap() + T::from_raw_parts(self.addr as usize, self.size as usize) } } } @@ -184,44 +146,6 @@ impl<'a, T: ?Sized + core::fmt::Debug> core::fmt::Debug for InBuffer<'a, T> { } } - -impl<'a, T> InBuffer<'a, [T]> { - /// Creates a new InBuffer from an underlying [IPCBuffer]. - /// - /// # Panics - /// - /// Panics if the passed buffer is not a Type-A buffer. - /// - /// # Errors - /// - /// Returns a InvalidIpcBuffer error if the size does not match what was - /// expected - pub fn new(buf: IPCBuffer) -> Result, Error> { - assert!(buf.buftype().is_type_a()); - if buf.size % size_of::() as u64 != 0 || buf.size == 0 || - buf.addr % (align_of::() as u64) != 0 { - Err(LibuserError::InvalidIpcBuffer.into()) - } else { - Ok(InBuffer { - addr: buf.addr, - size: buf.size, - phantom: PhantomData - }) - } - } -} - -impl<'a, T> core::ops::Deref for InBuffer<'a, [T]> { - type Target = [T]; - fn deref(&self) -> &[T] { - unsafe { - core::slice::from_raw_parts(self.addr as usize as *const T, - self.size as usize / size_of::()) - } - } -} - - /// An outcoming Buffer, also known as a Type-B Buffer. /// /// Note that `T` should be a POD type (in other word, it should be defined for @@ -238,7 +162,7 @@ pub struct OutBuffer<'a, T: ?Sized> { } -impl<'a, T> OutBuffer<'a, T> { +impl<'a, T: ?Sized + SizedIPCBuffer> OutBuffer<'a, T> { /// Creates a new OutBuffer from an underlying [IPCBuffer]. /// /// # Panics @@ -251,8 +175,7 @@ impl<'a, T> OutBuffer<'a, T> { /// expected pub fn new(buf: IPCBuffer) -> Result, Error> { assert!(buf.buftype().is_type_b()); - if buf.size != size_of::() as u64 || - buf.addr % (align_of::() as u64) != 0 { + if T::is_cool(buf.addr as usize, buf.size as usize) { Err(LibuserError::InvalidIpcBuffer.into()) } else { Ok(OutBuffer { @@ -264,19 +187,19 @@ impl<'a, T> OutBuffer<'a, T> { } } -impl<'a, T> core::ops::Deref for OutBuffer<'a, T> { +impl<'a, T: ?Sized + SizedIPCBuffer> core::ops::Deref for OutBuffer<'a, T> { type Target = T; fn deref(&self) -> &T { unsafe { - (self.addr as usize as *const T).as_ref().unwrap() + T::from_raw_parts(self.addr as usize, self.size as usize) } } } -impl<'a, T> core::ops::DerefMut for OutBuffer<'a, T> { +impl<'a, T: ?Sized + SizedIPCBuffer> core::ops::DerefMut for OutBuffer<'a, T> { fn deref_mut(&mut self) -> &mut T { unsafe { - (self.addr as usize as *mut T).as_mut().unwrap() + T::from_raw_parts_mut(self.addr as usize, self.size as usize) } } } @@ -287,50 +210,4 @@ impl<'a, T: ?Sized + core::fmt::Debug> core::fmt::Debug for OutBuffer<'a, T> { .field(&*self) .finish() } -} - - -impl<'a, T> OutBuffer<'a, [T]> { - /// Creates a new OutBuffer from an underlying [IPCBuffer]. - /// - /// # Panics - /// - /// Panics if the passed buffer is not a Type-B buffer. - /// - /// # Errors - /// - /// Returns a InvalidIpcBuffer error if the size does not match what was - /// expected - pub fn new(buf: IPCBuffer) -> Result, Error> { - assert!(buf.buftype().is_type_b()); - if buf.size % size_of::() as u64 != 0 || buf.size == 0 || - buf.addr % (align_of::() as u64) != 0 { - Err(LibuserError::InvalidIpcBuffer.into()) - } else { - Ok(OutBuffer { - addr: buf.addr, - size: buf.size, - phantom: PhantomData - }) - } - } -} - -impl<'a, T> core::ops::Deref for OutBuffer<'a, [T]> { - type Target = [T]; - fn deref(&self) -> &[T] { - unsafe { - core::slice::from_raw_parts(self.addr as usize as *const T, - self.size as usize / size_of::()) - } - } -} - -impl<'a, T> core::ops::DerefMut for OutBuffer<'a, [T]> { - fn deref_mut(&mut self) -> &mut [T] { - unsafe { - core::slice::from_raw_parts_mut(self.addr as usize as *mut T, - self.size as usize / size_of::()) - } - } -} +} \ No newline at end of file diff --git a/libuser/src/ipc/mod.rs b/libuser/src/ipc/mod.rs index 33fcecb53..841809ab3 100644 --- a/libuser/src/ipc/mod.rs +++ b/libuser/src/ipc/mod.rs @@ -179,18 +179,49 @@ pub struct IPCBuffer<'a> { pub trait SizedIPCBuffer { /// Return the size of the type. fn size(&self) -> usize; + + fn is_cool(addr: usize, size: usize) -> bool; + + unsafe fn from_raw_parts<'a>(addr: usize, size: usize) -> &'a Self; + unsafe fn from_raw_parts_mut<'a>(addr: usize, size: usize) -> &'a mut Self; } impl SizedIPCBuffer for T { fn size(&self) -> usize { core::mem::size_of::() } + + fn is_cool(addr: usize, size: usize) -> bool { + size == core::mem::size_of::() && + addr % (core::mem::align_of::()) == 0 + } + + unsafe fn from_raw_parts<'a>(addr: usize, size: usize) -> &'a Self { + (addr as *const T).as_ref().unwrap() + } + + unsafe fn from_raw_parts_mut<'a>(addr: usize, size: usize) -> &'a mut Self { + (addr as *mut T).as_mut().unwrap() + } } impl SizedIPCBuffer for [T] { fn size(&self) -> usize { core::mem::size_of::() * self.len() } + + fn is_cool(addr: usize, size: usize) -> bool { + size % core::mem::size_of::() == 0 && size != 0 && + addr % (core::mem::align_of::()) == 0 + } + + unsafe fn from_raw_parts<'a>(addr: usize, size: usize) -> &'a Self { + core::slice::from_raw_parts(addr as *const T, size / core::mem::size_of::()) + } + + unsafe fn from_raw_parts_mut<'a>(addr: usize, size: usize) -> &'a mut Self { + core::slice::from_raw_parts_mut(addr as *mut T, size / core::mem::size_of::()) + } } impl<'a> IPCBuffer<'a> { @@ -527,7 +558,7 @@ where /// /// This method is unsafe as it allows creating references to arbitrary /// memory, and of arbitrary type. - pub unsafe fn pop_in_buffer(&mut self) -> Result, Error> { + pub unsafe fn pop_in_buffer(&mut self) -> Result, Error> { self.buffers.iter().position(|buf| buf.buftype().is_type_a()) .and_then(|pos| self.buffers.pop_at(pos)) .ok_or_else(|| LibuserError::InvalidIpcBufferCount.into()) @@ -547,7 +578,7 @@ where /// /// This method is unsafe as it allows creating references to arbitrary /// memory, and of arbitrary type. - pub unsafe fn pop_out_buffer(&mut self) -> Result, Error> { + pub unsafe fn pop_out_buffer(&mut self) -> Result, Error> { self.buffers.iter().position(|buf| buf.buftype().is_type_b()) .and_then(|pos| self.buffers.pop_at(pos)) .ok_or_else(|| LibuserError::InvalidIpcBufferCount.into()) @@ -610,7 +641,7 @@ where /// /// This method is unsafe as it allows creating references to arbitrary /// memory, and of arbitrary type. - pub unsafe fn pop_in_pointer(&mut self) -> Result, Error> { + pub unsafe fn pop_in_pointer(&mut self) -> Result, Error> { self.buffers.iter().position(|buf| buf.buftype().is_type_x()) .and_then(|pos| self.buffers.pop_at(pos)) .ok_or_else(|| LibuserError::InvalidIpcBufferCount.into()) From 3a0f4b039843e2f42447da825d056c733f969244 Mon Sep 17 00:00:00 2001 From: Thomas Guillemard Date: Mon, 1 Jul 2019 13:12:32 +0000 Subject: [PATCH 02/16] libuser buffers: Remove lifetime dependency --- libuser/src/ipc/buffer.rs | 39 +++++++++++++++++++-------------------- libuser/src/ipc/mod.rs | 6 +++--- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/libuser/src/ipc/buffer.rs b/libuser/src/ipc/buffer.rs index b215cecb0..fa689b26d 100644 --- a/libuser/src/ipc/buffer.rs +++ b/libuser/src/ipc/buffer.rs @@ -22,7 +22,6 @@ use core::marker::PhantomData; use crate::ipc::IPCBuffer; use crate::error::{Error, LibuserError}; -use core::mem::{size_of, align_of}; use crate::ipc::SizedIPCBuffer; // TODO: Use plain to ensure T is a valid POD type @@ -35,17 +34,17 @@ use crate::ipc::SizedIPCBuffer; /// Note that `T` should be a POD type (in other word, it should be defined for /// all bit values). This usually means that it should be a repr(C) struct and /// only contain numeric fields. -pub struct InPointer<'a, T: ?Sized> { +pub struct InPointer { /// Address of the InBuffer in the current address space. addr: u64, /// Size of the InPointer. Should match the size of T, or be a multiple of /// the size of T::Item if T is a slice. size: u64, /// Lifetime of the InPointer, should be bound to a [Message](crate::ipc::Message). - phantom: PhantomData<&'a T> + phantom: PhantomData } -impl<'a, T: ?Sized + SizedIPCBuffer> InPointer<'a, T> { +impl InPointer { /// Creates a new InPointer from an underlying [IPCBuffer]. /// /// # Panics @@ -58,7 +57,7 @@ impl<'a, T: ?Sized + SizedIPCBuffer> InPointer<'a, T> { /// expected. /// /// Returns an InvalidIpcBuffer error if the address is not properly aligned. - pub fn new(buf: IPCBuffer) -> Result, Error> { + pub fn new(buf: IPCBuffer) -> Result, Error> { assert!(buf.buftype().is_type_x()); if T::is_cool(buf.addr as usize, buf.size as usize) { Err(LibuserError::InvalidIpcBuffer.into()) @@ -72,7 +71,7 @@ impl<'a, T: ?Sized + SizedIPCBuffer> InPointer<'a, T> { } } -impl<'a, T: ?Sized + SizedIPCBuffer> core::ops::Deref for InPointer<'a, T> { +impl core::ops::Deref for InPointer { type Target = T; fn deref(&self) -> &T { unsafe { @@ -81,7 +80,7 @@ impl<'a, T: ?Sized + SizedIPCBuffer> core::ops::Deref for InPointer<'a, T> { } } -impl<'a, T: ?Sized + core::fmt::Debug> core::fmt::Debug for InPointer<'a, T> { +impl core::fmt::Debug for InPointer { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { f.debug_tuple("InPointer") .field(&*self) @@ -94,17 +93,17 @@ impl<'a, T: ?Sized + core::fmt::Debug> core::fmt::Debug for InPointer<'a, T> { /// Note that `T` should be a POD type (in other word, it should be defined for /// all bit values). This usually means that it should be a repr(C) struct and /// only contain numeric fields. -pub struct InBuffer<'a, T: ?Sized> { +pub struct InBuffer { /// Address of the InBuffer in the current address space. addr: u64, /// Size of the InBuffer. Should match the size of T, or be a multiple of /// the size of T::Item if T is a slice. size: u64, /// Lifetime of the InBuffer, should be bound to a [Message](crate::ipc::Message). - phantom: PhantomData<&'a T> + phantom: PhantomData } -impl<'a, T: ?Sized + SizedIPCBuffer> InBuffer<'a, T> { +impl InBuffer { /// Creates a new InBuffer from an underlying [IPCBuffer]. /// /// # Panics @@ -115,7 +114,7 @@ impl<'a, T: ?Sized + SizedIPCBuffer> InBuffer<'a, T> { /// /// Returns a InvalidIpcBuffer error if the size does not match what was /// expected - pub fn new(buf: IPCBuffer) -> Result, Error> { + pub fn new(buf: IPCBuffer) -> Result, Error> { assert!(buf.buftype().is_type_a()); if T::is_cool(buf.addr as usize, buf.size as usize) { Err(LibuserError::InvalidIpcBuffer.into()) @@ -129,7 +128,7 @@ impl<'a, T: ?Sized + SizedIPCBuffer> InBuffer<'a, T> { } } -impl<'a, T: ?Sized + SizedIPCBuffer> core::ops::Deref for InBuffer<'a, T> { +impl core::ops::Deref for InBuffer { type Target = T; fn deref(&self) -> &T { unsafe { @@ -138,7 +137,7 @@ impl<'a, T: ?Sized + SizedIPCBuffer> core::ops::Deref for InBuffer<'a, T> { } } -impl<'a, T: ?Sized + core::fmt::Debug> core::fmt::Debug for InBuffer<'a, T> { +impl core::fmt::Debug for InBuffer { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { f.debug_tuple("InBuffer") .field(&*self) @@ -151,18 +150,18 @@ impl<'a, T: ?Sized + core::fmt::Debug> core::fmt::Debug for InBuffer<'a, T> { /// Note that `T` should be a POD type (in other word, it should be defined for /// all bit values). This usually means that it should be a repr(C) struct and /// only contain numeric fields. -pub struct OutBuffer<'a, T: ?Sized> { +pub struct OutBuffer { /// Address of the OutBuffer in the current address space. addr: u64, /// Size of the OutBuffer. Should match the size of T, or be a multiple of /// the size of T::Item if T is a slice. size: u64, /// Lifetime of the OutBuffer, should be bound to a [Message](crate::ipc::Message). - phantom: PhantomData<&'a T> + phantom: PhantomData } -impl<'a, T: ?Sized + SizedIPCBuffer> OutBuffer<'a, T> { +impl OutBuffer { /// Creates a new OutBuffer from an underlying [IPCBuffer]. /// /// # Panics @@ -173,7 +172,7 @@ impl<'a, T: ?Sized + SizedIPCBuffer> OutBuffer<'a, T> { /// /// Returns a InvalidIpcBuffer error if the size does not match what was /// expected - pub fn new(buf: IPCBuffer) -> Result, Error> { + pub fn new(buf: IPCBuffer) -> Result, Error> { assert!(buf.buftype().is_type_b()); if T::is_cool(buf.addr as usize, buf.size as usize) { Err(LibuserError::InvalidIpcBuffer.into()) @@ -187,7 +186,7 @@ impl<'a, T: ?Sized + SizedIPCBuffer> OutBuffer<'a, T> { } } -impl<'a, T: ?Sized + SizedIPCBuffer> core::ops::Deref for OutBuffer<'a, T> { +impl core::ops::Deref for OutBuffer { type Target = T; fn deref(&self) -> &T { unsafe { @@ -196,7 +195,7 @@ impl<'a, T: ?Sized + SizedIPCBuffer> core::ops::Deref for OutBuffer<'a, T> { } } -impl<'a, T: ?Sized + SizedIPCBuffer> core::ops::DerefMut for OutBuffer<'a, T> { +impl core::ops::DerefMut for OutBuffer { fn deref_mut(&mut self) -> &mut T { unsafe { T::from_raw_parts_mut(self.addr as usize, self.size as usize) @@ -204,7 +203,7 @@ impl<'a, T: ?Sized + SizedIPCBuffer> core::ops::DerefMut for OutBuffer<'a, T> { } } -impl<'a, T: ?Sized + core::fmt::Debug> core::fmt::Debug for OutBuffer<'a, T> { +impl core::fmt::Debug for OutBuffer { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { f.debug_tuple("OutBuffer") .field(&*self) diff --git a/libuser/src/ipc/mod.rs b/libuser/src/ipc/mod.rs index 841809ab3..e943aed13 100644 --- a/libuser/src/ipc/mod.rs +++ b/libuser/src/ipc/mod.rs @@ -558,7 +558,7 @@ where /// /// This method is unsafe as it allows creating references to arbitrary /// memory, and of arbitrary type. - pub unsafe fn pop_in_buffer(&mut self) -> Result, Error> { + pub unsafe fn pop_in_buffer(&mut self) -> Result, Error> { self.buffers.iter().position(|buf| buf.buftype().is_type_a()) .and_then(|pos| self.buffers.pop_at(pos)) .ok_or_else(|| LibuserError::InvalidIpcBufferCount.into()) @@ -578,7 +578,7 @@ where /// /// This method is unsafe as it allows creating references to arbitrary /// memory, and of arbitrary type. - pub unsafe fn pop_out_buffer(&mut self) -> Result, Error> { + pub unsafe fn pop_out_buffer(&mut self) -> Result, Error> { self.buffers.iter().position(|buf| buf.buftype().is_type_b()) .and_then(|pos| self.buffers.pop_at(pos)) .ok_or_else(|| LibuserError::InvalidIpcBufferCount.into()) @@ -641,7 +641,7 @@ where /// /// This method is unsafe as it allows creating references to arbitrary /// memory, and of arbitrary type. - pub unsafe fn pop_in_pointer(&mut self) -> Result, Error> { + pub unsafe fn pop_in_pointer(&mut self) -> Result, Error> { self.buffers.iter().position(|buf| buf.buftype().is_type_x()) .and_then(|pos| self.buffers.pop_at(pos)) .ok_or_else(|| LibuserError::InvalidIpcBufferCount.into()) From ccf397be26cdec79b2b70a07ce31aed0cee3dc66 Mon Sep 17 00:00:00 2001 From: Thomas Guillemard Date: Tue, 2 Jul 2019 12:05:04 +0000 Subject: [PATCH 03/16] Fix inverted logic and document --- libuser/src/ipc/buffer.rs | 6 +++--- libuser/src/ipc/mod.rs | 12 ++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/libuser/src/ipc/buffer.rs b/libuser/src/ipc/buffer.rs index fa689b26d..58bfe3100 100644 --- a/libuser/src/ipc/buffer.rs +++ b/libuser/src/ipc/buffer.rs @@ -59,7 +59,7 @@ impl InPointer { /// Returns an InvalidIpcBuffer error if the address is not properly aligned. pub fn new(buf: IPCBuffer) -> Result, Error> { assert!(buf.buftype().is_type_x()); - if T::is_cool(buf.addr as usize, buf.size as usize) { + if !T::is_cool(buf.addr as usize, buf.size as usize) { Err(LibuserError::InvalidIpcBuffer.into()) } else { Ok(InPointer { @@ -116,7 +116,7 @@ impl InBuffer { /// expected pub fn new(buf: IPCBuffer) -> Result, Error> { assert!(buf.buftype().is_type_a()); - if T::is_cool(buf.addr as usize, buf.size as usize) { + if !T::is_cool(buf.addr as usize, buf.size as usize) { Err(LibuserError::InvalidIpcBuffer.into()) } else { Ok(InBuffer { @@ -174,7 +174,7 @@ impl OutBuffer { /// expected pub fn new(buf: IPCBuffer) -> Result, Error> { assert!(buf.buftype().is_type_b()); - if T::is_cool(buf.addr as usize, buf.size as usize) { + if !T::is_cool(buf.addr as usize, buf.size as usize) { Err(LibuserError::InvalidIpcBuffer.into()) } else { Ok(OutBuffer { diff --git a/libuser/src/ipc/mod.rs b/libuser/src/ipc/mod.rs index e943aed13..0b0fc3ba5 100644 --- a/libuser/src/ipc/mod.rs +++ b/libuser/src/ipc/mod.rs @@ -180,9 +180,13 @@ pub trait SizedIPCBuffer { /// Return the size of the type. fn size(&self) -> usize; + /// Check if the address and size are correct. fn is_cool(addr: usize, size: usize) -> bool; + /// Create a reference to a ipc buffer from part. unsafe fn from_raw_parts<'a>(addr: usize, size: usize) -> &'a Self; + + /// Create a mutable reference to a ipc buffer from part. unsafe fn from_raw_parts_mut<'a>(addr: usize, size: usize) -> &'a mut Self; } @@ -193,14 +197,14 @@ impl SizedIPCBuffer for T { fn is_cool(addr: usize, size: usize) -> bool { size == core::mem::size_of::() && - addr % (core::mem::align_of::()) == 0 + (addr % core::mem::align_of::()) == 0 } - unsafe fn from_raw_parts<'a>(addr: usize, size: usize) -> &'a Self { + unsafe fn from_raw_parts<'a>(addr: usize, _size: usize) -> &'a Self { (addr as *const T).as_ref().unwrap() } - unsafe fn from_raw_parts_mut<'a>(addr: usize, size: usize) -> &'a mut Self { + unsafe fn from_raw_parts_mut<'a>(addr: usize, _size: usize) -> &'a mut Self { (addr as *mut T).as_mut().unwrap() } } @@ -212,7 +216,7 @@ impl SizedIPCBuffer for [T] { fn is_cool(addr: usize, size: usize) -> bool { size % core::mem::size_of::() == 0 && size != 0 && - addr % (core::mem::align_of::()) == 0 + (addr % core::mem::align_of::()) == 0 } unsafe fn from_raw_parts<'a>(addr: usize, size: usize) -> &'a Self { From d16c2d2f6cd9e164d76e4496e1e894d60b66a9e9 Mon Sep 17 00:00:00 2001 From: roblabla Date: Wed, 3 Jul 2019 09:32:13 +0000 Subject: [PATCH 04/16] Libuser: pop_buffer now returns raw reference Remove the InPointer/InBuffer/OutPointer/OutBuffer types as they did not have a reason to exist in the new object system. Instead, the pop_buffer family of function now return a raw reference, whose lifetime is selected by the caller. This simplifies the code a fair bit. --- libuser/src/ipc/buffer.rs | 212 -------------------------------------- libuser/src/ipc/mod.rs | 51 ++++++--- 2 files changed, 37 insertions(+), 226 deletions(-) delete mode 100644 libuser/src/ipc/buffer.rs diff --git a/libuser/src/ipc/buffer.rs b/libuser/src/ipc/buffer.rs deleted file mode 100644 index 58bfe3100..000000000 --- a/libuser/src/ipc/buffer.rs +++ /dev/null @@ -1,212 +0,0 @@ -//! Server wrappers around IPC Buffers -//! -//! IPC Servers may accept different kinds of IPC Buffers, in order to move -//! around large amounts of data efficiently. There exists two kinds of IPC -//! Buffers, the Pointers and the Buffers. -//! -//! Pointers work in a pair of input and output: the Server pushes an InPointer -//! while the Client pushes an OutPointer. The kernel will memcpy the contents of -//! the OutPointer to the appropriate InPointer of the other side. -//! -//! Buffers work by remapping the memory from the sender to the receiver. The in -//! and out simply decide whether the memory is remapped as read-only or write- -//! only (on supported platforms. On platforms that don't have write-only memory, -//! it will be mapped RW instead). -//! -//! Those types are not meant to be used directly. The swipc-gen `dispatch` -//! function will use them automatically as an implementation detail. -//! -//! The types will auto-deref to their underlying type, allowing the user to -//! manipulate them as if they were normal pointers. - -use core::marker::PhantomData; -use crate::ipc::IPCBuffer; -use crate::error::{Error, LibuserError}; -use crate::ipc::SizedIPCBuffer; - -// TODO: Use plain to ensure T is a valid POD type -// BODY: Plain would give us two benefits: it would do the alignment and size -// BODY: checks for us, and it would give a type-system guarantee that our T -// BODY: is valid for any arbitrary type. - -/// An incoming Pointer buffer, also known as a Type-X Buffer. -/// -/// Note that `T` should be a POD type (in other word, it should be defined for -/// all bit values). This usually means that it should be a repr(C) struct and -/// only contain numeric fields. -pub struct InPointer { - /// Address of the InBuffer in the current address space. - addr: u64, - /// Size of the InPointer. Should match the size of T, or be a multiple of - /// the size of T::Item if T is a slice. - size: u64, - /// Lifetime of the InPointer, should be bound to a [Message](crate::ipc::Message). - phantom: PhantomData -} - -impl InPointer { - /// Creates a new InPointer from an underlying [IPCBuffer]. - /// - /// # Panics - /// - /// Panics if the passed buffer is not a Type-X buffer. - /// - /// # Errors - /// - /// Returns an InvalidIpcBuffer error if the size does not match what was - /// expected. - /// - /// Returns an InvalidIpcBuffer error if the address is not properly aligned. - pub fn new(buf: IPCBuffer) -> Result, Error> { - assert!(buf.buftype().is_type_x()); - if !T::is_cool(buf.addr as usize, buf.size as usize) { - Err(LibuserError::InvalidIpcBuffer.into()) - } else { - Ok(InPointer { - addr: buf.addr, - size: buf.size, - phantom: PhantomData - }) - } - } -} - -impl core::ops::Deref for InPointer { - type Target = T; - fn deref(&self) -> &T { - unsafe { - T::from_raw_parts(self.addr as usize, self.size as usize) - } - } -} - -impl core::fmt::Debug for InPointer { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - f.debug_tuple("InPointer") - .field(&*self) - .finish() - } -} - -/// An incoming Buffer, also known as a Type-A Buffer. -/// -/// Note that `T` should be a POD type (in other word, it should be defined for -/// all bit values). This usually means that it should be a repr(C) struct and -/// only contain numeric fields. -pub struct InBuffer { - /// Address of the InBuffer in the current address space. - addr: u64, - /// Size of the InBuffer. Should match the size of T, or be a multiple of - /// the size of T::Item if T is a slice. - size: u64, - /// Lifetime of the InBuffer, should be bound to a [Message](crate::ipc::Message). - phantom: PhantomData -} - -impl InBuffer { - /// Creates a new InBuffer from an underlying [IPCBuffer]. - /// - /// # Panics - /// - /// Panics if the passed buffer is not a Type-A buffer. - /// - /// # Errors - /// - /// Returns a InvalidIpcBuffer error if the size does not match what was - /// expected - pub fn new(buf: IPCBuffer) -> Result, Error> { - assert!(buf.buftype().is_type_a()); - if !T::is_cool(buf.addr as usize, buf.size as usize) { - Err(LibuserError::InvalidIpcBuffer.into()) - } else { - Ok(InBuffer { - addr: buf.addr, - size: buf.size, - phantom: PhantomData - }) - } - } -} - -impl core::ops::Deref for InBuffer { - type Target = T; - fn deref(&self) -> &T { - unsafe { - T::from_raw_parts(self.addr as usize, self.size as usize) - } - } -} - -impl core::fmt::Debug for InBuffer { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - f.debug_tuple("InBuffer") - .field(&*self) - .finish() - } -} - -/// An outcoming Buffer, also known as a Type-B Buffer. -/// -/// Note that `T` should be a POD type (in other word, it should be defined for -/// all bit values). This usually means that it should be a repr(C) struct and -/// only contain numeric fields. -pub struct OutBuffer { - /// Address of the OutBuffer in the current address space. - addr: u64, - /// Size of the OutBuffer. Should match the size of T, or be a multiple of - /// the size of T::Item if T is a slice. - size: u64, - /// Lifetime of the OutBuffer, should be bound to a [Message](crate::ipc::Message). - phantom: PhantomData -} - - -impl OutBuffer { - /// Creates a new OutBuffer from an underlying [IPCBuffer]. - /// - /// # Panics - /// - /// Panics if the passed buffer is not a Type-A buffer. - /// - /// # Errors - /// - /// Returns a InvalidIpcBuffer error if the size does not match what was - /// expected - pub fn new(buf: IPCBuffer) -> Result, Error> { - assert!(buf.buftype().is_type_b()); - if !T::is_cool(buf.addr as usize, buf.size as usize) { - Err(LibuserError::InvalidIpcBuffer.into()) - } else { - Ok(OutBuffer { - addr: buf.addr, - size: buf.size, - phantom: PhantomData - }) - } - } -} - -impl core::ops::Deref for OutBuffer { - type Target = T; - fn deref(&self) -> &T { - unsafe { - T::from_raw_parts(self.addr as usize, self.size as usize) - } - } -} - -impl core::ops::DerefMut for OutBuffer { - fn deref_mut(&mut self) -> &mut T { - unsafe { - T::from_raw_parts_mut(self.addr as usize, self.size as usize) - } - } -} - -impl core::fmt::Debug for OutBuffer { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - f.debug_tuple("OutBuffer") - .field(&*self) - .finish() - } -} \ No newline at end of file diff --git a/libuser/src/ipc/mod.rs b/libuser/src/ipc/mod.rs index 0b0fc3ba5..4cb917157 100644 --- a/libuser/src/ipc/mod.rs +++ b/libuser/src/ipc/mod.rs @@ -16,6 +16,7 @@ use core::marker::PhantomData; use core::mem; +use core::convert::TryFrom; use byteorder::{ByteOrder, LE}; use arrayvec::{ArrayVec, Array}; use crate::utils::{self, align_up, CursorWrite, CursorRead}; @@ -24,8 +25,6 @@ use bit_field::BitField; use crate::error::{Error, LibuserError}; pub mod server; -mod buffer; -pub use buffer::*; bitfield! { /// Represenens the header of an HIPC command. @@ -562,11 +561,19 @@ where /// /// This method is unsafe as it allows creating references to arbitrary /// memory, and of arbitrary type. - pub unsafe fn pop_in_buffer(&mut self) -> Result, Error> { - self.buffers.iter().position(|buf| buf.buftype().is_type_a()) + pub unsafe fn pop_in_buffer<'b, T: SizedIPCBuffer + ?Sized>(&mut self) -> Result<&'b T, Error> { + let buffer = self.buffers.iter().position(|buf| buf.buftype().is_type_a()) .and_then(|pos| self.buffers.pop_at(pos)) - .ok_or_else(|| LibuserError::InvalidIpcBufferCount.into()) - .and_then(|buf| InBuffer::::new(buf)) + .ok_or_else(|| LibuserError::InvalidIpcBufferCount)?; + + let addr = usize::try_from(buffer.addr).map_err(|_| LibuserError::InvalidIpcBuffer)?; + let size = usize::try_from(buffer.size).map_err(|_| LibuserError::InvalidIpcBuffer)?; + + if T::is_cool(addr, size) { + Ok(T::from_raw_parts(addr, size)) + } else { + Err(LibuserError::InvalidIpcBuffer.into()) + } } /// Retreive the next OutBuffer (type-B buffer) in the message. @@ -582,11 +589,19 @@ where /// /// This method is unsafe as it allows creating references to arbitrary /// memory, and of arbitrary type. - pub unsafe fn pop_out_buffer(&mut self) -> Result, Error> { - self.buffers.iter().position(|buf| buf.buftype().is_type_b()) + pub unsafe fn pop_out_buffer<'b, T: SizedIPCBuffer + ?Sized>(&mut self) -> Result<&'b mut T, Error> { + let buffer = self.buffers.iter().position(|buf| buf.buftype().is_type_b()) .and_then(|pos| self.buffers.pop_at(pos)) - .ok_or_else(|| LibuserError::InvalidIpcBufferCount.into()) - .and_then(|buf| OutBuffer::::new(buf)) + .ok_or_else(|| LibuserError::InvalidIpcBufferCount)?; + + let addr = usize::try_from(buffer.addr).map_err(|_| LibuserError::InvalidIpcBuffer)?; + let size = usize::try_from(buffer.size).map_err(|_| LibuserError::InvalidIpcBuffer)?; + + if T::is_cool(addr, size) { + Ok(T::from_raw_parts_mut(addr, size)) + } else { + Err(LibuserError::InvalidIpcBuffer.into()) + } } /// Push an OutBuffer (type-A buffer) backed by the specified data. @@ -645,11 +660,19 @@ where /// /// This method is unsafe as it allows creating references to arbitrary /// memory, and of arbitrary type. - pub unsafe fn pop_in_pointer(&mut self) -> Result, Error> { - self.buffers.iter().position(|buf| buf.buftype().is_type_x()) + pub unsafe fn pop_in_pointer<'b, T: SizedIPCBuffer + ?Sized>(&mut self) -> Result<&'b T, Error> { + let buffer = self.buffers.iter().position(|buf| buf.buftype().is_type_x()) .and_then(|pos| self.buffers.pop_at(pos)) - .ok_or_else(|| LibuserError::InvalidIpcBufferCount.into()) - .and_then(|buf| InPointer::::new(buf)) + .ok_or_else(|| LibuserError::InvalidIpcBufferCount)?; + + let addr = usize::try_from(buffer.addr).map_err(|_| LibuserError::InvalidIpcBuffer)?; + let size = usize::try_from(buffer.size).map_err(|_| LibuserError::InvalidIpcBuffer)?; + + if T::is_cool(addr, size) { + Ok(T::from_raw_parts(addr, size)) + } else { + Err(LibuserError::InvalidIpcBuffer.into()) + } } // TODO: Move pack to a non-generic function From 6ebb038b6133cf2be2f8951fcdd8898ebc0af1ea Mon Sep 17 00:00:00 2001 From: roblabla Date: Mon, 22 Apr 2019 16:07:19 +0200 Subject: [PATCH 05/16] PCI: Move to libuser. Implement 64-bit BARs. Share the PCI implementation across multiple drivers by moving it to libuser. --- Cargo.lock | 12 ++ ahci/src/main.rs | 18 ++- libuser/Cargo.toml | 1 + libuser/src/lib.rs | 1 + {ahci => libuser}/src/pci.rs | 217 +++++++++++++++++++++++------------ 5 files changed, 174 insertions(+), 75 deletions(-) rename {ahci => libuser}/src/pci.rs (58%) diff --git a/Cargo.lock b/Cargo.lock index deda1c1ec..d3d737166 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -210,6 +210,16 @@ dependencies = [ "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "getset" +version = "0.0.6" +source = "git+https://github.com/razican/getset?branch=deref#f237bdcc82d39c08ef8f9c841d028908ca2843d5" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.32 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "gif" version = "0.10.0" @@ -566,6 +576,7 @@ dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "font-rs 0.1.3 (git+https://github.com/SunriseOS/font-rs)", + "getset 0.0.6 (git+https://github.com/razican/getset?branch=deref)", "hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "linked_list_allocator 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -771,6 +782,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum font-rs 0.1.3 (git+https://github.com/SunriseOS/font-rs)" = "" "checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" "checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" +"checksum getset 0.0.6 (git+https://github.com/razican/getset?branch=deref)" = "" "checksum gif 0.10.0 (git+https://github.com/SunriseOS/image-gif)" = "" "checksum hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3bae29b6653b3412c2e71e9d486db9f9df5d701941d86683005efb9f2d28e3da" "checksum hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e1de41fb8dba9714efd92241565cdff73f78508c95697dd56787d3cba27e2353" diff --git a/ahci/src/main.rs b/ahci/src/main.rs index dc842f40b..854d34949 100644 --- a/ahci/src/main.rs +++ b/ahci/src/main.rs @@ -64,7 +64,6 @@ extern crate log; #[macro_use] extern crate bitfield; -mod pci; mod hba; mod fis; mod disk; @@ -80,6 +79,8 @@ use spin::Mutex; use sunrise_libuser::syscalls; use sunrise_libuser::ipc::server::SessionWrapper; use sunrise_libuser::ahci::{AhciInterface as IAhciInterface, IDiskProxy, IDisk as _}; +use sunrise_libuser::pci; +use sunrise_libuser::pci::{PciHeader, BAR}; /// Array of discovered disk. /// @@ -101,7 +102,20 @@ static DISKS: Mutex>>> = Mutex::new(Vec::new()); /// 3. Start the event loop. fn main() { debug!("AHCI driver starting up"); - let ahci_controllers = pci::get_ahci_controllers(); + let ahci_controllers = pci::discover() + .filter(|device| device.class() == 0x01 && device.subclass() == 0x06 && device.prog_if() == 0x01) + .map(|device| { + match device.header() { + PciHeader::GeneralDevice(header00) => { + match header00.bar(5) { + Some(BAR::Memory(addr, size)) => (*addr, *size), + _ => panic!("PCI device with unexpected BAR 5") + } + }, + _ => panic!("PCI device with unexpected header") + } + }); + debug!("AHCI controllers : {:#x?}", ahci_controllers); for (bar5, _) in ahci_controllers { DISKS.lock().extend( diff --git a/libuser/Cargo.toml b/libuser/Cargo.toml index a1fee38ce..ceb3b7526 100644 --- a/libuser/Cargo.toml +++ b/libuser/Cargo.toml @@ -17,6 +17,7 @@ failure = { version = "0.1", default-features = false, features = ["derive"] } font-rs = { git = "https://github.com/SunriseOS/font-rs" } log = "0.4" lazy_static = "1.3" +getset = { git = "https://github.com/razican/getset", branch = "deref" } [dependencies.byteorder] default-features = false diff --git a/libuser/src/lib.rs b/libuser/src/lib.rs index d55076115..a7e7ac51a 100644 --- a/libuser/src/lib.rs +++ b/libuser/src/lib.rs @@ -56,6 +56,7 @@ pub mod allocator; pub mod terminal; pub mod window; pub mod zero_box; +pub mod pci; mod crt0; mod log_impl; diff --git a/ahci/src/pci.rs b/libuser/src/pci.rs similarity index 58% rename from ahci/src/pci.rs rename to libuser/src/pci.rs index 1f14f5e9a..28242d5bf 100644 --- a/ahci/src/pci.rs +++ b/libuser/src/pci.rs @@ -4,7 +4,8 @@ use sunrise_libutils::io::{Io, Pio}; use spin::Mutex; -use alloc::vec::Vec; +use getset::Getters; +use bit_field::BitField; /// The CONFIG_ADDRESS I/O location. pub const CONFIG_ADDRESS: u16 = 0xCF8; @@ -12,6 +13,7 @@ pub const CONFIG_ADDRESS: u16 = 0xCF8; pub const CONFIG_DATA: u16 = 0xCFC; /// A struct tying the two pci config ports together. +#[derive(Debug)] struct PciConfigPortsPair { /// The address port. /// @@ -49,64 +51,114 @@ const MAX_FUNC: u8 = 15; const MAX_REGISTER: u8 = 63; /// A pci device, addressed by its bus number, slot, and function. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Getters)] #[allow(clippy::missing_docs_in_private_items)] -struct PciDevice { +pub struct PciDevice { /// The device's bus number. + #[get = "pub"] #[deref] bus: u8, /// The device's slot number on its bus. + #[get = "pub"] #[deref] slot: u8, /// The device's function number. + #[get = "pub"] #[deref] function: u8, /* [register 0x00] */ - /// Device id. + /// Identifies the particular device. Where valid IDs are allocated by the vendor. + #[get = "pub"] #[deref] did: u16, - /// Vendor id. + /// Identifies the manufacturer of the device. Where valid IDs are allocated by PCI-SIG (the list + /// is [here]) to ensure uniqueness and 0xFFFF is an invalid value that will be returned on read + /// accesses to Configuration Space registers of non-existent devices. + /// + /// [here]: https://pcisig.com/membership/member-companies + #[get = "pub"] #[deref] vid: u16, /* [register 0x01] */ /* status + command are volatile */ /* [register 0x02] */ + /// Specifies the type of function the device performs. + #[get = "pub"] #[deref] class: u8, + /// Specifies the specific function the device performs. + #[get = "pub"] #[deref] subclass: u8, + /// Specifies a register-level programming interface the device has, if it has any at all. + #[get = "pub"] #[deref] prog_if: u8, + /// Specifies a revision identifier for a particular device. Where valid IDs are allocated by the vendor. + #[get = "pub"] #[deref] rev_id: u8, /* [register 0x03] */ /* bist is volatile */ header_type: u8, + /// Specifies the latency timer in units of PCI bus clocks. + #[get = "pub"] #[deref] latency_timer: u8, + /// Specifies the system cache line size in 32-bit units. A device can limit the number of + /// cacheline sizes it can support, if a unsupported value is written to this field, the device + /// will behave as if a value of 0 was written. + #[get = "pub"] #[deref] cache_line_size: u8, /// Remaining registers values, based on header type. + #[get = "pub"] #[deref] header: PciHeader } /// Pci header when Header Type == 0x00 (General device). -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Getters)] #[allow(clippy::missing_docs_in_private_items)] -struct PciHeader00 { - bar0: BAR, - bar1: BAR, - bar2: BAR, - bar3: BAR, - bar4: BAR, - bar5: BAR, +pub struct GeneralPciHeader { + bars: [Option; 6], + /// Points to the Card Information Structure and is used by devices that share silicon between + /// CardBus and PCI. + #[get] #[deref] cardbus_cis_ptr: u32, subsystem_id: u16, subsystem_vendor_id: u16, expansion_rom_base_address: u32, + /// Points to a linked list of new capabilities implemented by the device. Used if bit 4 of the + /// status register (Capabilities List bit) is set to 1. The bottom two bits are reserved and + /// should be masked before the Pointer is used to access the Configuration Space. + #[get] #[deref] capabilities_ptr: u8, + /// Specifies how often the device needs access to the PCI bus (in 1/4 microsecond units). + #[get] #[deref] max_latency: u8, + /// Specifies the burst period length, in 1/4 microsecond units, that the device needs (assuming + /// a 33 MHz clock rate). + #[get] #[deref] min_grant: u8, + /// Specifies which interrupt pin the device uses. Where a value of 0x01 is INTA#, 0x02 is + /// INTB#, 0x03 is INTC#, 0x04 is INTD#, and 0x00 means the device does not use an interrupt + /// pin. + #[get] #[deref] interrupt_pin: u8, + /// Specifies which input of the system interrupt controllers the device's interrupt pin is + /// connected to and is implemented by any device that makes use of an interrupt pin. For the + /// x86 architecture this register corresponds to the PIC IRQ numbers 0-15 (and not I/O APIC IRQ + /// numbers) and a value of 0xFF defines no connection. + #[get] #[deref] interrupt_line: u8, } +impl GeneralPciHeader { + /// Get the Base Address Register at the specified index. + /// + /// May return None if idx bigger than 5, or if specified BAR does not exist (may happen if the + /// previous BAR was a 64-bit BAR). + pub fn bar(&self, idx: usize) -> Option<&BAR> { + self.bars.get(idx).and_then(|x| x.as_ref()) + } +} + /// Contents of pci config registers 0x4-0xf, structure varies based on Header Type. #[derive(Copy, Clone, Debug)] -enum PciHeader { +pub enum PciHeader { /// header type == 0x00 - GeneralDevice(PciHeader00), + GeneralDevice(GeneralPciHeader), /// header type == 0x01, not implemented PCItoPCIBridge, /// header type == 0x02, not implemented @@ -117,9 +169,11 @@ enum PciHeader { /// Base Address Registers. Minimal implementation, does not support 64-bits BARs. #[derive(Copy, Clone, Debug)] -enum BAR { +pub enum BAR { /// a memory space address and its size Memory(u32, u32), + /// a 64-bit memory space address and its size + Memory64(u64, u64), /// an IO space address Io(u32, u32) } @@ -161,13 +215,8 @@ impl PciDevice { // header_type, but bit 8 informs if device is multi-function, ignore it. let header_type = (pci_config_read_word(bus, slot, function, 3) >> 16) as u8; return match header_type & 0x7f { - 0x00 => PciHeader::GeneralDevice(PciHeader00 { - bar0: decode_bar(bus, slot, function, 4), - bar1: decode_bar(bus, slot, function, 5), - bar2: decode_bar(bus, slot, function, 6), - bar3: decode_bar(bus, slot, function, 7), - bar4: decode_bar(bus, slot, function, 8), - bar5: decode_bar(bus, slot, function, 9), + 0x00 => PciHeader::GeneralDevice(GeneralPciHeader { + bars: decode_bars(bus, slot, function), cardbus_cis_ptr: pci_config_read_word(bus, slot, function, 0xa), subsystem_id: (pci_config_read_word(bus, slot, function, 0xb) >> 16) as u16, subsystem_vendor_id: pci_config_read_word(bus, slot, function, 0xb) as u16, @@ -183,9 +232,8 @@ impl PciDevice { other => PciHeader::UnknownHeaderType(other) }; - /// Decode an u32 to BAR. - /// 64-bit BARs are not supported. - fn decode_bar(bus: u8, slot: u8, function: u8, register: u8) -> BAR { + /// Decode an u32 to . + fn decode_bar(bus: u8, slot: u8, function: u8, register: u8) -> (u32, u32) { // read bar address let addr = pci_config_read_word(bus, slot, function, register); // write to get length @@ -194,16 +242,40 @@ impl PciDevice { let length = pci_config_read_word(bus, slot, function, register); // restore original value pci_config_write_word(bus, slot, function, register, addr); - match addr & 0x01 { - 0 => { - // memory space bar - BAR::Memory(addr & 0xFFFF_FFF0, (!(length & 0xFFFF_FFF0)).wrapping_add(1)) - }, - _ => { - // io space bar - BAR::Io(addr & 0xFFFF_FFFC, (!(length & 0xFFFF_FFFC)).wrapping_add(1)) - } + + (addr, length) + } + + fn decode_bars(bus: u8, slot: u8, function: u8) -> [Option; 6] { + let mut register = 4; + let mut bars = [None; 6]; + while register < 10 { + let (addr, length) = decode_bar(bus, slot, function, register); + bars[register as usize - 4] = match (addr.get_bit(0), addr.get_bits(1..3)) { + (false, 0) => { + // memory space bar + Some(BAR::Memory(addr & 0xFFFF_FFF0, (!(length & 0xFFFF_FFF0)).wrapping_add(1))) + }, + (false, 2) => { + // memory space bar + register += 1; + let (addrhigh, lengthhigh) = decode_bar(bus, slot, function, register); + let addr = (addr as u64 & 0xFFFF_FFF0) | ((addrhigh as u64) << 32); + let length = ((length as u64 & 0xFFFF_FFF0) | ((lengthhigh as u64) << 32)).wrapping_add(1); + Some(BAR::Memory64(addr, length)) + }, + (true, _) => { + // io space bar + Some(BAR::Io(addr & 0xFFFF_FFFC, (!(length & 0xFFFF_FFFC)).wrapping_add(1))) + }, + _ => { + info!("Unsupported PCI BAR idx {} value {:#08x}", register - 4, addr); + None + } + }; + register += 1; } + bars } } } @@ -279,51 +351,50 @@ fn pci_config_write_word(bus: u8, slot: u8, func: u8, register: u8, value: u32) ports.data.write(value) } -/// Discover all pci devices, by probing the PID-VID of every slot on every bus. -/// -/// A device is discovered when its `(bus, slot, function 0x00)[register 0x00] != 0xFFFF_FFFF`. -/// Then, an additional [PciDevice] will be returned for every of its other functions that also -/// return anything different from `0xFFFF_FFFF`. -fn discover() -> Vec { - let mut devices = vec![]; - for bus in 0..MAX_BUS { - for slot in 0..MAX_SLOT { - // test function 0. - if let Some(device) = PciDevice::probe(bus, slot, 0) { - let is_multifunction = device.header_type & 0x80 != 0; - devices.push(device); - // check for other function on the same device - if is_multifunction { - for function in 1..MAX_FUNC { - if let Some(device) = PciDevice::probe(bus, slot, function) { - devices.push(device); +/// Iterator created with the [discover] function. +// First u8 is bus, second is slot, third is func. +#[derive(Debug)] +struct PciDeviceIterator(u8, u8, u8); + +impl Iterator for PciDeviceIterator { + type Item = PciDevice; + fn next(&mut self) -> Option { + for bus in self.0..MAX_BUS { + for slot in self.1..MAX_SLOT { + // test function 0. + if let Some(device) = PciDevice::probe(bus, slot, 0) { + let is_multifunction = device.header_type & 0x80 != 0; + if self.2 == 0 { + self.2 += 1; + return Some(device); + } + // check for other function on the same device + if is_multifunction { + for function in self.2..MAX_FUNC { + self.2 += 1; + if let Some(device) = PciDevice::probe(bus, slot, function) { + return Some(device); + } } } } + self.2 = 0; + self.1 += 1; } + self.2 = 0; + self.1 = 0; + self.0 += 1; } + return None; } - devices } -/// Gets the ahci controllers found by pci discovery. -/// -/// # Returns +/// Discover all pci devices, by probing the PID-VID of every slot on every bus. /// -/// Returns the controller's BAR5 address and size, if one was found. -pub fn get_ahci_controllers() -> Vec<(u32, u32)> { - discover().iter() - .filter(|device| device.class == 0x01 && device.subclass == 0x06 && device.prog_if == 0x01) - .map(|device| { - match device.header { - PciHeader::GeneralDevice(header00) => { - match header00.bar5 { - BAR::Memory(addr, size) => (addr, size), - _ => panic!("PCI device with unexpected BAR 5") - } - }, - _ => panic!("PCI device with unexpected header") - } - }) - .collect() +/// A device is discovered when its `(bus, slot, function 0x00)[register 0x00] != 0xFFFF_FFFF`. +/// Then, an additional [PciDevice] will be returned for every of its other functions that also +/// return anything different from `0xFFFF_FFFF`. +pub fn discover() -> impl Iterator + core::fmt::Debug { + PciDeviceIterator(0, 0, 0) } + From 121ff08212c5ec7d846763a522650d7232e9e111 Mon Sep 17 00:00:00 2001 From: roblabla Date: Mon, 22 Apr 2019 18:40:35 +0200 Subject: [PATCH 06/16] PCI: Fix memory64 length --- libuser/src/pci.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libuser/src/pci.rs b/libuser/src/pci.rs index 28242d5bf..0a1fcef8f 100644 --- a/libuser/src/pci.rs +++ b/libuser/src/pci.rs @@ -116,6 +116,8 @@ pub struct GeneralPciHeader { /// CardBus and PCI. #[get] #[deref] cardbus_cis_ptr: u32, + /// Subsystem ID + #[get = "pub"] #[deref] subsystem_id: u16, subsystem_vendor_id: u16, expansion_rom_base_address: u32, @@ -250,8 +252,9 @@ impl PciDevice { let mut register = 4; let mut bars = [None; 6]; while register < 10 { + let bar = &mut bars[register as usize - 4]; let (addr, length) = decode_bar(bus, slot, function, register); - bars[register as usize - 4] = match (addr.get_bit(0), addr.get_bits(1..3)) { + *bar = match (addr.get_bit(0), addr.get_bits(1..3)) { (false, 0) => { // memory space bar Some(BAR::Memory(addr & 0xFFFF_FFF0, (!(length & 0xFFFF_FFF0)).wrapping_add(1))) @@ -261,7 +264,7 @@ impl PciDevice { register += 1; let (addrhigh, lengthhigh) = decode_bar(bus, slot, function, register); let addr = (addr as u64 & 0xFFFF_FFF0) | ((addrhigh as u64) << 32); - let length = ((length as u64 & 0xFFFF_FFF0) | ((lengthhigh as u64) << 32)).wrapping_add(1); + let length = (!((length as u64 & 0xFFFF_FFF0) | ((lengthhigh as u64) << 32))).wrapping_add(1); Some(BAR::Memory64(addr, length)) }, (true, _) => { From cb434ea31e90066da62db3d323867f09f473dad1 Mon Sep 17 00:00:00 2001 From: roblabla Date: Mon, 27 May 2019 16:09:02 +0000 Subject: [PATCH 07/16] Some PCI stuff --- ahci/src/main.rs | 2 +- libuser/src/error.rs | 2 + libuser/src/pci.rs | 416 ++++++++++++++++++++++++++++---- libuser/src/pci/capabilities.rs | 186 ++++++++++++++ 4 files changed, 558 insertions(+), 48 deletions(-) create mode 100644 libuser/src/pci/capabilities.rs diff --git a/ahci/src/main.rs b/ahci/src/main.rs index 854d34949..28bbc8832 100644 --- a/ahci/src/main.rs +++ b/ahci/src/main.rs @@ -108,7 +108,7 @@ fn main() { match device.header() { PciHeader::GeneralDevice(header00) => { match header00.bar(5) { - Some(BAR::Memory(addr, size)) => (*addr, *size), + Ok(BAR::Memory(memory)) => (memory.phys_addr(), memory.size()), _ => panic!("PCI device with unexpected BAR 5") } }, diff --git a/libuser/src/error.rs b/libuser/src/error.rs index c61be839a..5c1a230c6 100644 --- a/libuser/src/error.rs +++ b/libuser/src/error.rs @@ -129,6 +129,8 @@ enum_with_val! { InvalidIpcBufferCount = 5, /// Invalid IPCBuffer InvalidIpcBuffer = 6, + /// Specified BAR does not exist in this PCI device. + MissingBAR = 7, } } diff --git a/libuser/src/pci.rs b/libuser/src/pci.rs index 0a1fcef8f..20342f8d0 100644 --- a/libuser/src/pci.rs +++ b/libuser/src/pci.rs @@ -1,17 +1,29 @@ //! PCI discovery //! //! A minimal PCI implementation, that permits only discovering AHCI devices, and querying their BAR. +//! +//! PCI Local Bus Specification: https://web.archive.org/web/20180712233954/http://fpga-faq.narod.ru/PCI_Rev_30.pdf use sunrise_libutils::io::{Io, Pio}; use spin::Mutex; use getset::Getters; use bit_field::BitField; +use alloc::vec::Vec; +use crate::types::MappedSharedMemory; +use crate::error::{LibuserError, KernelError, Error}; +use byteorder::ByteOrder; +use capabilities::Capability; + +pub mod capabilities; /// The CONFIG_ADDRESS I/O location. pub const CONFIG_ADDRESS: u16 = 0xCF8; /// The CONFIG_DATA I/O location. pub const CONFIG_DATA: u16 = 0xCFC; +/// Offset of first capability list entry. +const PCI_CAPABILITY_LIST: usize = 0x34; + /// A struct tying the two pci config ports together. #[derive(Debug)] struct PciConfigPortsPair { @@ -48,7 +60,7 @@ const MAX_SLOT: u8 = 31; /// The highest addressable function on a slot on a bus. const MAX_FUNC: u8 = 15; /// The highest addressable register on a function on a slot on a bus. -const MAX_REGISTER: u8 = 63; +const MAX_REGISTER: u8 = 63 * 4; /// A pci device, addressed by its bus number, slot, and function. #[derive(Debug, Copy, Clone, Getters)] @@ -142,17 +154,25 @@ pub struct GeneralPciHeader { /// connected to and is implemented by any device that makes use of an interrupt pin. For the /// x86 architecture this register corresponds to the PIC IRQ numbers 0-15 (and not I/O APIC IRQ /// numbers) and a value of 0xFF defines no connection. - #[get] #[deref] + #[get = "pub"] #[deref] interrupt_line: u8, } impl GeneralPciHeader { /// Get the Base Address Register at the specified index. /// - /// May return None if idx bigger than 5, or if specified BAR does not exist (may happen if the - /// previous BAR was a 64-bit BAR). - pub fn bar(&self, idx: usize) -> Option<&BAR> { - self.bars.get(idx).and_then(|x| x.as_ref()) + /// # Errors + /// + /// * [LibuserError::MissingBAR] + /// * `idx` is bigger than 5 + /// * Specified BAR does not exist (happens if the previous BAR was 64-bit). + pub fn bar(&self, idx: usize) -> Result<&BAR, Error> { + self.bars.get(idx).and_then(|x| x.as_ref()).ok_or(LibuserError::MissingBAR.into()) + } + + /// Get the 6 Base Address Registers associated with this device. + pub fn bars(&self) -> &[Option; 6] { + &self.bars } } @@ -169,15 +189,246 @@ pub enum PciHeader { UnknownHeaderType(u8) } +#[derive(Debug, Clone, Copy, Getters)] +pub struct BARMemory { + #[get = "pub"] #[deref] + phys_addr: u32, + #[get = "pub"] #[deref] + size: u32, +} + +#[derive(Debug, Clone, Copy, Getters)] +pub struct BARMemory64 { + #[get = "pub"] #[deref] + phys_addr: u64, + #[get = "pub"] #[deref] + size: u64, +} + +#[derive(Debug, Clone, Copy, Getters)] +pub struct BARIo { + #[get = "pub"] #[deref] + bus: u8, + #[get = "pub"] #[deref] + slot: u8, + #[get = "pub"] #[deref] + func: u8, + #[get = "pub"] #[deref] + register: u32, + #[get = "pub"] #[deref] + size: u32 +} + /// Base Address Registers. Minimal implementation, does not support 64-bits BARs. #[derive(Copy, Clone, Debug)] pub enum BAR { /// a memory space address and its size - Memory(u32, u32), + Memory(BARMemory), /// a 64-bit memory space address and its size - Memory64(u64, u64), + Memory64(BARMemory64), /// an IO space address - Io(u32, u32) + Io(BARIo) +} + +impl BAR { + pub fn map(&self) -> Result { + match self { + BAR::Memory(memory) => { + Ok(MappedBAR::Memory(MappedBARMemory { + phys_addr: memory.phys_addr, + virt_addr: crate::mem::map_mmio_range(memory.phys_addr as usize, memory.size as usize)?, + size: memory.size, + })) + }, + BAR::Memory64(memory) => { + Ok(MappedBAR::Memory64(MappedBARMemory64 { + phys_addr: memory.phys_addr, + virt_addr: crate::mem::map_mmio_range(memory.phys_addr as usize, memory.size as usize)?, + size: memory.size, + })) + }, + BAR::Io(io) => Ok(MappedBAR::Io(io.clone())) + } + } + +} + +#[derive(Debug)] +pub struct MappedBARMemory { + phys_addr: u32, + /// Virtual address of the BAR. + virt_addr: *mut u8, + size: u32, +} + +impl Drop for MappedBARMemory { + fn drop(&mut self) { + // TODO: Unmap the virt_addr + } +} + +#[derive(Debug)] +pub struct MappedBARMemory64 { + phys_addr: u64, + virt_addr: *mut u8, + size: u64, +} + +impl Drop for MappedBARMemory64 { + fn drop(&mut self) { + // TODO: Unmap the virt_addr + } +} + +#[derive(Debug)] +pub enum MappedBAR { + Memory(MappedBARMemory), + Memory64(MappedBARMemory64), + Io(BARIo) +} + +impl MappedBAR { + fn size(&self) -> u64 { + match self { + MappedBAR::Io(BARIo { size, .. }) => *size as u64, + MappedBAR::Memory(MappedBARMemory { size, .. }) => *size as u64, + MappedBAR::Memory64(MappedBARMemory64 { size, .. }) => *size, + } + } + + pub fn read_u8(&self, offset: u64) -> u8 { + // First, check the offset is within the size + assert!(offset < self.size(), "Out of bound read: {} < {}", offset, self.size()); + + let addr = match self { + MappedBAR::Io(bar) => { + // Handle IO-Ports + let offset = bar.register as u64 + offset; + let val = pci_config_read_word(bar.bus, bar.slot, bar.func, offset as u8 & 0xFC); + return val.to_ne_bytes()[offset.get_bits(0..2) as usize] + }, + MappedBAR::Memory(MappedBARMemory { virt_addr, .. }) => *virt_addr, + MappedBAR::Memory64(MappedBARMemory64 { virt_addr, .. }) => *virt_addr, + }; + + // Handle Memory. + unsafe { + addr.wrapping_add(offset as usize).read_volatile() + } + } + + pub fn read_u16(&self, offset: u64) -> u16 { + // First, check the offset is within the size + assert!(offset.saturating_add(1) < self.size(), "Out of bound read: {} + 1 < {}", offset, self.size()); + + let addr = match self { + MappedBAR::Io(bar) => { + // Handle IO-Ports + let offset = bar.register as u64 + offset; + let val = pci_config_read_word(bar.bus, bar.slot, bar.func, offset as u8 & 0xFE); + return BO::read_u16(&val.to_ne_bytes()[offset.get_bits(0..2) as usize..]); + }, + MappedBAR::Memory(MappedBARMemory { virt_addr, .. }) => *virt_addr, + MappedBAR::Memory64(MappedBARMemory64 { virt_addr, .. }) => *virt_addr, + }; + + // Handle Memory. + unsafe { + (addr.wrapping_add(offset as usize) as *mut u16).read_volatile() + } + } + + pub fn read_u32(&self, offset: u64) -> u32 { + // First, check the offset is within the size + assert!(offset.saturating_add(3) < self.size(), "Out of bound read: {} + 3 < {}", offset, self.size()); + + let addr = match self { + MappedBAR::Io(bar) => { + // Handle IO-Ports + let offset = bar.register as u64 + offset; + let val = pci_config_read_word(bar.bus, bar.slot, bar.func, offset as u8 & 0xFE); + return BO::read_u32(&val.to_ne_bytes()); + }, + MappedBAR::Memory(MappedBARMemory { virt_addr, .. }) => *virt_addr, + MappedBAR::Memory64(MappedBARMemory64 { virt_addr, .. }) => *virt_addr, + }; + + // Handle Memory. + unsafe { + (addr.wrapping_add(offset as usize) as *mut u32).read_volatile() + } + } + + pub fn write_u8(&self, offset: u64, data: u8) { + // First, check the offset is within the size + assert!(offset < self.size(), "Out of bound write: {} < {}", offset, self.size()); + + let addr = match self { + MappedBAR::Io(bar) => { + // Handle IO-Ports + let offset = bar.register as u64 + offset; + let val = pci_config_read_word(bar.bus, bar.slot, bar.func, offset as u8 & 0xFC); + let mut val = val.to_ne_bytes(); + val[offset.get_bits(0..2) as usize] = data; + pci_config_write_word(bar.bus, bar.slot, bar.func, offset as u8 & 0xFC, u32::from_ne_bytes(val)); + return; + }, + MappedBAR::Memory(MappedBARMemory { virt_addr, .. }) => *virt_addr, + MappedBAR::Memory64(MappedBARMemory64 { virt_addr, .. }) => *virt_addr, + }; + + // Handle Memory. + unsafe { + addr.wrapping_add(offset as usize).write_volatile(data) + } + } + + pub fn write_u16(&self, offset: u64, data: u16) { + // First, check the offset is within the size + assert!(offset.saturating_add(1) < self.size(), "Out of bound read: {} + 1 < {}", offset, self.size()); + + let addr = match self { + MappedBAR::Io(bar) => { + // Handle IO-Ports + let offset = bar.register as u64 + offset; + let val = pci_config_read_word(bar.bus, bar.slot, bar.func, offset as u8 & 0xFE); + let mut val = val.to_ne_bytes(); + BO::write_u16(&mut val[offset.get_bits(0..2) as usize..], data); + pci_config_write_word(bar.bus, bar.slot, bar.func, offset as u8 & 0xFC, u32::from_ne_bytes(val)); + return; + }, + MappedBAR::Memory(MappedBARMemory { virt_addr, .. }) => *virt_addr, + MappedBAR::Memory64(MappedBARMemory64 { virt_addr, .. }) => *virt_addr, + }; + + // Handle Memory. + unsafe { + (addr.wrapping_add(offset as usize) as *mut u16).write_volatile(data) + } + } + + pub fn write_u32(&self, offset: u64, data: u32) { + // First, check the offset is within the size + assert!(offset.saturating_add(3) < self.size(), "Out of bound read: {} + 3 < {}", offset, self.size()); + + let addr = match self { + MappedBAR::Io(bar) => { + // Handle IO-Ports + let offset = bar.register as u64 + offset; + let mut val = [0; 4]; + BO::write_u32(&mut val, data); + pci_config_write_word(bar.bus, bar.slot, bar.func, offset as u8 & 0xFC, u32::from_ne_bytes(val)); + return; + }, + MappedBAR::Memory(MappedBARMemory { virt_addr, .. }) => *virt_addr, + MappedBAR::Memory64(MappedBARMemory64 { virt_addr, .. }) => *virt_addr, + }; + + // Handle Memory. + unsafe { + (addr.wrapping_add(offset as usize) as *mut u32).write_volatile(data) + } + } } impl PciDevice { @@ -200,13 +451,13 @@ impl PciDevice { function, did: (did_vid >> 16) as u16, vid: did_vid as u16, - class: (pci_config_read_word(bus, slot, function, 2) >> 24) as u8, - subclass: (pci_config_read_word(bus, slot, function, 2) >> 16) as u8, - prog_if: (pci_config_read_word(bus, slot, function, 2) >> 8) as u8, - rev_id: pci_config_read_word(bus, slot, function, 2) as u8, - header_type: (pci_config_read_word(bus, slot, function, 3) >> 16) as u8, - latency_timer: (pci_config_read_word(bus, slot, function, 3) >> 8) as u8, - cache_line_size: pci_config_read_word(bus, slot, function, 3) as u8, + class: (pci_config_read_word(bus, slot, function, 8) >> 24) as u8, + subclass: (pci_config_read_word(bus, slot, function, 8) >> 16) as u8, + prog_if: (pci_config_read_word(bus, slot, function, 8) >> 8) as u8, + rev_id: pci_config_read_word(bus, slot, function, 8) as u8, + header_type: (pci_config_read_word(bus, slot, function, 12) >> 16) as u8, + latency_timer: (pci_config_read_word(bus, slot, function, 12) >> 8) as u8, + cache_line_size: pci_config_read_word(bus, slot, function, 12) as u8, header: read_header(bus, slot, function) }) @@ -215,27 +466,32 @@ impl PciDevice { /// Reads the remaining of the pci-registers, organising them based on header type. fn read_header(bus: u8, slot: u8, function: u8) -> PciHeader { // header_type, but bit 8 informs if device is multi-function, ignore it. - let header_type = (pci_config_read_word(bus, slot, function, 3) >> 16) as u8; + let header_type = (pci_config_read_word(bus, slot, function, 12) >> 16) as u8; return match header_type & 0x7f { 0x00 => PciHeader::GeneralDevice(GeneralPciHeader { bars: decode_bars(bus, slot, function), - cardbus_cis_ptr: pci_config_read_word(bus, slot, function, 0xa), - subsystem_id: (pci_config_read_word(bus, slot, function, 0xb) >> 16) as u16, - subsystem_vendor_id: pci_config_read_word(bus, slot, function, 0xb) as u16, - expansion_rom_base_address: pci_config_read_word(bus, slot, function, 0xc), - capabilities_ptr: pci_config_read_word(bus, slot, function, 0xd) as u8, - max_latency: (pci_config_read_word(bus, slot, function, 0xf) >> 24) as u8, - min_grant: (pci_config_read_word(bus, slot, function, 0xf) >> 16) as u8, - interrupt_pin: (pci_config_read_word(bus, slot, function, 0xf) >> 8) as u8, - interrupt_line: pci_config_read_word(bus, slot, function, 0xf) as u8, + cardbus_cis_ptr: pci_config_read_word(bus, slot, function, 40), + subsystem_id: (pci_config_read_word(bus, slot, function, 44) >> 16) as u16, + subsystem_vendor_id: pci_config_read_word(bus, slot, function, 44) as u16, + expansion_rom_base_address: pci_config_read_word(bus, slot, function, 48), + capabilities_ptr: { + let mut cap = pci_config_read_word(bus, slot, function, 52) as u8; + // Get rid of the bottom 2 bits, they are reserved. + *cap.set_bits(0..2, 0) + }, + max_latency: (pci_config_read_word(bus, slot, function, 60) >> 24) as u8, + min_grant: (pci_config_read_word(bus, slot, function, 60) >> 16) as u8, + interrupt_pin: (pci_config_read_word(bus, slot, function, 60) >> 8) as u8, + interrupt_line: pci_config_read_word(bus, slot, function, 60) as u8, }), 0x01 => PciHeader::PCItoPCIBridge, 0x02 => PciHeader::CardBus, other => PciHeader::UnknownHeaderType(other) }; - /// Decode an u32 to . - fn decode_bar(bus: u8, slot: u8, function: u8, register: u8) -> (u32, u32) { + /// Decode an u32 to BAR values. + fn decode_bar(bus: u8, slot: u8, function: u8, bar_num: u8) -> (u32, u32) { + let register = (bar_num + 4) * 4; // read bar address let addr = pci_config_read_word(bus, slot, function, register); // write to get length @@ -249,34 +505,44 @@ impl PciDevice { } fn decode_bars(bus: u8, slot: u8, function: u8) -> [Option; 6] { - let mut register = 4; + let mut bar_num = 0; let mut bars = [None; 6]; - while register < 10 { - let bar = &mut bars[register as usize - 4]; - let (addr, length) = decode_bar(bus, slot, function, register); + while bar_num < 6 { + let bar = &mut bars[bar_num as usize]; + let (addr, length) = decode_bar(bus, slot, function, bar_num); *bar = match (addr.get_bit(0), addr.get_bits(1..3)) { (false, 0) => { // memory space bar - Some(BAR::Memory(addr & 0xFFFF_FFF0, (!(length & 0xFFFF_FFF0)).wrapping_add(1))) + Some(BAR::Memory(BARMemory { + phys_addr: addr & 0xFFFF_FFF0, + size: (!(length & 0xFFFF_FFF0)).wrapping_add(1) + })) }, (false, 2) => { // memory space bar - register += 1; - let (addrhigh, lengthhigh) = decode_bar(bus, slot, function, register); + bar_num += 1; + let (addrhigh, lengthhigh) = decode_bar(bus, slot, function, bar_num); let addr = (addr as u64 & 0xFFFF_FFF0) | ((addrhigh as u64) << 32); let length = (!((length as u64 & 0xFFFF_FFF0) | ((lengthhigh as u64) << 32))).wrapping_add(1); - Some(BAR::Memory64(addr, length)) + Some(BAR::Memory64(BARMemory64 { + phys_addr: addr, + size: length + })) }, (true, _) => { // io space bar - Some(BAR::Io(addr & 0xFFFF_FFFC, (!(length & 0xFFFF_FFFC)).wrapping_add(1))) + Some(BAR::Io(BARIo { + bus, slot, func: function, + register: addr & 0xFFFF_FFFC, + size: (!(length & 0xFFFF_FFFC)).wrapping_add(1), + })) }, _ => { - info!("Unsupported PCI BAR idx {} value {:#08x}", register - 4, addr); + info!("Unsupported PCI BAR idx {} value {:#08x}", bar_num, addr); None } }; - register += 1; + bar_num += 1; } bars } @@ -284,35 +550,88 @@ impl PciDevice { } /// Reads a configuration space register. - fn read_config_register(&self, register: u8) -> u32 { + pub fn read_config_register(&self, register: u8) -> u32 { pci_config_read_word(self.bus, self.slot, self.function, register) } /// Writes to a configuration space register. - fn write_config_register(&self, register: u8, value: u32) { + pub fn write_config_register(&self, register: u8, value: u32) { pci_config_write_word(self.bus, self.slot, self.function, register, value) } // register 1 /// Reads the status register. - fn status(&self) -> u16 { - (self.read_config_register(1) >> 16) as u16 + pub fn status(&self) -> u16 { + (self.read_config_register(4) >> 16) as u16 } /// Reads the command register. fn command(&self) -> u16 { - (self.read_config_register(1) >> 0) as u16 + (self.read_config_register(4) >> 0) as u16 + } + + pub fn capabilities(&self) -> impl Iterator { + let mut capabilities_ptr = 0; + if self.status().get_bit(4) { + if let PciHeader::GeneralDevice(device) = self.header { + capabilities_ptr = device.capabilities_ptr + } + } + + capabilities::CapabilitiesIter::new(self, capabilities_ptr) + } + + pub fn enable_msix(&self, val: bool) -> Result<(), ()> { + let msix = self.capabilities().find(|v| if let Capability::MsiX(..) = v { true } else { false }); + if let Some(Capability::MsiX(msix)) = msix { + msix.enable_msix(val); + Ok(()) + } else { + Err(()) + } + } + + pub fn msix_table_size(&self) -> Result { + let msix = self.capabilities().find(|v| if let Capability::MsiX(..) = v { true } else { false }); + if let Some(Capability::MsiX(msix)) = msix { + Ok(msix.table_size()) + } else { + Err(()) + } + } + + pub fn set_msix_message_entry(&self, entry: usize, val: capabilities::MsiXEntry) -> Result<(), ()> { + let msix = self.capabilities().find(|v| if let Capability::MsiX(..) = v { true } else { false }); + if let Some(Capability::MsiX(msix)) = msix { + msix.set_message_entry(entry, val); + Ok(()) + } else { + Err(()) + } + } + + pub fn set_msix_message_upper_address(&self, val: u32) -> Result<(), ()> { + let msix = self.capabilities().find(|v| if let Capability::MsiX(..) = v { true } else { false }); + if let Some(Capability::MsiX(msix)) = msix { + msix.set_message_upper_address(val); + Ok(()) + } else { + Err(()) + } } } /// Read one of the 64 32-bit registers of a pci bus>device>func. +/// Register is a byte offset. It should be aligned to 4. #[allow(clippy::absurd_extreme_comparisons)] fn pci_config_read_word(bus: u8, slot: u8, func: u8, register: u8) -> u32 { debug_assert!(bus <= MAX_BUS); debug_assert!(slot <= MAX_SLOT); debug_assert!(func <= MAX_FUNC); debug_assert!(register <= MAX_REGISTER); + debug_assert!(register & 0x3 == 0); + let lbus = u32::from(bus); let lslot = u32::from(slot); let lfunc = u32::from(func); @@ -321,7 +640,7 @@ fn pci_config_read_word(bus: u8, slot: u8, func: u8, register: u8) -> u32 { /* create the configuration address */ let address: u32 = (lbus << 16) | (lslot << 11) | - (lfunc << 8) | (lregister << 2) | 0x80000000; + (lfunc << 8) | (lregister & 0xFC) | 0x80000000; /* write out the address */ ports.address.write(address); @@ -330,13 +649,16 @@ fn pci_config_read_word(bus: u8, slot: u8, func: u8, register: u8) -> u32 { ports.data.read() } -/// Read one of the 64 32-bit registers of a pci bus>device>func. +/// Write one of the 64 32-bit registers of a pci bus>device>func. +/// Register is a byte offset. It should be aligned to 4. #[allow(clippy::absurd_extreme_comparisons)] fn pci_config_write_word(bus: u8, slot: u8, func: u8, register: u8, value: u32) { debug_assert!(bus <= MAX_BUS); debug_assert!(slot <= MAX_SLOT); debug_assert!(func <= MAX_FUNC); debug_assert!(register <= MAX_REGISTER); + debug_assert!(register & 0x3 == 0); + let lbus = u32::from(bus); let lslot = u32::from(slot); let lfunc = u32::from(func); @@ -345,7 +667,7 @@ fn pci_config_write_word(bus: u8, slot: u8, func: u8, register: u8, value: u32) /* create the configuration address */ let address: u32 = (lbus << 16) | (lslot << 11) | - (lfunc << 8) | (lregister << 2) | 0x80000000; + (lfunc << 8) | (lregister & 0xFC) | 0x80000000; /* write out the address */ ports.address.write(address); diff --git a/libuser/src/pci/capabilities.rs b/libuser/src/pci/capabilities.rs new file mode 100644 index 000000000..743c8731a --- /dev/null +++ b/libuser/src/pci/capabilities.rs @@ -0,0 +1,186 @@ +use crate::pci::{PciDevice, PciHeader}; +use crate::pci::{pci_config_read_word, pci_config_write_word}; +use crate::error::KernelError; +use bit_field::BitField; +use byteorder::LE; + +pub(super) struct CapabilitiesIter<'a> { + device: &'a PciDevice, + offset: u8, +} + +impl<'a> Iterator for CapabilitiesIter<'a> { + type Item = Capability<'a>; + fn next(&mut self) -> Option> { + if self.offset == 0 { + return None + } + info!("Reading capability at {:#02x}", self.offset); + let (cap, next) = Capability::parse(self.device, self.offset); + self.offset = next; + Some(cap) + } +} + +impl<'a> CapabilitiesIter<'a> { + pub(super) fn new(device: &'a PciDevice, offset: u8) -> CapabilitiesIter<'a> { + CapabilitiesIter { device, offset } + } +} + +#[derive(Debug)] // TODO: More interesting debug. +pub struct MsiX<'a> { + inner: RWCapability<'a> +} + +bitfield! { + pub struct MsiXEntry(u64); + impl Debug; + pub pending, _: 0; + pub masked, set_masked: 1; + pub message_address, set_message_address: 31, 2; + pub message_data, set_message_data: 63, 32; +} + +impl<'a> MsiX<'a> { + pub fn enable_msix(&self, val: bool) { + let mut val = self.inner.read_u32(0); + val.set_bit(31, true); + self.inner.write_u32(0, val); + } + + pub fn table_size(&self) -> usize { + self.inner.read_u32(0).get_bits(16..27) as usize + 1 + } + + pub fn message_upper_address(&self) -> u32 { + self.inner.read_u32(4) + } + + pub fn set_message_upper_address(&self, val: u32) { + self.inner.write_u32(4, val) + } + + pub fn set_message_entry(&self, entry: usize, val: MsiXEntry) -> Result<(), KernelError> { + assert!(entry < self.table_size()); + let mut table_offset_bir = self.inner.read_u32(8); + let bir = table_offset_bir.get_bits(0..3); + let table_offset = *table_offset_bir.set_bits(0..3, 0); + + let PciDevice { bus, slot, function, .. } = self.inner.device; + + let header = match self.inner.device.header { + PciHeader::GeneralDevice(header) => header, + _ => unreachable!() + }; + + let bar = header.bar(bir as usize).expect(&format!("Device {}.{}.{} to contain BAR {}", bus, slot, function, bir)); + let mapped_bar = bar.map()?; + + mapped_bar.write_u32::((entry * 8) as u64, val.0.get_bits(0..32) as u32); + mapped_bar.write_u32::((entry * 8 + 4) as u64, val.0.get_bits(32..64) as u32); + + Ok(()) + } +} + +#[derive(Debug)] +pub struct RWCapability<'a> { + device: &'a PciDevice, + offset: u8, + len: u8 +} + +impl<'a> RWCapability<'a> { + pub fn read_u32(&self, offset: u8) -> u32 { + assert!(offset < self.len, "Attempted to read at offset {}, but capability only has {} bytes", offset, self.len); + pci_config_read_word(self.device.bus, self.device.slot, self.device.function, (self.offset + offset) & 0xFC) + } + pub fn write_u32(&self, offset: u8, value: u32) { + assert!(offset < self.len, "Attempted to write at offset {}, but capability only has {} bytes", offset, self.len); + pci_config_write_word(self.device.bus, self.device.slot, self.device.function, (self.offset + offset) & 0xFC, value); + } + + pub fn len(&self) -> usize { + self.len as usize + } +} + +#[derive(Debug)] +pub enum Capability<'a> { + Reserved, + PciPowerManagement, + AcceleratedGraphicsPort, + VitalProductData, + SlotIdentification, + Msi, + CompactPciHotSwap, + PciX, + HyperTransport, + VendorSpecific(RWCapability<'a>), + DebugPort, + CompactPciCentralResourceControl, + PciHotPlug, + AcceleratedGraphicsPort8x, + SecureDevice, + PciExpress, + MsiX(MsiX<'a>), + Unknown(u8), +} + +/*// TODO: Lazily read the Vendor-Specific Capabilities? +// BODY: Currently, vendor-specific capabilities are eagerly read and kept in a +// BODY: vector. This is likely suboptimal. Ideally, we should get some type that +// BODY: we can call functions on to get u32s from. +let mut size = word.get_bits(16..24) as u8; +let mut data = Vec::with_capacity(size.saturating_sub(3) as usize); + +let mut idx = 4u8; +data.push(word.get_bits(24..32) as u8); +while idx < size { + let word = pci_config_read_word(bus, slot, function, register + idx); + for i in 0..core::cmp::min(size - idx, 4) { + let i = i as usize; + data.push(word.get_bits(i * 8..(i + 1) * 8) as u8); + } + idx += 4; +}*/ +impl<'a> Capability<'a> { + pub fn parse(device: &'a PciDevice, register: u8) -> (Capability<'a>, u8) { + let word = pci_config_read_word(device.bus, device.slot, device.function, register); + let ty = word.get_bits(0..8) as u8; + let mut next = word.get_bits(8..16) as u8; + // 6.7: Get rid of the lower 2 bits, they are reserved for future use. + next.set_bits(0..2, 0); + let len = word.get_bits(16..24) as u8; + + let rw_cap = RWCapability { + device, offset: register, len + }; + + let cap = match ty { + 0x00 => Capability::Reserved, + 0x01 => Capability::PciPowerManagement, + 0x02 => Capability::AcceleratedGraphicsPort, + 0x03 => Capability::VitalProductData, + 0x04 => Capability::SlotIdentification, + 0x05 => Capability::Msi, + 0x06 => Capability::CompactPciHotSwap, + 0x07 => Capability::PciX, + 0x08 => Capability::HyperTransport, + 0x09 => Capability::VendorSpecific(rw_cap), + 0x0A => Capability::DebugPort, + 0x0B => Capability::CompactPciCentralResourceControl, + 0x0C => Capability::PciHotPlug, + 0x0E => Capability::AcceleratedGraphicsPort8x, + 0x0F => Capability::SecureDevice, + 0x10 => Capability::PciExpress, + 0x11 => Capability::MsiX(MsiX { + inner: rw_cap + }), + id => Capability::Unknown(id) + }; + + (cap, next) + } +} From e5bc0bb0c5bf735d549270080f9dac1782b23415 Mon Sep 17 00:00:00 2001 From: roblabla Date: Mon, 27 May 2019 16:07:00 +0000 Subject: [PATCH 08/16] Add virtio service --- Cargo.lock | 41 +++ Cargo.toml | 2 +- Makefile.toml | 64 +++- isofiles/boot/grub/grub.cfg | 1 + libuser/src/error.rs | 22 ++ libuser/src/mem.rs | 11 + virtio/Cargo.toml | 25 ++ virtio/src/main.rs | 575 ++++++++++++++++++++++++++++++++++++ virtio/src/net.rs | 391 ++++++++++++++++++++++++ virtio/src/pci.rs | 345 ++++++++++++++++++++++ virtio/src/virtqueue.rs | 408 +++++++++++++++++++++++++ 11 files changed, 1877 insertions(+), 8 deletions(-) create mode 100644 virtio/Cargo.toml create mode 100755 virtio/src/main.rs create mode 100644 virtio/src/net.rs create mode 100644 virtio/src/pci.rs create mode 100644 virtio/src/virtqueue.rs diff --git a/Cargo.lock b/Cargo.lock index d3d737166..3455dde19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -277,6 +277,14 @@ dependencies = [ "spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "log" version = "0.4.6" @@ -290,6 +298,11 @@ name = "lzw" version = "0.10.0" source = "git+https://github.com/SunriseOS/lzw#b4ca11f83315129ee683aa3d9ca8d6c3a4cde1b4" +[[package]] +name = "managed" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "maplit" version = "1.0.1" @@ -449,6 +462,17 @@ name = "smallvec" version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "smoltcp" +version = "0.5.0" +source = "git+https://github.com/roblabla/smoltcp#21b0b27f8b5d3b5bf56fbd9cc5356d13422529e3" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "managed 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "spin" version = "0.4.10" @@ -629,6 +653,20 @@ dependencies = [ "sunrise-libutils 0.1.0", ] +[[package]] +name = "sunrise-virtio" +version = "0.1.0" +dependencies = [ + "bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitfield 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "smoltcp 0.5.0 (git+https://github.com/roblabla/smoltcp)", + "static_assertions 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "sunrise-libuser 0.1.0", +] + [[package]] name = "swipc-gen" version = "0.1.0" @@ -791,8 +829,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "6281b86796ba5e4366000be6e9e18bf35580adf9e63fbe2294aadb587613a319" "checksum linked_list_allocator 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "47314ec1d29aa869ee7cb5a5be57be9b1055c56567d59c3fb6689926743e0bea" +"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum lzw 0.10.0 (git+https://github.com/SunriseOS/lzw)" = "" +"checksum managed 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fdcec5e97041c7f0f1c5b7d93f12e57293c831c646f4cc7a5db59460c7ea8de6" "checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43" "checksum mashup 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f2d82b34c7fb11bb41719465c060589e291d505ca4735ea30016a91f6fc79c3b" "checksum mashup-impl 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "aa607bfb674b4efb310512527d64266b065de3f894fc52f84efcbf7eaa5965fb" @@ -814,6 +854,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" "checksum sha-1 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9d1f3b5de8a167ab06834a7c883bd197f2191e1dda1a22d9ccfeedbf9aded" "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" +"checksum smoltcp 0.5.0 (git+https://github.com/roblabla/smoltcp)" = "" "checksum spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ceac490aa12c567115b40b7b7fceca03a6c9d53d5defea066123debc83c5dc1f" "checksum spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44363f6f51401c34e7be73db0db371c04705d35efbe9f7d6082e03a921a32c55" "checksum static_assertions 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4f8de36da215253eb5f24020bfaa0646613b48bf7ebe36cdfa37c3b3b33b241" diff --git a/Cargo.toml b/Cargo.toml index 36a272531..f2c848db0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["kernel", "bootstrap", "shell", "libuser", "clock", "sm", "vi", "ahci", "libutils", "libkern", "swipc-gen", "swipc-parser"] +members = ["kernel", "bootstrap", "shell", "libuser", "clock", "sm", "vi", "ahci", "libutils", "libkern", "swipc-gen", "swipc-parser", "virtio"] [profile.release] debug = true diff --git a/Makefile.toml b/Makefile.toml index 9c9580809..73df426cf 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -138,15 +138,30 @@ dependencies = ["install-rust-src"] command = "cargo" args = ["xbuild", "--target=i386-unknown-none-user", "--package=sunrise-ahci", "--release"] +[tasks.virtio] +workspace = false +description = "Compiles sunrise-virtio" +dependencies = ["install-rust-src"] +command = "cargo" +args = ["xbuild", "--target=i386-unknown-none-user", "--package=sunrise-virtio"] + +[tasks.virtio-release] +workspace = false +description = "Compiles sunrise-virtio" +dependencies = ["install-rust-src"] +command = "cargo" +args = ["xbuild", "--target=i386-unknown-none-user", "--package=sunrise-virtio", "--release"] + + [tasks.userspace] workspace = false description = "Compiles userspace apps" -dependencies = ["shell", "clock", "sm", "vi", "ahci"] +dependencies = ["shell", "clock", "sm", "vi", "ahci", "virtio"] [tasks.userspace-release] workspace = false description = "Compiles userspace apps" -dependencies = ["shell-release", "clock-release", "sm-release", "vi-release", "ahci-release"] +dependencies = ["shell-release", "clock-release", "sm-release", "vi-release", "ahci-release", "virtio-release"] [tasks.iso] workspace = false @@ -162,6 +177,7 @@ cp target/i386-unknown-none-user/debug/sunrise-clock isofiles/boot/ cp target/i386-unknown-none-user/debug/sunrise-sm isofiles/boot/ cp target/i386-unknown-none-user/debug/sunrise-vi isofiles/boot/ cp target/i386-unknown-none-user/debug/sunrise-ahci isofiles/boot/ +cp target/i386-unknown-none-user/debug/sunrise-virtio isofiles/boot/ mkisofs-rs external/grub/isofiles isofiles -o os.iso -b boot/grub/i386-pc/eltorito.img --no-emul-boot --boot-info-table --embedded-boot external/grub/embedded.img ''' ] @@ -180,6 +196,7 @@ cp target/i386-unknown-none-user/release/sunrise-clock isofiles/boot/ cp target/i386-unknown-none-user/release/sunrise-sm isofiles/boot/ cp target/i386-unknown-none-user/release/sunrise-vi isofiles/boot/ cp target/i386-unknown-none-user/release/sunrise-ahci isofiles/boot/ +cp target/i386-unknown-none-user/release/sunrise-virtio isofiles/boot/ mkisofs-rs external/grub/isofiles isofiles -o os.iso -b boot/grub/i386-pc/eltorito.img --no-emul-boot --boot-info-table --embedded-boot external/grub/embedded.img ''' ] @@ -213,13 +230,28 @@ description = "Runs the bootable ISO in qemu." dependencies = ["iso-release", "create-disk-if-not-exist"] command = "qemu-system-i386" args = [ + # Boot device "-cdrom", "os.iso", - "-serial", "stdio", + # Redirect serial to STDIO + "-serial", "mon:stdio", + # Redirect graphics to VNC "-vnc", "${VNC_PORT}", + # Don't reboot on triple fault "-no-reboot", - "-enable-kvm", + # Enable hardware acceleration + #"-enable-kvm", + # Setup an AHCI device, with a disk on the first IDE port. "-drive", "id=diskA,file=DISK.img,format=raw,if=none", "-device", "ahci,id=ahci", "-device", "ide-drive,drive=diskA,bus=ahci.0", "-machine", "q35", + # Setup a network device, using virtio-net. + # TODO: iommu_platform=true + "-device", "virtio-net,disable-legacy=on,netdev=u1", "-netdev", "user,id=u1", + # Dump packets going on the vlan + "-object", "filter-dump,id=f1,netdev=u1,file=dump.dat", + # Trace virtio-related events + "--trace", "virtio_set_status,file=trace", "--trace", "virtio_queue_notify,file=trace", "--trace", "virtio_notify,file=trace", "--trace", "virtio_input_queue_full,file=trace", + "--trace", "virtio_net_announce_notify,file=trace", "--trace", "virtio_net_announce_timer,file=trace", "--trace", "virtio_net_handle_announce,file=trace", + "--trace", "virtio_net_post_load_device,file=trace", "--trace", "virtio_notify_irqfd,file=trace", ] [tasks.qemu-debug] @@ -228,14 +260,32 @@ description = "Runs the bootable ISO in qemu with gdb support" dependencies = ["iso", "create-disk-if-not-exist"] command = "qemu-system-i386" args = [ + # Boot device "-cdrom", "os.iso", - "-serial", "stdio", + # Redirect serial to STDIO + "-serial", "mon:stdio", + # Redirect graphics to VNC "-vnc", "${VNC_PORT}", - "-no-reboot", + # Enable hardware acceleration + #"-enable-kvm", + # Setup an AHCI device, with a disk on the first IDE port. + "-drive", "id=diskA,file=DISK.img,format=raw,if=none", "-device", "ahci,id=ahci", "-device", "ide-drive,drive=diskA,bus=ahci.0", + # Setup a network device, using virtio-net. + # TODO: ,iommu_platform=true + "-device", "virtio-net,disable-legacy=on,netdev=u1", "-netdev", "user,id=u1", + # Dump packets going on the vlan + "-object", "filter-dump,id=f1,netdev=u1,file=dump.dat", + + # Enable GDB "-gdb", "tcp::${GDB_PORT}", "-S", + # Print state on CPU reset "-d", "cpu_reset", "-drive", "id=diskA,file=DISK.img,format=raw,if=none", "-device", "ahci,id=ahci", "-device", "ide-drive,drive=diskA,bus=ahci.0", "-machine", "q35", + # Trace virtio-related events + "--trace", "virtio_set_status,file=trace", "--trace", "virtio_queue_notify,file=trace", "--trace", "virtio_notify,file=trace", "--trace", "virtio_input_queue_full,file=trace", + "--trace", "virtio_net_announce_notify,file=trace", "--trace", "virtio_net_announce_timer,file=trace", "--trace", "virtio_net_handle_announce,file=trace", + "--trace", "virtio_net_post_load_device,file=trace", "--trace", "virtio_notify_irqfd,file=trace", ] [tasks.doc] @@ -274,7 +324,7 @@ args = ["-c", "kernel/src/main.rs", "bootstrap/src/main.rs", "shell/src/main.rs", "libuser/src/lib.rs", "clock/src/main.rs", "sm/src/main.rs", "vi/src/main.rs", "ahci/src/main.rs", "libutils/src/lib.rs", "libkern/src/lib.rs", "swipc-gen/src/lib.rs", - "swipc-parser/src/lib.rs" + "swipc-parser/src/lib.rs", "virtio/src/main.rs", ] [tasks.clippy-sunrise-target] diff --git a/isofiles/boot/grub/grub.cfg b/isofiles/boot/grub/grub.cfg index a57ef6d78..207487988 100644 --- a/isofiles/boot/grub/grub.cfg +++ b/isofiles/boot/grub/grub.cfg @@ -26,5 +26,6 @@ menuentry "my os" { module2 /boot/sunrise-sm sm module2 /boot/sunrise-vi vi module2 /boot/sunrise-ahci ahci + module2 /boot/sunrise-virtio virtio boot } diff --git a/libuser/src/error.rs b/libuser/src/error.rs index 5c1a230c6..b7abf1d08 100644 --- a/libuser/src/error.rs +++ b/libuser/src/error.rs @@ -51,6 +51,8 @@ pub enum Error { Libuser(LibuserError, Backtrace), /// Ahci driver error. Ahci(AhciError, Backtrace), + /// Virtio driver error. + Virtio(VirtioError, Backtrace), /// An unknown error type. Either someone returned a custom error, or this /// version of libuser is outdated. Unknown(u32, Backtrace) @@ -68,6 +70,7 @@ impl Error { //Module::Vi => Error::Vi(ViError(description), Backtrace::new()), Module::Libuser => Error::Libuser(LibuserError(description), Backtrace::new()), Module::Ahci => Error::Ahci(AhciError(description), Backtrace::new()), + Module::Virtio => Error::Virtio(VirtioError(description), Backtrace::new()), _ => Error::Unknown(errcode, Backtrace::new()) } } @@ -82,6 +85,7 @@ impl Error { //Error::Vi(err, ..) => err.0 << 9 | Module::Vi.0, Error::Libuser(err, ..) => err.0 << 9 | Module::Libuser.0, Error::Ahci(err, ..) => err.0 << 9 | Module::Ahci.0, + Error::Virtio(err, ..) => err.0 << 9 | Module::Virtio.0, Error::Unknown(err, ..) => err, } } @@ -110,6 +114,7 @@ enum_with_val! { Vi = 114, Libuser = 115, Ahci = 116, + Virtio = 117, } } @@ -192,3 +197,20 @@ impl From for Error { Error::Ahci(error, Backtrace::new()) } } + +enum_with_val! { + /// Virtio errors. + #[derive(PartialEq, Eq, Clone, Copy)] + pub struct VirtioError(u32) { + /// A required PCI Vendor-Specific feature was missing. + MissingRequiredFeature = 0, + /// Failed to negociate features with the virtio device. + FeatureNegociationFailed = 1, + } +} + +impl From for Error { + fn from(error: VirtioError) -> Self { + Error::Virtio(error, Backtrace::new()) + } +} diff --git a/libuser/src/mem.rs b/libuser/src/mem.rs index b9827fe92..cfc700af3 100644 --- a/libuser/src/mem.rs +++ b/libuser/src/mem.rs @@ -64,6 +64,17 @@ pub fn map_mmio(physical_address: usize) -> Result<*mut T, KernelError> { Ok((virt_addr + (physical_address % PAGE_SIZE)) as *mut T) } +/// Maps a range of bytes. +/// +/// This function preserves the offset relative to `PAGE_SIZE`. +pub fn map_mmio_range(physical_address: usize, len: usize) -> Result<*mut u8, KernelError> { + let aligned_phys_addr = align_down(physical_address, PAGE_SIZE); + let full_size = align_up(aligned_phys_addr + len, PAGE_SIZE) - aligned_phys_addr; + let virt_addr = find_free_address(full_size as _, 1).unwrap(); + syscalls::map_mmio_region(aligned_phys_addr as _, full_size as _, virt_addr, true)?; + Ok((virt_addr + (physical_address % PAGE_SIZE)) as *mut u8) +} + /// Gets the physical address of a structure from its virtual address, preserving offset in the page. /// /// # Panics diff --git a/virtio/Cargo.toml b/virtio/Cargo.toml new file mode 100644 index 000000000..4e54fe76b --- /dev/null +++ b/virtio/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "sunrise-virtio" +version = "0.1.0" +authors = ["roblabla "] +edition = "2018" + +[dependencies] +sunrise-libuser = { path = "../libuser" } +log = "0.4" +bit_field = "0.9.0" +bitflags = "1.0.4" +bitfield = "0.13" + +[dependencies.static_assertions] +version = "0.3.1" +features = ["nightly"] + +[dependencies.byteorder] +default-features = false +version = "1.3.1" + +[dependencies.smoltcp] +git = "https://github.com/roblabla/smoltcp" +default-features = false +features = ["alloc", "proto-ipv4", "proto-igmp", "proto-ipv6", "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", "log"] \ No newline at end of file diff --git a/virtio/src/main.rs b/virtio/src/main.rs new file mode 100755 index 000000000..807a65c0b --- /dev/null +++ b/virtio/src/main.rs @@ -0,0 +1,575 @@ +#![no_std] +#![feature(alloc, underscore_const_names)] + +#[macro_use] +extern crate alloc; + +use sunrise_libuser::capabilities; +use sunrise_libuser::syscalls; +use sunrise_libuser::types::ReadableEvent; +use sunrise_libuser::pci::{discover as pci_discover, PciHeader, PciDevice, + GeneralPciHeader, + CONFIG_ADDRESS as PCI_CONFIG_ADDRESS, + CONFIG_DATA as PCI_CONFIG_DATA}; +use sunrise_libuser::pci::capabilities::{MsiXEntry, Capability}; + +use sunrise_libuser::error::{VirtioError, Error}; +use log::*; +use bitflags::bitflags; +use crate::pci::{CommonCfg, NotificationCfg, Config}; +use crate::net::VirtioNet; +use alloc::vec::Vec; +use bitfield::bitfield; +use virtqueue::VirtQueue; +use core::sync::atomic::{fence, Ordering}; + +mod pci; +mod net; +mod virtqueue; + +bitflags! { + /// 2.1: Device Status field + /// + /// The device status field provides a simple low-level indication of the completed steps of the + /// device initialization sequence (specified in chapter 3.1). + struct DeviceStatus: u8 { + /// Indicates that the guest OS has found the device and recognized it as a valid virtio + /// device. + const ACKNOWLEDGE = 1; + /// Indicates that the guest OS knows how to drive the device. + const DRIVER = 2; + /// Indicates that something went wrong with the guest, and it has given up on the device. + /// This could be an internal error, or the driver didn't like the device for some reason, + /// or even a fatal error during device operation. + const FAILED = 128; + /// Indicates that the driver has acknowledged all the features it understands, and feature + /// negociation is complete. + const FEATURES_OK = 8; + /// Indicates that the driver is set up and ready to drive the device. + const DRIVER_OK = 4; + /// Indicates that the device has experienced an error from which it can't recover. + const DEVICE_NEEDS_RESET = 64; + } +} + +bitflags! { + /// 6: Reserved Feature Bits + struct CommonFeatures: u64 { + /// Negotiating this feature indicates that the driver can use + /// descriptors with the VIRTQ_DESC_F_INDIRECT flag set, as described in + /// 2.6.5.3 Indirect Descriptors and 2.7.7 Indirect Flag: Scatter-Gather + /// Support. + const RING_INDIRECT_DESC = 1 << 28; + /// This feature enables the used_event and the avail_event fields as + /// described in 2.6.7, 2.6.8 and 2.7.10. + const RING_EVENT_IDX = 1 << 29; + /// This indicates compliance with this specification (Virtio 1.1), + /// giving a simple way to detect legacy devices or drivers. + const VERSION_1 = 1 << 32; + /// This feature indicates that the device can be used on a platform + /// where device access to data in memory is limited and/or translated. + /// E.g. this is the case if the device can be located behind an IOMMU + /// that translates bus addresses from the device into physical addresses + /// in memory, if the device can be limited to only access certain memory + /// addresses or if special commands such as a cache flush can be needed + /// to synchronise data in memory with the device. Whether accesses are + /// actually limited or translated is described by platform-specific + /// means. If this feature bit is set to 0, then the device has same + /// access to memory addresses supplied to it as the driver has. In + /// particular, the device will always use physical addresses matching + /// addresses used by the driver (typically meaning physical addresses + /// used by the CPU) and not translated further, and can access any + /// address supplied to it by the driver. When clear, this overrides any + /// platform-specific description of whether device access is limited or + /// translated in any way, e.g. whether an IOMMU may be present. + // NOTE: If this flag is not negociated, either the device becomes a + // backdoor, or it becomes unusable... It might be a good idea to find + // out which. + const ACCESS_PLATFORM = 1 << 33; + /// This feature indicates support for the packed virtqueue layout as + /// described in 2.7 Packed Virtqueues. + const RING_PACKED = 1 << 34; + /// This feature indicates that all buffers are used by the device in the + /// same order in which they have been made available. + const IN_ORDER = 1 << 35; + /// This feature indicates that memory accesses by the driver and the + /// device are ordered in a way described by the platform. + /// + /// If this feature bit is negotiated, the ordering in effect for any + /// memory accesses by the driver that need to be ordered in a specific + /// way with respect to accesses by the device is the one suitable for + /// devices described by the platform. This implies that the driver needs + /// to use memory barriers suitable for devices described by the + /// platform; e.g. for the PCI transport in the case of hardware PCI + /// devices. + /// + /// If this feature bit is not negotiated, then the device and driver are + /// assumed to be implemented in software, that is they can be assumed to + /// run on identical CPUs in an SMP configuration. Thus a weaker form of + /// memory barriers is sufficient to yield better performance. + const ORDER_PLATFORM = 1 << 36; + /// This feature indicates that the device supports Single Root I/O + /// Virtualization. Currently only PCI devices support this feature. + const SR_IOV = 1 << 37; + /// This feature indicates that the driver passes extra data (besides + /// identifying the virtqueue) in its device notifications. See 2.7.23 + /// Driver notifications. + const NOTIFICATION_DATA = 1 << 38; + } +} + +bitfield! { + pub struct Notification(u32); + impl Debug; + /// Virtqueue number to be notified. + virtqueue_idx, set_virtqueue_idx: 15, 0; + /// Offset within the ring where the next available ring entry will be + /// written. When VIRTIO_F_RING_PACKED has been negotiated this refers to the + /// offset (in units of descriptor entries) within the descriptor ring where + /// the next available descriptor will be written. + next_off_packed, set_next_off_packed: 30, 16; + /// Wrap Counter. With VIRTIO_F_RING_PACKED this is the wrap counter + /// referring to the next available descriptor. + next_wrap_packed, set_next_wrap_packed: 31; + /// Offset within the ring where the next available ring entry will be + /// written. When VIRTIO_F_RING_PACKED has not been negotiated this refers to + /// the available index. + next_off_split, set_next_off_split: 31, 16; +} + + +#[derive(Debug)] +pub struct VirtioDevice { + virtio_did: u16, + common_features: CommonFeatures, + device: PciDevice, + header: GeneralPciHeader, + common_cfg: CommonCfg, + notif_cfg: NotificationCfg, + device_cfg: Option, + queues: Vec>, + irq_event: ReadableEvent, +} + +impl VirtioDevice { + /// 3.1: Device Initialization + pub fn acknowledge(&mut self) { + self.reset(); + self.common_cfg.set_device_status(DeviceStatus::ACKNOWLEDGE); + + self.queues.clear(); + for i in 0..self.common_cfg.num_queues() { + self.queues.push(None) + } + } + + /// 4.1.4.3: Writing a 0 to device status resets the device. + pub fn reset(&mut self) { + self.common_cfg.set_device_status(DeviceStatus::empty()); + while !self.common_cfg.device_status().is_empty() { + // TODO: Schedule out? + } + } + + /// 4.1.5.1.3 Virtqueue Configuration + pub fn setup_virtqueue(&mut self, virtqueue_idx: u16) { + let mut queue = self.common_cfg.queue(virtqueue_idx); + let size = queue.size; + let virtqueue = VirtQueue::new(size); + queue.desc = virtqueue.descriptor_area_dma_addr(); + queue.driver = virtqueue.driver_area_dma_addr(); + queue.device = virtqueue.device_area_dma_addr(); + let mut entry = MsiXEntry(0); + // TODO: DMAR + entry.set_message_address(0x0FEE_0000); + entry.set_message_data(0x0000_0000); + self.device.set_msix_message_entry(0, entry); + queue.enable = true; + self.common_cfg.set_queue(virtqueue_idx, &queue); + self.queues[virtqueue_idx as usize] = Some(virtqueue); + } + + /// Negociate common features. + pub fn negociate_features(&mut self, supported_features: u64, required_features: u64, preconditions: fn(u64) -> bool) -> Result { + let device_features = self.common_cfg.device_feature_bits(); + + let required_virtio_features = CommonFeatures::VERSION_1 /*| CommonFeatures::ACCESS_PLATFORM*/; + + let required_features = required_virtio_features.bits() | required_features; + + let supported_features = supported_features | required_features; + + let common_features = device_features & supported_features; + + if common_features & required_features != required_features { + info!("Required features not set: {:x}", !common_features & required_features); + self.common_cfg.set_device_status(DeviceStatus::FAILED); + Err(VirtioError::FeatureNegociationFailed.into()) + } else if !preconditions(common_features) { + info!("Preconditions not met for features: {:x}", common_features); + self.common_cfg.set_device_status(DeviceStatus::FAILED); + Err(VirtioError::FeatureNegociationFailed.into()) + } else { + self.common_cfg.set_driver_features(common_features); + self.common_cfg.set_device_status(DeviceStatus::FEATURES_OK); + if self.common_cfg.device_status().contains(DeviceStatus::FEATURES_OK) { + self.common_features = CommonFeatures::from_bits_truncate(common_features); + Ok(common_features) + } else { + info!("Device refused our feature set! {:x}", common_features); + self.common_cfg.set_device_status(DeviceStatus::FAILED); + Err(VirtioError::FeatureNegociationFailed.into()) + } + } + } + + pub fn acquire_device_cfg(&mut self) -> Config { + self.device_cfg.take().unwrap() + } + + pub fn notify(&self, vq: u16) { + if let Some(queue) = self.queues.get(vq as usize).and_then(|v| v.as_ref()) { + // 2.6.13.6: The driver performs a suitable memory barrier to ensure + // that it updates the idx field before checking for notification + // suppression. + fence(Ordering::SeqCst); + info!("{:#?}", queue); + + if !queue.device_notif_suppressed() { + let queue_notify_off = self.common_cfg.queue_notify_off(vq) as usize; + if self.common_features.contains(CommonFeatures::NOTIFICATION_DATA) { + info!("Notifying {}", vq); + let mut notif = Notification(0); + notif.set_virtqueue_idx(vq.into()); + notif.set_next_off_split(queue.get_available_idx().into()); + self.notif_cfg.notify_with_notification(queue_notify_off as usize, notif); + } else { + info!("Notifying {}", vq); + self.notif_cfg.notify_with_virtqueue(queue_notify_off as usize, vq); + } + } else { + info!("Notifications for {} suppressed", vq); + } + } else { + info!("Queue {} does not exist", vq); + } + } + + pub fn queues(&mut self) -> &mut [Option] { + &mut self.queues[..] + } +} + +macro_rules! send_icmp_ping { + ( $repr_type:ident, $packet_type:ident, $ident:expr, $seq_no:expr, + $echo_payload:expr, $socket:expr, $remote_addr:expr ) => {{ + let icmp_repr = $repr_type::EchoRequest { + ident: $ident, + seq_no: $seq_no, + data: &$echo_payload, + }; + + let icmp_payload = $socket + .send(icmp_repr.buffer_len(), $remote_addr) + .unwrap(); + + let mut icmp_packet = $packet_type::new_unchecked(icmp_payload); + (icmp_repr, icmp_packet) + }} +} + +macro_rules! get_icmp_pong { + ( $repr_type:ident, $repr:expr, $payload:expr, $waiting_queue:expr, $remote_addr:expr, + $timestamp:expr, $received:expr ) => {{ + if let $repr_type::EchoReply { seq_no, data, .. } = $repr { + if let Some(_) = $waiting_queue.get(&seq_no) { + let packet_timestamp_ms = NetworkEndian::read_i64(data); + info!("{} bytes from {}: icmp_seq={}, time={}ms", + data.len(), $remote_addr, seq_no, + $timestamp.total_millis() - packet_timestamp_ms); + $waiting_queue.remove(&seq_no); + $received += 1; + } + } + }} +} + +fn ping(device: VirtioNet) { + use smoltcp::time::{Duration, Instant}; + use smoltcp::phy::Device; + //use smoltcp::phy::wait as phy_wait; + use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, + Ipv6Address, Icmpv6Repr, Icmpv6Packet, + Ipv4Address, Icmpv4Repr, Icmpv4Packet}; + use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder, Routes}; + use smoltcp::socket::{SocketSet, IcmpSocket, IcmpSocketBuffer, IcmpPacketMetadata, IcmpEndpoint}; + use byteorder::{NetworkEndian, ByteOrder}; + use alloc::collections::BTreeMap; + + let count = 5; + let interval = Duration::from_millis(50); + let timeout = Duration::from_millis(5000); + let device_caps = device.capabilities(); + + let neighbor_cache = NeighborCache::new(BTreeMap::new()); + + let remote_addr = IpAddress::v4(10, 0, 2, 2); + + let icmp_rx_buffer = IcmpSocketBuffer::new(vec![IcmpPacketMetadata::EMPTY], vec![0; 256]); + let icmp_tx_buffer = IcmpSocketBuffer::new(vec![IcmpPacketMetadata::EMPTY], vec![0; 256]); + let icmp_socket = IcmpSocket::new(icmp_rx_buffer, icmp_tx_buffer); + + let ethernet_addr = EthernetAddress(device.mac()); + let src_ipv4 = IpCidr::new(IpAddress::v4(10, 0, 2, 15), 24); + let ip_addrs = [src_ipv4]; + let default_v4_gw = Ipv4Address::new(10, 0, 2, 2); + let mut routes_storage = [None; 1]; + let mut routes = Routes::new(&mut routes_storage[..]); + routes.add_default_ipv4_route(default_v4_gw).unwrap(); + let mut iface = EthernetInterfaceBuilder::new(device) + .ethernet_addr(ethernet_addr) + .ip_addrs(ip_addrs) + .routes(routes) + .neighbor_cache(neighbor_cache) + .finalize(); + + let mut sockets = SocketSet::new(vec![]); + let icmp_handle = sockets.add(icmp_socket); + + let mut send_at = Instant::from_millis(0); + let mut seq_no = 0; + let mut received = 0; + let mut echo_payload = [0xffu8; 40]; + let mut waiting_queue = BTreeMap::new(); + let ident = 0x22b; + + let mut timestamp = Instant::from_millis(0); + loop { + timestamp += interval; + //info!("{:#?}", iface); + match iface.poll(&mut sockets, timestamp) { + Ok(_) => {}, + Err(e) => { + debug!("poll error: {}", e); + } + } + + { + let timestamp = timestamp + Duration::from_millis(50); + let mut socket = sockets.get::(icmp_handle); + if !socket.is_open() { + socket.bind(IcmpEndpoint::Ident(ident)).unwrap(); + send_at = timestamp; + } + + if socket.can_send() && seq_no < count as u16 && + send_at <= timestamp { + NetworkEndian::write_i64(&mut echo_payload, timestamp.total_millis()); + + match remote_addr { + IpAddress::Ipv4(_) => { + let (icmp_repr, mut icmp_packet) = send_icmp_ping!( + Icmpv4Repr, Icmpv4Packet, ident, seq_no, + echo_payload, socket, remote_addr); + icmp_repr.emit(&mut icmp_packet, &device_caps.checksum); + }, + /*IpAddress::Ipv6(_) => { + let (icmp_repr, mut icmp_packet) = send_icmp_ping!( + Icmpv6Repr, Icmpv6Packet, ident, seq_no, + echo_payload, socket, remote_addr); + icmp_repr.emit(&src_ipv6, &remote_addr, + &mut icmp_packet, &device_caps.checksum); + },*/ + _ => unimplemented!() + } + + waiting_queue.insert(seq_no, timestamp); + seq_no += 1; + send_at += interval; + } + + if socket.can_recv() { + let (payload, _) = socket.recv().unwrap(); + + match remote_addr { + IpAddress::Ipv4(_) => { + let icmp_packet = Icmpv4Packet::new_checked(&payload).unwrap(); + let icmp_repr = + Icmpv4Repr::parse(&icmp_packet, &device_caps.checksum).unwrap(); + get_icmp_pong!(Icmpv4Repr, icmp_repr, payload, + waiting_queue, remote_addr, timestamp, received); + } + /*IpAddress::Ipv6(_) => { + let icmp_packet = Icmpv6Packet::new_checked(&payload).unwrap(); + let icmp_repr = Icmpv6Repr::parse(&remote_addr, &src_ipv6, + &icmp_packet, &device_caps.checksum).unwrap(); + get_icmp_pong!(Icmpv6Repr, icmp_repr, payload, + waiting_queue, remote_addr, timestamp, received); + },*/ + _ => unimplemented!() + } + } + + let mut to_remove = Vec::new(); + + for (seq, from) in &waiting_queue { + if timestamp - *from >= timeout { + info!("From {} icmp_seq={} timeout", remote_addr, seq); + to_remove.push(seq.clone()); + } + } + + for item in to_remove { + waiting_queue.remove(&item); + } + + + if seq_no == count as u16 && waiting_queue.is_empty() { + break + } + } + + let timestamp = timestamp + Duration::from_millis(50); + match iface.poll_at(&sockets, timestamp) { + Some(poll_at) if timestamp < poll_at => { + let resume_at = core::cmp::min(poll_at, send_at); + let ns = (resume_at - timestamp).total_millis() * 0x1_000_000; + syscalls::wait_synchronization(&[iface.device().device.irq_event.0.as_ref()], Some(ns as usize)).unwrap(); + }, + Some(_) => (), + None => { + let ns = (send_at - timestamp).total_millis() * 0x1_000_000; + syscalls::wait_synchronization(&[iface.device().device.irq_event.0.as_ref()], Some(ns as usize)).unwrap(); + } + } + } +} + +fn main() { + debug!("Virtio driver starting up"); + unsafe { + let mapping : *mut [u8; 0x1000] = sunrise_libuser::mem::map_mmio(0xfe003000).unwrap(); + (*mapping)[4] = 1; + } + + let virtio_devices = pci_discover() + .filter(|device| device.vid() == 0x1AF4 && 0x1000 <= device.did() && device.did() <= 0x107F) + ; + + let mut devices = Vec::new(); + + for device in virtio_devices { + let header = match device.header() { + PciHeader::GeneralDevice(header) => header, + _ => { + info!("Unsupported device"); + continue; + } + }; + + let virtio_did = if device.did() < 0x1040 { + // Transitional device: use PCI subsystem id + header.subsystem_id() + } else { + device.did() - 0x1040 + }; + + let mut common_cfg = None; + let mut device_cfg = None; + let mut notify_cfg = None; + for capability in device.capabilities() { + match capability { + Capability::VendorSpecific(data) => { + if let Ok(Some(cap)) = pci::Cap::read(header.bars(), &data) { + info!("{:?}", cap); + match cap { + pci::Cap::CommonCfg(cfg) => common_cfg = Some(cfg), + pci::Cap::DeviceCfg(cfg) => device_cfg = Some(cfg), + pci::Cap::NotifyCfg(cfg) => notify_cfg = Some(cfg), + cap => (), + } + } else { + info!("Unsupported virtio cap {:#?}", &data); + } + }, + cap => info!("Capability = {:#?}", cap) + } + } + + match (common_cfg, device_cfg, notify_cfg) { + (Some(common_cfg), Some(device_cfg), Some(notif_cfg)) => + devices.push(VirtioDevice { + virtio_did, device, header, common_cfg, device_cfg: Some(device_cfg), + common_features: CommonFeatures::empty(), notif_cfg, queues: Vec::new(), + irq_event: syscalls::create_interrupt_event(header.interrupt_line() as usize, 0).unwrap() + }), + _ => () + } + } + + for device in devices.iter_mut() { + info!("Pinging on device {:#x?}", device); + device.acknowledge(); + } + + for device in devices { + match device.virtio_did { + 1 => { + info!("Creating device"); + let mut device = net::VirtioNet::new(device); + info!("Initializing"); + device.init().unwrap(); + + info!("Pinging on device {:#x?}", device); + ping(device); + }, + id => info!("Unsupported did {}", id) + } + } + + // event loop + /*let man = WaitableManager::new(); + let handler = Box::new(PortHandler::::new("virtio:\0").unwrap()); + man.add_waitable(handler as Box); + man.run();*/ +} + +capabilities!(CAPABILITIES = Capabilities { + svcs: [ + sunrise_libuser::syscalls::nr::SleepThread, + sunrise_libuser::syscalls::nr::ExitProcess, + sunrise_libuser::syscalls::nr::CloseHandle, + sunrise_libuser::syscalls::nr::WaitSynchronization, + sunrise_libuser::syscalls::nr::OutputDebugString, + + sunrise_libuser::syscalls::nr::SetHeapSize, + sunrise_libuser::syscalls::nr::QueryMemory, + sunrise_libuser::syscalls::nr::MapSharedMemory, + sunrise_libuser::syscalls::nr::UnmapSharedMemory, + sunrise_libuser::syscalls::nr::ConnectToNamedPort, + sunrise_libuser::syscalls::nr::CreateInterruptEvent, + sunrise_libuser::syscalls::nr::QueryPhysicalAddress, + sunrise_libuser::syscalls::nr::MapMmioRegion, + sunrise_libuser::syscalls::nr::SendSyncRequestWithUserBuffer, + sunrise_libuser::syscalls::nr::ReplyAndReceiveWithUserBuffer, + sunrise_libuser::syscalls::nr::AcceptSession, + sunrise_libuser::syscalls::nr::CreateSession, + ], + raw_caps: [ + // todo: IRQ capabilities at runtime + // body: Currently IRQ capabilities are declared at compile-time. + // body: + // body: However, for PCI, the IRQ line we want to subscribe to + // body: can only be determined at runtime by reading the `Interrupt Line` register + // body: that has been set-up during POST. + // body: + // body: What would be the proper way to handle such a case ? + // body: + // body: - Declaring every IRQ line in our capabilities, but only effectively using one ? + // body: - Deporting the PIC management to a userspace module, and allow it to accept + // body: dynamic irq capabilities in yet undefined way. + sunrise_libuser::caps::ioport(PCI_CONFIG_ADDRESS + 0), sunrise_libuser::caps::ioport(PCI_CONFIG_ADDRESS + 1), sunrise_libuser::caps::ioport(PCI_CONFIG_ADDRESS + 2), sunrise_libuser::caps::ioport(PCI_CONFIG_ADDRESS + 3), + sunrise_libuser::caps::ioport(PCI_CONFIG_DATA + 0), sunrise_libuser::caps::ioport(PCI_CONFIG_DATA + 1), sunrise_libuser::caps::ioport(PCI_CONFIG_DATA + 2), sunrise_libuser::caps::ioport(PCI_CONFIG_DATA + 3), + sunrise_libuser::caps::irq_pair(0xb, 0x3FF) + ] +}); diff --git a/virtio/src/net.rs b/virtio/src/net.rs new file mode 100644 index 000000000..ee3c72b65 --- /dev/null +++ b/virtio/src/net.rs @@ -0,0 +1,391 @@ +//! Network Device + +use crate::VirtioDevice; +use byteorder::{LE, ByteOrder}; +use crate::pci::Config; +use crate::virtqueue::VirtQueue; +use crate::{DeviceStatus, CommonFeatures}; +use bitflags::bitflags; +use alloc::vec::Vec; +use smoltcp::phy::{Device, Checksum, DeviceCapabilities, ChecksumCapabilities, RxToken, TxToken}; +use smoltcp::time::Instant; +use core::fmt; +use sunrise_libuser::error::Error; +use bit_field::BitField; +use log::info; + +bitflags! { + /// Features supported by the Virtio-Net driver. + struct Features: u64 { + /// Device handles packets with partial checksum. This “checksum offload” + /// is a common feature on modern network cards. + const CSUM = 1 << 0; + /// Driver handles packets with partial checksum. + const GUEST_CSUM = 1 << 1; + /// Control channel offloads reconfiguration support. + const CTRL_GUEST_OFFLOADS = 1 << 2; + /// Device maximum MTU reporting is supported. If offered by the device, + /// device advises driver about the value of its maximum MTU. If + /// negotiated, the driver uses mtu as the maximum MTU value. + const MTU = 1 << 3; + + /// Device has given MAC address. + const MAC = 1 << 5; + + /// Driver can receive TSOv4. + const GUEST_TSO4 = 1 << 7; + /// Driver can receive TSOv6. + const GUEST_TSO6 = 1 << 8; + /// Driver can receive TSO with ECN. + const GUEST_ECN = 1 << 9; + /// Driver can receive UFO. + const GUEST_UFO = 1 << 10; + /// Device can receive TSOv4. + const HOST_TSO4 = 1 << 11; + /// Device can receive TSOv6. + const HOST_TSO6 = 1 << 12; + /// Device can receive TSO with ECN. + const HOST_ECN = 1 << 13; + /// Device can receive UFO. + const HOST_UFO = 1 << 14; + /// Driver can merge receive buffers. + const MRG_RXBUF = 1 << 15; + /// Configuration status field is available. + const STATUS = 1 << 16; + /// Control channel is available. + const CTRL_VQ = 1 << 17; + /// Control channel RX mode support. + const CTRL_RX = 1 << 18; + /// Control channel VLAN filtering. + const CTRL_VLAN = 1 << 19; + + /// Driver can send gratuitous packets. + const GUEST_ANNOUNCE = 1 << 21; + /// Device supports multiqueue with automatic receive steering. + const MQ = 1 << 22; + /// Set MAC address through control channel. + const CTRL_MAC_ADDR = 1 << 23; + + /// Device can process duplicated ACKs and report number of coalesced + /// segments and duplicated ACKs. + const RSC_EXT = 1 << 61; + /// Device may act as a standby for a primary device with the same MAC + /// address. + const STANDBY = 1 << 62; + } +} + +pub struct NetConfiguration { + config: Config +} + +impl fmt::Debug for NetConfiguration { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("NetConfiguration") + .field("mac", &self.mac()) + .field("status", &self.status()) + .field("max_virtqueue_pairs", &self.max_virtqueue_pairs()) + .field("mtu", &self.mtu()) + .finish() + } +} + +impl NetConfiguration { + pub fn mac(&self) -> [u8; 6] { + let mac0 = self.config.read_u8(0); + let mac1 = self.config.read_u8(1); + let mac2 = self.config.read_u8(2); + let mac3 = self.config.read_u8(3); + let mac4 = self.config.read_u8(4); + let mac5 = self.config.read_u8(5); + [mac0, mac1, mac2, mac3, mac4, mac5] + } + + pub fn status(&self) -> u16 { + self.config.read_u16::(6) + } + + pub fn max_virtqueue_pairs(&self) -> u16 { + self.config.read_u16::(8) + } + + pub fn mtu(&self) -> u16 { + self.config.read_u16::(10) + } +} + +/// Ensure the feature bit requirements of section 5.1.3.1 are met. +fn ensure_requirements_met(bits: u64) -> bool { + let bits = Features::from_bits_truncate(bits); + if bits.intersects(Features::GUEST_TSO4 | Features::GUEST_TSO6 | Features::GUEST_UFO) && !bits.contains(Features::GUEST_CSUM) { + return false; + } + + if bits.intersects(Features::GUEST_ECN) && !bits.intersects(Features::GUEST_TSO4 | Features::GUEST_TSO6) { + return false + } + + if bits.intersects(Features::HOST_TSO4 | Features::HOST_TSO6 | Features::HOST_UFO) && !bits.contains(Features::CSUM) { + return false; + } + + if bits.intersects(Features::HOST_ECN | Features::RSC_EXT) && !bits.intersects(Features::HOST_TSO4 | Features::HOST_TSO6) { + return false + } + + if bits.intersects(Features::CTRL_RX | Features::CTRL_VLAN | Features::GUEST_ANNOUNCE | Features::MQ | Features::CTRL_MAC_ADDR) && !bits.contains(Features::CTRL_VQ) { + return false; + } + + return true; +} + +#[derive(Debug)] +pub struct VirtioNet { + // TODO: Tmp + pub device: VirtioDevice, + net_config: NetConfiguration, + common_features: Features, +} + +impl VirtioNet { + pub fn new(mut device: VirtioDevice) -> VirtioNet { + let net_config = NetConfiguration { + config: device.acquire_device_cfg() + }; + VirtioNet { + device, net_config, common_features: Features::empty() + } + } + + /// 5.1.5: Device Initialization + pub fn init(&mut self) -> Result<(), Error> { + // TODO: Is it OK to assume device is ack'd in VirtioNet::init? + // 3.1.1 Driver Requirements: Device Initialization + self.device.common_cfg.set_device_status(DeviceStatus::DRIVER); + + // Negociate features + + // Minimum features that we **should** negociate, as part of 5.1.4.2 + let wanted_features = Features::MAC | Features::MTU; + + // Additional features that would be nice to have + let wanted_features = Features::CSUM | wanted_features; + + let common_features = self.device.negociate_features(wanted_features.bits(), 0, ensure_requirements_met)?; + self.common_features = Features::from_bits_truncate(common_features); + + // 4.1.5.1: PCI-specific initialization + // TODO: MSI-X Vector Configuration. + // Virtqueue Configuration + + // 5.1.5: Device Initialization + // Identify and initialize the receive and transmission virtqueues. + // TODO: Support VIRTIO_NET_F_MQ + info!("Setup virtqueues"); + for virtqueue_idx in 0..2 { + self.device.setup_virtqueue(virtqueue_idx); + } + + if self.common_features.contains(Features::CTRL_VQ) { + // TODO: Setup ctrl_vq + } + + info!("Push a ton of buffers"); + // Fill the receive queues buffer: See 5.1.6.3 + for queue in self.receive_queues() { + for item in 0..queue.len() { + //info!("Pushing buffer for item {}/{}", item, queue.len()); + //if item == 63 { + // info!("{:#?}", queue); + //} + queue.push_buffer_w(Vec::with_capacity(65560)) + } + } + + // Even with VIRTIO_NET_F_MQ, only receiveq1, transmitq1 and controlq are + // used by default. The driver would send the VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET + // command specifying the number of the transmit and receive queues to use. + + // If the VIRTIO_NET_F_MAC feature bit is set, the configuration space mac + // entry indicates the “physical” address of the network card, otherwise + // the driver would typically generate a random local MAC address. + info!("Get mac address"); + let mac = if self.common_features.contains(Features::MAC) { + self.net_config.mac() + } else { + // TODO: Generate random mac + [0, 1, 2, 3, 4, 5] + }; + + info!("We're good to go!"); + self.device.common_cfg.set_device_status(DeviceStatus::DRIVER_OK); + + Ok(()) + } + + fn receive_queues(&mut self) -> impl Iterator { + let num_queues = if self.common_features.contains(Features::MQ) { + self.net_config.max_virtqueue_pairs() as usize + } else { + 1 + }; + self.device.queues.iter_mut().step_by(2).take(num_queues).filter_map(|v| v.as_mut()) + } + + fn transmit_queues(&mut self) -> impl Iterator { + let num_queues = if self.common_features.contains(Features::MQ) { + self.net_config.max_virtqueue_pairs() as usize + } else { + 1 + }; + self.device.queues.iter_mut().skip(1).step_by(2).take(num_queues).filter_map(|v| v.as_mut()) + } + + fn control_queue(&mut self) -> Option<&mut VirtQueue> { + if self.common_features.contains(Features::CTRL_VQ) { + let num_queues = if self.common_features.contains(Features::MQ) { + self.net_config.max_virtqueue_pairs() as usize + } else { + 1 + }; + self.device.queues[num_queues * 2].as_mut() + } else { + None + } + } + + pub fn link_status(&self) -> bool { + if self.common_features.contains(Features::STATUS) { + self.net_config.status().get_bit(0) + } else { + true + } + } + + pub fn mac(&self) -> [u8; 6] { + if self.common_features.contains(Features::MAC) { + self.net_config.mac() + } else { + // TODO: Generate random mac + [0, 1, 2, 3, 4, 5] + } + } +} + +impl<'a> Device<'a> for VirtioNet { + type RxToken = VirtioNetRxToken; + type TxToken = VirtioNetTxToken<'a>; + + + fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> { + let buf = self.receive_queues().nth(0).unwrap().pop_buffer_w()?; + let rx = VirtioNetRxToken(buf); + let tx = VirtioNetTxToken(self); + Some((rx, tx)) + } + + fn transmit(&'a mut self) -> Option { + Some(VirtioNetTxToken(self)) + } + + fn capabilities(&self) -> DeviceCapabilities { + let mut device_caps = DeviceCapabilities::default(); + if self.common_features.contains(Features::MTU) { + device_caps.max_transmission_unit = self.net_config.mtu() as usize; + } else { + device_caps.max_transmission_unit = 65535; + } + if self.common_features.contains(Features::CSUM) { + device_caps.checksum.udp = Checksum::None; + device_caps.checksum.tcp = Checksum::None; + } + device_caps + } +} + +pub struct VirtioNetRxToken(Vec); + +impl RxToken for VirtioNetRxToken { + fn consume(self, timestamp: Instant, f: F) -> smoltcp::Result + where + F: FnOnce(&[u8]) -> smoltcp::Result + { + f(&self.0[..]) + } +} + +pub struct VirtioNetTxToken<'a>(&'a mut VirtioNet); + +impl<'a> TxToken for VirtioNetTxToken<'a> { + fn consume(self, timestamp: Instant, len: usize, f: F) -> smoltcp::Result + where + F: FnOnce(&mut [u8]) -> smoltcp::Result + { + // TODO: Instead of allocating a new vec, use scatter-gather IO. + + let mut v = Vec::new(); + v.resize(len + core::mem::size_of::(), 0); + let res = f(&mut v[core::mem::size_of::()..]); + + v[0] = 0;/*NetHdrFlags::NEEDS_CSUM.bits()*/; + v[1] = 0; + LE::write_u16(&mut v[2..], 0); + LE::write_u16(&mut v[4..], 0); + LE::write_u16(&mut v[6..], 0); + LE::write_u16(&mut v[8..], 0); + LE::write_u16(&mut v[10..], 0); + + self.0.transmit_queues().nth(0).unwrap().push_buffer_r(v); + self.0.device.notify(1); + res + } +} + +bitflags! { + struct NetHdrFlags: u8 { + const NEEDS_CSUM = 1 << 0; + const DATA_VALID = 1 << 1; + const RSC_INFO = 1 << 2; + } +} + +bitflags! { + struct GsoType: u8 { + const None = 0; + const TCPv4 = 1; + const UDP = 3; + const TCPv6 = 4; + + const ECN = 0x80; + } +} + +#[repr(C)] +struct NetHdr { + flags: NetHdrFlags, + gso_type: GsoType, + hdr_len: u16, + gso_size: u16, + csum_start: u16, + csum_offset: u16, + num_buffers: u16, +} + +impl NetHdr { + fn new_transmission(needs_csum: bool, csum_start: u16, csum_offset: u16) -> NetHdr { + let mut flags = NetHdrFlags::empty(); + if needs_csum { + flags |= NetHdrFlags::NEEDS_CSUM; + } + NetHdr { + flags: flags, + gso_type: GsoType::None, + hdr_len: 0, + gso_size: 0, + csum_start: csum_start.to_le(), + csum_offset: csum_offset.to_le(), + num_buffers: 0 + } + } +} diff --git a/virtio/src/pci.rs b/virtio/src/pci.rs new file mode 100644 index 000000000..9d6108b9e --- /dev/null +++ b/virtio/src/pci.rs @@ -0,0 +1,345 @@ +use sunrise_libuser::pci::{BAR, MappedBAR}; +use byteorder::{LE, NativeEndian, ByteOrder}; +use bit_field::BitField; +use sunrise_libuser::error::KernelError; +use sunrise_libuser::pci::capabilities::RWCapability; +use crate::{DeviceStatus, Notification}; + +#[derive(Debug)] +pub enum Cap { + /// Common Configuration. + CommonCfg(CommonCfg), + /// Notifications. + NotifyCfg(NotificationCfg), + /// ISR Status. + IsrCfg(u8, u64, u64), + /// Device-specific configuration. + DeviceCfg(Config), + /// PCI configuration access. + PciCfg(u8, u64, u64), +} + +impl Cap { + pub fn read(bars: &[Option; 6], data: &RWCapability) -> Result, KernelError> { + let bar = data.read_u32(4).get_bits(0..8) as u8; + if bar > 5 { + return Ok(None) + } + + let offset = data.read_u32(8) as u64; + let length = data.read_u32(12) as u64; + + match data.read_u32(0).get_bits(24..32) { + 1 => Ok(Some(Cap::CommonCfg(CommonCfg { config: Config { + bar: bars[bar as usize].unwrap().map()?, + offset, length + }}))), + 2 => Ok(Some(Cap::NotifyCfg(NotificationCfg { config: Config { + bar: bars[bar as usize].unwrap().map()?, + offset, length + }, notify_off_multiplier: data.read_u32(16)}))), + 3 => Ok(Some(Cap::IsrCfg(bar, offset, length))), + 4 => Ok(Some(Cap::DeviceCfg(Config { + bar: bars[bar as usize].unwrap().map()?, + offset, length + }))), + 5 => Ok(Some(Cap::PciCfg(bar, offset, length))), + _ => Ok(None), + } + } +} + +const DEVICE_FEATURES_SELECT_OFF: u64 = 0; +const DEVICE_FEATURES_OFF: u64 = 4; +const DRIVER_FEATURES_SELECT_OFF: u64 = 8; +const DRIVER_FEATURES_OFF: u64 = 12; +const MSIX_CONFIG_OFF: u64 = 16; +const NUM_QUEUES_OFF: u64 = 18; +const DEVICE_STATUS_OFF: u64 = 20; +const CONFIG_GENERATION_OFF: u64 = 21; +const QUEUE_SELECT_OFF: u64 = 22; +const QUEUE_SIZE_OFF: u64 = 24; +const QUEUE_MSIX_VECTOR_OFF: u64 = 26; +const QUEUE_ENABLE_OFF: u64 = 28; +const QUEUE_NOTIFY_OFF_OFF: u64 = 30; +const QUEUE_DESC_OFF: u64 = 32; +const QUEUE_DRIVER_OFF: u64 = 40; +const QUEUE_DEVICE_OFF: u64 = 48; + +#[derive(Debug, Default)] +pub struct Queue { + pub size: u16, + pub msix_vector: u16, + pub enable: bool, + notify_off: u16, + pub desc: u64, + pub driver: u64, + pub device: u64, +} + +#[derive(Clone)] +pub struct QueueIter<'a>(&'a CommonCfg, u16); + +impl<'a> Iterator for QueueIter<'a> { + type Item = Queue; + fn next(&mut self) -> Option { + if self.1 < self.0.num_queues() { + let ret = Some(self.0.queue(self.1)); + self.1 += 1; + ret + } else { + None + } + } +} + +impl<'a> core::fmt::Debug for QueueIter<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} + +#[derive(Debug)] +pub struct Config { + bar: MappedBAR, + offset: u64, + length: u64, +} + +impl Config { + pub fn read_u8(&self, additional_offset: u64) -> u8 { + assert!(additional_offset < self.length, "OOB Read: {} < {}", additional_offset, self.length); + self.bar.read_u8(self.offset + additional_offset) + } + + pub fn write_u8(&self, additional_offset: u64, data: u8) { + assert!(additional_offset < self.length, "OOB Write: {} < {}", additional_offset, self.length); + self.bar.write_u8(self.offset + additional_offset, data); + } + pub fn read_u16(&self, additional_offset: u64) -> u16 { + assert!(additional_offset.saturating_add(1) < self.length, "OOB Read: {} + 1 < {}", additional_offset, self.length); + self.bar.read_u16::(self.offset + additional_offset) + } + + pub fn write_u16(&self, additional_offset: u64, data: u16) { + assert!(additional_offset.saturating_add(1) < self.length, "OOB Write: {} + 1 < {}", additional_offset, self.length); + self.bar.write_u16::(self.offset + additional_offset, data); + } + + pub fn read_u32(&self, additional_offset: u64) -> u32 { + assert!(additional_offset.saturating_add(3) < self.length, "OOB Read: {} + 3 < {}", additional_offset, self.length); + self.bar.read_u32::(self.offset + additional_offset) + } + + pub fn write_u32(&self, additional_offset: u64, data: u32) { + assert!(additional_offset.saturating_add(3) < self.length, "OOB Write: {} + 3 < {}", additional_offset, self.length); + self.bar.write_u32::(self.offset + additional_offset, data); + } + + pub fn read_u64(&self, additional_offset: u64) -> u64 { + assert!(additional_offset.saturating_add(7) < self.length, "OOB Read: {} + 7 < {}", additional_offset, self.length); + let lo = self.bar.read_u32::(self.offset + additional_offset); + let hi = self.bar.read_u32::(self.offset + additional_offset + 4); + let mut bytes = [0u8; 8]; + NativeEndian::write_u32(&mut bytes, lo); + NativeEndian::write_u32(&mut bytes[4..], hi); + BO::read_u64(&bytes) + } + + pub fn write_u64(&self, additional_offset: u64, data: u64) { + assert!(additional_offset.saturating_add(7) < self.length, "OOB Write: {} + 7 < {}", additional_offset, self.length); + let mut bytes = [0; 8]; + BO::write_u64(&mut bytes, data); + let lo = NativeEndian::read_u32(&bytes); + let hi = NativeEndian::read_u32(&bytes[4..]); + self.bar.write_u32::(self.offset + additional_offset, lo); + self.bar.write_u32::(self.offset + additional_offset + 4, hi); + } +} + +pub struct CommonCfg { + config: Config, +} + +impl core::fmt::Debug for CommonCfg { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("CommonCfg") + .field("device_feature_bits", &self.device_feature_bits()) + .field("driver_feature_bits", &self.driver_feature_bits()) + .field("msix_config", &self.msix_config()) + .field("device_status", &self.device_status()) + .field("config_generation", &self.config_generation()) + .field("queues", &self.queues()) + .finish() + } +} + +impl CommonCfg { + pub fn device_feature_bits(&self) -> u64 { + self.config.write_u32::(DEVICE_FEATURES_SELECT_OFF, 0); + let datalo = self.config.read_u32::(DEVICE_FEATURES_OFF); + self.config.write_u32::(DEVICE_FEATURES_SELECT_OFF, 1); + let datahi = self.config.read_u32::(DEVICE_FEATURES_OFF); + + *0u64 + .set_bits(0..32, datalo as u64) + .set_bits(32..64, datahi as u64) + } + + pub fn driver_feature_bits(&self) -> u64 { + self.config.write_u32::(DRIVER_FEATURES_SELECT_OFF, 0); + let datalo = self.config.read_u32::(DRIVER_FEATURES_OFF); + self.config.write_u32::(DRIVER_FEATURES_SELECT_OFF, 1); + let datahi = self.config.read_u32::(DRIVER_FEATURES_OFF); + + *0u64 + .set_bits(0..32, datalo as u64) + .set_bits(32..64, datahi as u64) + } + + pub fn set_driver_features(&mut self, features: u64) { + self.config.write_u32::(DRIVER_FEATURES_SELECT_OFF, 0); + self.config.write_u32::(DRIVER_FEATURES_OFF, features.get_bits(0..32) as u32); + self.config.write_u32::(DRIVER_FEATURES_SELECT_OFF, 1); + self.config.write_u32::(DRIVER_FEATURES_OFF, features.get_bits(32..64) as u32); + } + + pub fn msix_config(&self) -> u16 { + self.config.read_u16::(MSIX_CONFIG_OFF) + } + + pub fn set_msix_config(&mut self, config: u16) { + self.config.write_u16::(MSIX_CONFIG_OFF, config); + } + + pub fn num_queues(&self) -> u16 { + self.config.read_u16::(NUM_QUEUES_OFF) + } + + pub fn device_status(&self) -> DeviceStatus { + // Maybe use from_bits_truncate? The device isn't supposed to change that field though, so + // this should never fail. On the other hand... do I want to trust the device? Maybe I should + // return None, and let the driver put the device in FAILED status. + DeviceStatus::from_bits(self.config.read_u8(DEVICE_STATUS_OFF)).unwrap() + } + + /// Sets the new device status bits. The old bits are or'd with the new ones (unless 0 is sent, + /// which is used to reset the device). + pub fn set_device_status(&mut self, newflags: DeviceStatus) { + if newflags.is_empty() { + self.config.write_u8(DEVICE_STATUS_OFF, 0); + } + let oldflags = self.device_status(); + self.config.write_u8(DEVICE_STATUS_OFF, (oldflags | newflags).bits()); + } + + pub fn config_generation(&self) -> u8 { + self.config.read_u8(CONFIG_GENERATION_OFF) + } + + fn queues(&self) -> impl Iterator + core::fmt::Debug + '_ { + QueueIter(self, 0) + } + + pub fn queue(&self, queue_idx: u16) -> Queue { + self.config.write_u16::(QUEUE_SELECT_OFF, queue_idx); + Queue { + size: self.config.read_u16::(QUEUE_SIZE_OFF), + msix_vector: self.config.read_u16::(QUEUE_MSIX_VECTOR_OFF), + enable: self.config.read_u16::(QUEUE_ENABLE_OFF) != 0, + notify_off: self.config.read_u16::(QUEUE_NOTIFY_OFF_OFF), + desc: self.config.read_u64::(QUEUE_DESC_OFF), + driver: self.config.read_u64::(QUEUE_DRIVER_OFF), + device: self.config.read_u64::(QUEUE_DEVICE_OFF) + } + } + + pub fn queue_size(&self, queue: u16) -> u16 { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.read_u16::(QUEUE_SIZE_OFF) + } + + pub fn set_queue_size(&self, queue: u16, size: u16) { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.write_u16::(QUEUE_SIZE_OFF, size); + } + + pub fn queue_msix_vector(&self, queue: u16) -> u16 { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.read_u16::(QUEUE_MSIX_VECTOR_OFF) + } + + pub fn set_queue_msix_vector(&mut self, queue: u16, vector: u16) { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.write_u16::(QUEUE_MSIX_VECTOR_OFF, vector); + } + + pub fn queue_enabled(&self, queue: u16) -> bool { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.read_u16::(QUEUE_ENABLE_OFF) != 0 + } + + pub fn set_queue_enabled(&mut self, queue: u16, enabled: bool) { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.write_u16::(QUEUE_ENABLE_OFF, enabled as u16); + } + + pub fn queue_notify_off(&self, queue: u16) -> u16 { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.read_u16::(QUEUE_NOTIFY_OFF_OFF) + } + + pub fn queue_desc(&self, queue: u16) -> u64 { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.read_u64::(QUEUE_DESC_OFF) + } + pub fn set_queue_desc(&mut self, queue: u16, off: u64) { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.write_u64::(QUEUE_DESC_OFF, off); + + } + pub fn queue_driver(&self, queue: u16) -> u64 { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.read_u64::(QUEUE_DRIVER_OFF) + } + pub fn set_queue_driver(&mut self, queue: u16, off: u64) { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.write_u64::(QUEUE_DRIVER_OFF, off); + + } + pub fn queue_device(&self, queue: u16) -> u64 { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.read_u64::(QUEUE_DEVICE_OFF) + } + pub fn set_queue_device(&mut self, queue: u16, off: u64) { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.write_u64::(QUEUE_DEVICE_OFF, off); + } + + pub fn set_queue(&mut self, queue_idx: u16, queue: &Queue) { + self.config.write_u16::(QUEUE_SELECT_OFF, queue_idx); + self.config.write_u16::(QUEUE_SIZE_OFF, queue.size); + self.config.write_u16::(QUEUE_MSIX_VECTOR_OFF, queue.msix_vector); + self.config.write_u64::(QUEUE_DESC_OFF, queue.desc); + self.config.write_u64::(QUEUE_DRIVER_OFF, queue.driver); + self.config.write_u64::(QUEUE_DEVICE_OFF, queue.device); + // Write enable last. + self.config.write_u16::(QUEUE_ENABLE_OFF, queue.enable as u16); + } +} + +#[derive(Debug)] +pub struct NotificationCfg { + config: Config, + notify_off_multiplier: u32 +} + +impl NotificationCfg { + pub fn notify_with_notification(&self, queue_notify_off: usize, notification: Notification) { + self.config.write_u32::(queue_notify_off as u64 * self.notify_off_multiplier as u64, notification.0.to_le()) + } + pub fn notify_with_virtqueue(&self, queue_notify_off: usize, virtqueue_idx: u16) { + log::info!("Notifying at {:#010x}, {:#010x?}", queue_notify_off as u64 * self.notify_off_multiplier as u64 + self.config.offset, self.config.bar); + self.config.write_u16::(queue_notify_off as u64 * self.notify_off_multiplier as u64, virtqueue_idx.to_le()) + } +} diff --git a/virtio/src/virtqueue.rs b/virtio/src/virtqueue.rs new file mode 100644 index 000000000..776738df9 --- /dev/null +++ b/virtio/src/virtqueue.rs @@ -0,0 +1,408 @@ +use bitflags::bitflags; +use static_assertions::assert_eq_size; +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::sync::atomic::{fence, Ordering}; +use core::fmt; +use sunrise_libuser::mem::virt_to_phys; +use log::info; + +#[repr(C)] +struct Ptr { + ptr: usize, + len: usize, +} + +pub struct VirtQueue { + free_head: usize, + last_used: u16, + /// An array of queue_size descriptors + descriptor_area: Box<[Descriptor]>, + /// An array matching descriptor_area, which contains the Virtual Address of + /// the associated descriptor. + virt_area: Box<[usize]>, + /// Contains an array of queue_size + driver_area: Box, + /// Contains an array of queue_size + device_area: Box, +} + +impl fmt::Debug for VirtQueue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + + struct DriverRingIterator<'a>(&'a [u16], &'a [Descriptor], u16); + impl<'a> fmt::Debug for DriverRingIterator<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_list().entries(self.0.iter().map(|v| { + (v, self.1.get(*v as usize)) + }).take(self.2 as usize)).finish() + } + } + + struct DeviceRingIterator<'a>(&'a [UsedElem], &'a [Descriptor], u16, u16); + impl<'a> fmt::Debug for DeviceRingIterator<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_list().entries(self.0.iter().map(|v| { + (v.id, v.len, self.1.get(v.id as usize)) + }).skip(self.2 as usize).take(self.3 as usize)).finish() + } + } + + f.debug_struct("VirtQueue") + .field("free_head", &self.free_head) + .field("last_used", &self.last_used) + .field("driver_area", &self.driver_area) + .field("driver_area_ring", &DriverRingIterator(self.driver_area.ring(), &*self.descriptor_area, self.driver_area.idx())) + .field("device_area", &self.device_area) + .field("device_area_ring", &DeviceRingIterator(self.device_area.ring(), &*self.descriptor_area, self.last_used, self.device_area.idx())) + .finish() + } +} + +impl VirtQueue { + pub fn new(queue_size: u16) -> VirtQueue { + let mut descriptor_area = vec![Descriptor { + addr: 0, + len: 0, + flags: 0, + next: 0, + }; queue_size as usize].into_boxed_slice(); + + for (idx, item) in descriptor_area.iter_mut().enumerate() { + item.next = (idx as u16 + 1).to_le(); + } + + let driver_area = Avail::new(queue_size); + let device_area = Used::new(queue_size); + + VirtQueue { + free_head: 0, + last_used: 0, + virt_area: vec![0; queue_size as usize].into_boxed_slice(), + descriptor_area, + driver_area, + device_area + } + } + + pub fn len(&self) -> u16 { + self.descriptor_area.len() as u16 + } + + pub fn descriptor_area_dma_addr(&self) -> u64 { + let vaddr = &*self.descriptor_area as *const _ as *const u8; + let paddr = virt_to_phys(vaddr) as u64; + info!("Getting descriptor DMA ADDR for {:p} => {:#010x}", vaddr, paddr); + paddr + } + + pub fn device_area_dma_addr(&self) -> u64 { + let vaddr = &*self.device_area as *const _ as *const u8; + let paddr = virt_to_phys(vaddr) as u64; + info!("Getting device DMA ADDR for {:p} => {:#010x}", vaddr, paddr); + paddr + } + + pub fn driver_area_dma_addr(&self) -> u64 { + let vaddr = &*self.driver_area as *const _ as *const u8; + let paddr = virt_to_phys(vaddr) as u64; + info!("Getting driver DMA ADDR for {:p} => {:#010x}", vaddr, paddr); + paddr + } + + /// 2.6.13: Supplying Buffers to The Device + pub fn push_buffer_r(&mut self, mut buf: Vec) { + assert!(buf.len() != 0); + + // TODO: Blow up if cur_free_head == EOL marker. + let cur_free_head = self.free_head; + self.free_head = u16::from_le(self.descriptor_area[cur_free_head].next) as usize; + + self.descriptor_area[cur_free_head].addr = (virt_to_phys(buf.as_mut_ptr()) as u64).to_le(); + self.virt_area[cur_free_head] = buf.as_ptr() as usize; + self.descriptor_area[cur_free_head].len = (buf.len() as u32).to_le(); + self.descriptor_area[cur_free_head].flags = DescriptorFlags::empty().bits().to_le(); + self.descriptor_area[cur_free_head].next = 0; + core::mem::forget(buf); + + self.driver_area.push_buffer(cur_free_head as u16); + } + + /// 2.6.13: Supplying Buffers to The Device + pub fn push_buffer_w(&mut self, mut buf: Vec) { + assert!(buf.capacity() != 0); + + // TODO: Blow up if cur_free_head == EOL marker. + let cur_free_head = self.free_head; + self.free_head = u16::from_le(self.descriptor_area[cur_free_head].next) as usize; + + self.descriptor_area[cur_free_head].addr = (virt_to_phys(buf.as_mut_ptr()) as u64).to_le(); + self.virt_area[cur_free_head] = buf.as_ptr() as usize; + self.descriptor_area[cur_free_head].len = (buf.capacity() as u32).to_le(); + self.descriptor_area[cur_free_head].flags = DescriptorFlags::WRITE.bits().to_le(); + self.descriptor_area[cur_free_head].next = 0; + core::mem::forget(buf); + + self.driver_area.push_buffer(self.free_head as u16); + } + + pub fn pop_buffer_w(&mut self) -> Option> { + if self.last_used != self.device_area.idx() { + let ring_len = self.device_area.ring().len(); + let used_elem = self.device_area.ring()[self.last_used as usize % ring_len]; + let used_elem_id = used_elem.id as usize; + assert!(!DescriptorFlags::from_bits_truncate(self.descriptor_area[used_elem_id].flags).contains(DescriptorFlags::NEXT)); + let addr = self.virt_area[used_elem_id]; + let capacity = self.descriptor_area[used_elem_id].len as usize; + let len = used_elem.len as usize; + + let ret = unsafe { + Vec::from_raw_parts(addr as *mut u8, len, capacity) + }; + + self.last_used += 1; + + Some(ret) + } else { + None + } + } + + pub fn get_available_idx(&self) -> u16 { + self.driver_area.idx() + } + + pub fn device_notif_suppressed(&self) -> bool { + self.device_area.flags().contains(UsedFlags::NO_NOTIFY) + } + + /*pub fn push_buffer_ro(&mut self, buf: &[u8]) -> CompletionToken { + + }*/ +} + +bitflags! { + struct DescriptorFlags: u16 { + /// This marks a buffer as continuing via the next field. + const NEXT = 1; + /// This marks a buffer as write-only (otherwise read-only). + const WRITE = 2; + /// This means the buffer contains a list of buffer descriptors. + const INDIRECT = 4; + } +} + +#[repr(C, align(16))] +#[derive(Clone, Copy)] +pub struct Descriptor { + /// Address (guest-physical) + addr: u64, + /// Length + len: u32, + /// The flags as indicated above + flags: u16, + /// We chain unused descriptors via this, too + next: u16 +} + +impl fmt::Debug for Descriptor { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Descriptor") + .field("addr", &self.addr()) + .field("len", &self.len()) + .field("flags", &self.flags()) + .field("next", &self.next()) + .finish() + } +} + +impl Descriptor { + fn addr(&self) -> u64 { + u64::from_le(self.addr) + } + fn len(&self) -> u32 { + u32::from_le(self.len) + } + fn flags(&self) -> DescriptorFlags { + DescriptorFlags::from_bits_truncate(u16::from_le(self.flags)) + } + fn next(&self) -> u16 { + u16::from_le(self.next) + } +} + +assert_eq_size!(Descriptor, [u8; 16]); + +bitflags! { + struct AvailFlags: u16 { + /// The driver uses this to advise the device: don't kick me when you + /// add a buffer. It's unreliable, so it's simply an optimization. + const NO_INTERRUPT = 1; + } +} + +#[repr(C)] +pub struct Avail { + flags: u16, + idx: u16, + // Array of queue_size elements. The last element is reserved for used_event! + ring_and_used_event: [u16], +} + +impl fmt::Debug for Avail { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Avail") + .field("flags", &self.flags()) + .field("idx", &self.idx()) + //.field("ring", &self.ring()) + .field("used_event", &self.used_event()) + .finish() + } +} + +impl Avail { + fn new(queue_size: u16) -> Box { + let vec = vec![0u16; queue_size as usize + 3]; + let b = Box::leak(vec.into_boxed_slice()); + unsafe { + Box::from_raw(core::mem::transmute(Ptr { + ptr: b as *mut _ as *mut u8 as usize, + // Ignore flags and idx. Keep used_event in. + len: b.len() - 2, + })) + } + } + + fn flags(&self) -> AvailFlags { + AvailFlags::from_bits_truncate(u16::from_le(self.flags)) + } + + fn set_flags(&mut self, flags: AvailFlags) { + self.flags = flags.bits().to_le(); + } + + fn idx(&self) -> u16 { + u16::from_le(self.idx) + } + + fn set_idx(&mut self, idx: u16) { + fence(Ordering::SeqCst); + self.idx = idx.to_le(); + } + + fn ring(&self) -> &[u16] { + &self.ring_and_used_event[..self.ring_and_used_event.len() - 1] + } + + fn ring_mut(&mut self) -> &mut [u16] { + let len = self.ring_and_used_event.len(); + &mut self.ring_and_used_event[..len - 1] + } + + fn used_event(&self) -> u16 { + u16::from_le(*self.ring_and_used_event.last().unwrap()) + } + + fn push_buffer(&mut self, desc_idx: u16) { + let len = self.ring_mut().len(); + let idx = self.idx() as usize % len; + self.ring_mut()[idx] = desc_idx.to_le(); + self.set_idx(self.idx().wrapping_add(1)); + } +} + +bitflags! { + struct UsedFlags: u16 { + /// The device uses this to advise the driver: don't kick me when you + /// add a buffer. It's unreliable, so it's simply an optimization. + const NO_NOTIFY = 1; + } +} + +#[repr(C)] +#[derive(Clone, Copy)] +struct UsedElem { + /// Index of start of used descriptor chain. + id: u32, + /// Total length of the descriptor chain which was written to. + len: u32 +} + +impl UsedElem { + fn id(&self) -> u32 { + u32::from_le(self.id) + } + + fn len(&self) -> u32 { + u32::from_le(self.len) + } +} + +impl fmt::Debug for UsedElem { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Used") + .field("id", &self.id()) + .field("len", &self.len()) + .finish() + } +} + +union UsedElemOrAvailEvent { + elem: UsedElem, + avail_event: u16 +} + +#[repr(C)] +pub struct Used { + flags: u16, + idx: u16, + // Array of UsedElem. The last element is reserved for avail_event. + ring_and_avail_event: [UsedElemOrAvailEvent], +} + +impl fmt::Debug for Used { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Used") + .field("flags", &self.flags()) + .field("idx", &self.idx()) + //.field("ring", &self.ring()) // Wrong endianness + .field("avail_event", &self.avail_event()) + .finish() + } +} + +impl Used { + fn new(queue_size: u16) -> Box { + let vec = vec![0u32; queue_size as usize * 2 + 2]; + let b = Box::leak(vec.into_boxed_slice()); + unsafe { + Box::from_raw(core::mem::transmute(Ptr { + ptr: b as *mut _ as *mut u8 as usize, + // Ignore flags and idx. Keep used_event in. + len: (b.len() - 1) / 2, + })) + } + } + + fn flags(&self) -> UsedFlags { + UsedFlags::from_bits_truncate(u16::from_le(self.flags)) + } + + fn idx(&self) -> u16 { + u16::from_le(self.idx) + } + + fn ring(&self) -> &[UsedElem] { + unsafe { + // Safety: + let ring = &self.ring_and_avail_event[..self.ring_and_avail_event.len() - 1]; + core::mem::transmute(ring) + } + } + + fn avail_event(&self) -> u16 { + unsafe { + u16::from_le(self.ring_and_avail_event.last().unwrap().avail_event) + } + } +} From 2351900f9592134cea0297ca90cb87d2b6f017be Mon Sep 17 00:00:00 2001 From: roblabla Date: Fri, 28 Jun 2019 18:34:38 +0000 Subject: [PATCH 09/16] Kernel: implement get_system_tick. The GetSystemTick syscall returns a monotonic up-counter representing a frequency of 19200000 Hz. Note that none of our clocks actually run at this frequency (HPET is likely to have a much higher frequency, while PIT is likely to have a much, much, much lower frequency). As such, we have to do some maths. In order to implement GetSystemTick, the KERNEL_TIMER_INFO now contains a get_tick function pointer. When the kernel timer gets selected, it should provide an implementation of that function as a parameter to set_kernel_timer_info. --- kernel/src/devices/hpet.rs | 23 ++++++++++++++++++++--- kernel/src/devices/pit.rs | 11 ++++++++++- kernel/src/event.rs | 6 ++++++ kernel/src/interrupts/syscalls.rs | 15 ++++++++++++++- kernel/src/timer.rs | 18 ++++++++++++++++-- libuser/src/syscalls.rs | 19 +++++++++++++++++++ 6 files changed, 85 insertions(+), 7 deletions(-) diff --git a/kernel/src/devices/hpet.rs b/kernel/src/devices/hpet.rs index 43683eaac..018580a99 100644 --- a/kernel/src/devices/hpet.rs +++ b/kernel/src/devices/hpet.rs @@ -108,7 +108,8 @@ pub struct HpetRegister { /// General Interrupt Status Register. pub general_interrupt_status: Mmio, // 0x20 _reserved3: [u8; 0xCC], // 0x24 - /// main counter value. + /// Current value of the main counter. Should be an up-counter updating at + /// the frequency specified by the period field. pub main_counter_value: Mmio, // 0xF0 _reserved4: u64, // 0xF8 } @@ -361,7 +362,7 @@ impl Hpet { /// Return the frequency of the HPET device. pub fn get_frequency(&self) -> u64 { - 1000000000000000 / u64::from(self.get_period()) + 1_000_000_000_000_000 / u64::from(self.get_period()) } /// Enable the "legacy mapping". @@ -528,8 +529,24 @@ pub unsafe fn init(hpet: &acpi::Hpet) -> bool { // Clear the interrupt state hpet_instance.enable(); - timer::set_kernel_timer_info(16, hpet_instance.get_frequency(), irq_period_ns); + timer::set_kernel_timer_info(16, hpet_instance.get_frequency(), irq_period_ns, get_tick); HPET_INSTANCE = Some(hpet_instance); true } + +/// Returns the current tick in nanoseconds +fn get_tick() -> u64 { + unsafe { + let instance = HPET_INSTANCE.as_ref().expect("HPET not initialized!"); + let period = instance.get_period(); + if period < 1_000_000 { + // nanosecond precision + (*instance.inner).main_counter_value.read() / (1_000_000 / u64::from(instance.get_period())) + } else { + // 100-nanosecond precision. This is the worse possible precision on + // the HPET. + (*instance.inner).main_counter_value.read() / (100_000_000 / u64::from(instance.get_period())) * 100 + } + } +} \ No newline at end of file diff --git a/kernel/src/devices/pit.rs b/kernel/src/devices/pit.rs index 1ee01b36a..0cfb270a8 100644 --- a/kernel/src/devices/pit.rs +++ b/kernel/src/devices/pit.rs @@ -56,6 +56,7 @@ use crate::sync::SpinLock; use crate::io::Io; use crate::i386::pio::Pio; use crate::timer; +use core::convert::TryFrom; /// The oscillator frequency when not divided, in hertz. const OSCILLATOR_FREQ: usize = 1193182; @@ -206,7 +207,7 @@ pub unsafe fn init_channel_0() { ); ports.write_reload_value(ChannelSelector::Channel0, CHAN_0_DIVISOR); - timer::set_kernel_timer_info(0, OSCILLATOR_FREQ as u64, 1_000_000_000 / (CHAN_0_FREQUENCY as u64)); + timer::set_kernel_timer_info(0, OSCILLATOR_FREQ as u64, 1_000_000_000 / (CHAN_0_FREQUENCY as u64), get_tick); } /// Prevent the PIT from generating interrupts. @@ -215,3 +216,11 @@ pub unsafe fn disable() { ports.port_cmd.write(0b00110010); // channel 0, lobyte/hibyte, one-shot ports.write_reload_value(ChannelSelector::Channel0, 1); } + +/// Get the current tick in nanosecond. +/// +/// Note that the PIT's frequency is set at 10 millisecond, so the update +/// frequency of this tick is going to be, erm, not ideal. +fn get_tick() -> u64 { + u64::try_from(crate::event::get_current_count(0)).unwrap().wrapping_mul(100_000) +} \ No newline at end of file diff --git a/kernel/src/event.rs b/kernel/src/event.rs index 29b72cfe4..e28abd894 100644 --- a/kernel/src/event.rs +++ b/kernel/src/event.rs @@ -187,6 +187,12 @@ pub fn wait_event(irq: u8) -> IRQEvent { } } +/// Gets the number of times a certain IRQ got triggered since boot. +pub fn get_current_count(irqnum: u8) -> usize { + IRQ_STATES[usize::from(irqnum)].counter.load(Ordering::SeqCst) +} + + /// Global state of an IRQ. /// /// Counts the number of times this IRQ was triggered from kernel boot. diff --git a/kernel/src/interrupts/syscalls.rs b/kernel/src/interrupts/syscalls.rs index aff39727a..651e9bee4 100644 --- a/kernel/src/interrupts/syscalls.rs +++ b/kernel/src/interrupts/syscalls.rs @@ -20,7 +20,7 @@ use crate::sync::RwLock; use crate::timer; use failure::Backtrace; use sunrise_libkern::{nr, SYSCALL_NAMES, MemoryInfo, MemoryAttributes, MemoryPermissions, MemoryType}; -use bit_field::BitArray; +use bit_field::{BitField, BitArray}; /// Resize the heap of a process, just like a brk. /// It can both expand, and shrink the heap. @@ -532,6 +532,14 @@ fn query_memory(mut meminfo: UserSpacePtrMut, _unk: usize, addr: usi Ok(0) } +/// Returns the current system tick (if you're a linuxian, you might call this +/// a jiffie). +/// +/// The frequency is 19200000 Hz (constant from official sw). +fn get_system_tick() -> u64 { + crate::timer::get_tick().wrapping_mul(625) / 12 // Accurate way of doing * 52.083333 +} + /// Create a new Session pair. Those sessions are linked to each-other: The /// server will receive requests sent through the client. /// @@ -687,6 +695,11 @@ pub extern fn syscall_handler_inner(registers: &mut Registers) { (true, nr::UnmapSharedMemory) => registers.apply0(unmap_shared_memory(x0 as _, x1 as _, x2 as _)), (true, nr::CloseHandle) => registers.apply0(close_handle(x0 as _)), (true, nr::WaitSynchronization) => registers.apply1(wait_synchronization(UserSpacePtr::from_raw_parts(x0 as _, x1), x2)), + (true, nr::GetSystemTick) => { + let tick = get_system_tick(); + registers.eax = tick.get_bits(0..32) as usize; + registers.ebx = tick.get_bits(32..64) as usize; + }, (true, nr::ConnectToNamedPort) => registers.apply1(connect_to_named_port(UserSpacePtr(x0 as _))), (true, nr::SendSyncRequestWithUserBuffer) => registers.apply0(send_sync_request_with_user_buffer(UserSpacePtrMut::from_raw_parts_mut(x0 as _, x1), x2 as _)), (true, nr::OutputDebugString) => registers.apply0(output_debug_string(UserSpacePtr::from_raw_parts(x0 as _, x1), x2, UserSpacePtr::from_raw_parts(x3 as _, x4))), diff --git a/kernel/src/timer.rs b/kernel/src/timer.rs index b2e076414..638dbdbec 100644 --- a/kernel/src/timer.rs +++ b/kernel/src/timer.rs @@ -19,6 +19,9 @@ struct KernelTimerInfo { /// The IRQ number that the timer use. pub irq_number: u8, + + /// Get the current tick in nanoseconds. + pub get_tick: fn() -> u64, } /// Stores the information needed for Sunrise's internal timing. @@ -29,13 +32,14 @@ static KERNEL_TIMER_INFO: Once = Once::new(); /// # Panics /// /// Panics if the timer info has already been initialized. -pub fn set_kernel_timer_info(irq_number: u8, oscillator_frequency: u64, irq_period_ns: u64) { +pub fn set_kernel_timer_info(irq_number: u8, oscillator_frequency: u64, irq_period_ns: u64, get_tick: fn() -> u64) { assert!(KERNEL_TIMER_INFO.r#try().is_none(), "Kernel Timer Info is already initialized!"); KERNEL_TIMER_INFO.call_once(|| { KernelTimerInfo { irq_number, oscillator_frequency, - irq_period_ns + irq_period_ns, + get_tick } }); } @@ -101,3 +105,13 @@ impl Waitable for IRQTimer { } } +/// Gets the current tick in nanosecond according to the [KERNEL_TIMER_INFO]. +/// +/// The tick should be monotonically increasing. Note that the underlying timer +/// might not have nanosecond precision - the HPET, for instance, has a worst +/// precision of 100ns. +pub fn get_tick() -> u64 { + let timer_info = KERNEL_TIMER_INFO.r#try().expect("Kernel Timer Info is not initialized!"); + + (timer_info.get_tick)() +} \ No newline at end of file diff --git a/libuser/src/syscalls.rs b/libuser/src/syscalls.rs index 6b3fc0482..863d98e95 100644 --- a/libuser/src/syscalls.rs +++ b/libuser/src/syscalls.rs @@ -439,3 +439,22 @@ pub fn map_mmio_region(physical_address: usize, size: usize, virtual_address: us Ok(()) } } + +pub fn get_system_tick() -> u64 { + let mut registers = Registers { + eax: nr::GetSystemTick, + ebx: 0, + ecx: 0, + edx: 0, + esi: 0, + edi: 0, + ebp: 0, + }; + + unsafe { + // Safety: Syscall to GetSystemTick is always safe. + syscall_inner(&mut registers); + } + + registers.eax as u64 | ((registers.ebx as u64) << 32) +} \ No newline at end of file From 9c1b0161a8cbd99274357d211c4e51b12e8f6e94 Mon Sep 17 00:00:00 2001 From: roblabla Date: Fri, 28 Jun 2019 18:42:00 +0000 Subject: [PATCH 10/16] Virtio: Proper MSI-X implementation. Turns out, the spec we were basic our PCI implementation on previously was probably a draft. After updating to a more correct spec, things started working a lot better, unsurprisingly. --- kernel/src/event.rs | 7 ++++--- kernel/src/interrupts/irq.rs | 9 ++++++++- libuser/src/pci.rs | 15 ++++----------- libuser/src/pci/capabilities.rs | 34 ++++++++++++++++----------------- virtio/src/main.rs | 34 ++++++++++++++------------------- 5 files changed, 47 insertions(+), 52 deletions(-) diff --git a/kernel/src/event.rs b/kernel/src/event.rs index e28abd894..fb1010516 100644 --- a/kernel/src/event.rs +++ b/kernel/src/event.rs @@ -159,7 +159,7 @@ impl Waitable for IRQEvent { fn register(&self) { let curproc = scheduler::get_current_thread(); let mut veclock = self.state.waiting_processes.lock(); - info!("Registering {:010x} for irq {}", &*curproc as *const _ as usize, self.state.irqnum); + debug!("Registering {:010x} for irq {}", &*curproc as *const _ as usize, self.state.irqnum); if veclock.iter().find(|v| Arc::ptr_eq(&curproc, v)).is_none() { veclock.push(scheduler::get_current_thread()); } @@ -220,10 +220,11 @@ impl IRQState { } /// Global state for all the IRQ handled by the IOAPIC. -static IRQ_STATES: [IRQState; 17] = [ +static IRQ_STATES: [IRQState; 24] = [ IRQState::new(0x20), IRQState::new(0x21), IRQState::new(0x22), IRQState::new(0x23), IRQState::new(0x24), IRQState::new(0x25), IRQState::new(0x26), IRQState::new(0x27), IRQState::new(0x28), IRQState::new(0x29), IRQState::new(0x2A), IRQState::new(0x2B), IRQState::new(0x2C), IRQState::new(0x2D), IRQState::new(0x2E), IRQState::new(0x2F), - IRQState::new(0x30), + IRQState::new(0x30), IRQState::new(0x31), IRQState::new(0x32), IRQState::new(0x33), + IRQState::new(0x34), IRQState::new(0x35), IRQState::new(0x36), IRQState::new(0x37), ]; diff --git a/kernel/src/interrupts/irq.rs b/kernel/src/interrupts/irq.rs index eb64bef05..16e13733a 100644 --- a/kernel/src/interrupts/irq.rs +++ b/kernel/src/interrupts/irq.rs @@ -23,7 +23,7 @@ macro_rules! irq_handler { /// Array of interrupt handlers. The position in the array defines the IRQ this /// handler is targeting. See the module documentation for more information. -pub static IRQ_HANDLERS : [extern "x86-interrupt" fn(stack_frame: &mut ExceptionStackFrame); 17] = [ +pub static IRQ_HANDLERS : [extern "x86-interrupt" fn(stack_frame: &mut ExceptionStackFrame); 24] = [ irq_handler!(0, pit_handler), irq_handler!(1, keyboard_handler), irq_handler!(2, cascade_handler), @@ -41,4 +41,11 @@ pub static IRQ_HANDLERS : [extern "x86-interrupt" fn(stack_frame: &mut Exception irq_handler!(14, primary_ata_handler), irq_handler!(15, secondary_ata_handler), irq_handler!(16, hpet_handler), + irq_handler!(17, irq17_handler), + irq_handler!(18, irq18_handler), + irq_handler!(19, network_handler), + irq_handler!(20, irq20_handler), + irq_handler!(21, irq21_handler), + irq_handler!(22, irq22_handler), + irq_handler!(23, irq23_handler), ]; diff --git a/libuser/src/pci.rs b/libuser/src/pci.rs index 20342f8d0..7e220b5e9 100644 --- a/libuser/src/pci.rs +++ b/libuser/src/pci.rs @@ -2,7 +2,10 @@ //! //! A minimal PCI implementation, that permits only discovering AHCI devices, and querying their BAR. //! -//! PCI Local Bus Specification: https://web.archive.org/web/20180712233954/http://fpga-faq.narod.ru/PCI_Rev_30.pdf +//! PCI Local Bus Specification: https://web.archive.org/web/20170728023923/https://lekensteyn.nl/files/docs/PCI_SPEV_V3_0.pdf +//! +//! (Careful, there are many outdated PCI specs documents out there that are +//! incompatible with the real spec, especially in regards to MSI-X!) use sunrise_libutils::io::{Io, Pio}; use spin::Mutex; @@ -610,16 +613,6 @@ impl PciDevice { Err(()) } } - - pub fn set_msix_message_upper_address(&self, val: u32) -> Result<(), ()> { - let msix = self.capabilities().find(|v| if let Capability::MsiX(..) = v { true } else { false }); - if let Some(Capability::MsiX(msix)) = msix { - msix.set_message_upper_address(val); - Ok(()) - } else { - Err(()) - } - } } /// Read one of the 64 32-bit registers of a pci bus>device>func. diff --git a/libuser/src/pci/capabilities.rs b/libuser/src/pci/capabilities.rs index 743c8731a..8b9fbe018 100644 --- a/libuser/src/pci/capabilities.rs +++ b/libuser/src/pci/capabilities.rs @@ -33,13 +33,19 @@ pub struct MsiX<'a> { inner: RWCapability<'a> } +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct MsiXEntry { + pub addr: u64, + pub data: u32, + pub ctrl: MsiXControl +} + bitfield! { - pub struct MsiXEntry(u64); + #[derive(Clone, Copy)] + pub struct MsiXControl(u32); impl Debug; - pub pending, _: 0; - pub masked, set_masked: 1; - pub message_address, set_message_address: 31, 2; - pub message_data, set_message_data: 63, 32; + pub masked, set_masked: 0; } impl<'a> MsiX<'a> { @@ -53,19 +59,11 @@ impl<'a> MsiX<'a> { self.inner.read_u32(0).get_bits(16..27) as usize + 1 } - pub fn message_upper_address(&self) -> u32 { - self.inner.read_u32(4) - } - - pub fn set_message_upper_address(&self, val: u32) { - self.inner.write_u32(4, val) - } - pub fn set_message_entry(&self, entry: usize, val: MsiXEntry) -> Result<(), KernelError> { assert!(entry < self.table_size()); - let mut table_offset_bir = self.inner.read_u32(8); + let mut table_offset_bir = self.inner.read_u32(4); let bir = table_offset_bir.get_bits(0..3); - let table_offset = *table_offset_bir.set_bits(0..3, 0); + let table_offset = *table_offset_bir.set_bits(0..3, 0) as u64; let PciDevice { bus, slot, function, .. } = self.inner.device; @@ -77,8 +75,10 @@ impl<'a> MsiX<'a> { let bar = header.bar(bir as usize).expect(&format!("Device {}.{}.{} to contain BAR {}", bus, slot, function, bir)); let mapped_bar = bar.map()?; - mapped_bar.write_u32::((entry * 8) as u64, val.0.get_bits(0..32) as u32); - mapped_bar.write_u32::((entry * 8 + 4) as u64, val.0.get_bits(32..64) as u32); + mapped_bar.write_u32::(table_offset + (entry * 16) as u64, val.addr.get_bits(0..32) as u32); + mapped_bar.write_u32::(table_offset + (entry * 16 + 4) as u64, val.addr.get_bits(32..64) as u32); + mapped_bar.write_u32::(table_offset + (entry * 16 + 8) as u64, val.data); + mapped_bar.write_u32::(table_offset + (entry * 16 + 12) as u64, val.ctrl.0); Ok(()) } diff --git a/virtio/src/main.rs b/virtio/src/main.rs index 807a65c0b..8c6b222c6 100755 --- a/virtio/src/main.rs +++ b/virtio/src/main.rs @@ -11,7 +11,7 @@ use sunrise_libuser::pci::{discover as pci_discover, PciHeader, PciDevice, GeneralPciHeader, CONFIG_ADDRESS as PCI_CONFIG_ADDRESS, CONFIG_DATA as PCI_CONFIG_DATA}; -use sunrise_libuser::pci::capabilities::{MsiXEntry, Capability}; +use sunrise_libuser::pci::capabilities::{MsiXEntry, MsiXControl, Capability}; use sunrise_libuser::error::{VirtioError, Error}; use log::*; @@ -157,6 +157,16 @@ impl VirtioDevice { self.reset(); self.common_cfg.set_device_status(DeviceStatus::ACKNOWLEDGE); + // Setup MSI-X vector. + self.device.enable_msix(true).unwrap(); + let mut entry = MsiXEntry { + // TODO: DMAR + addr: 0xFEE0_0000, + data: 0x0000_0033, + ctrl: MsiXControl(0) + }; + self.device.set_msix_message_entry(0, entry).unwrap(); + self.queues.clear(); for i in 0..self.common_cfg.num_queues() { self.queues.push(None) @@ -179,11 +189,7 @@ impl VirtioDevice { queue.desc = virtqueue.descriptor_area_dma_addr(); queue.driver = virtqueue.driver_area_dma_addr(); queue.device = virtqueue.device_area_dma_addr(); - let mut entry = MsiXEntry(0); - // TODO: DMAR - entry.set_message_address(0x0FEE_0000); - entry.set_message_data(0x0000_0000); - self.device.set_msix_message_entry(0, entry); + queue.msix_vector = 0; queue.enable = true; self.common_cfg.set_queue(virtqueue_idx, &queue); self.queues[virtqueue_idx as usize] = Some(virtqueue); @@ -501,7 +507,7 @@ fn main() { devices.push(VirtioDevice { virtio_did, device, header, common_cfg, device_cfg: Some(device_cfg), common_features: CommonFeatures::empty(), notif_cfg, queues: Vec::new(), - irq_event: syscalls::create_interrupt_event(header.interrupt_line() as usize, 0).unwrap() + irq_event: syscalls::create_interrupt_event(19, 0).unwrap() }), _ => () } @@ -556,20 +562,8 @@ capabilities!(CAPABILITIES = Capabilities { sunrise_libuser::syscalls::nr::CreateSession, ], raw_caps: [ - // todo: IRQ capabilities at runtime - // body: Currently IRQ capabilities are declared at compile-time. - // body: - // body: However, for PCI, the IRQ line we want to subscribe to - // body: can only be determined at runtime by reading the `Interrupt Line` register - // body: that has been set-up during POST. - // body: - // body: What would be the proper way to handle such a case ? - // body: - // body: - Declaring every IRQ line in our capabilities, but only effectively using one ? - // body: - Deporting the PIC management to a userspace module, and allow it to accept - // body: dynamic irq capabilities in yet undefined way. sunrise_libuser::caps::ioport(PCI_CONFIG_ADDRESS + 0), sunrise_libuser::caps::ioport(PCI_CONFIG_ADDRESS + 1), sunrise_libuser::caps::ioport(PCI_CONFIG_ADDRESS + 2), sunrise_libuser::caps::ioport(PCI_CONFIG_ADDRESS + 3), sunrise_libuser::caps::ioport(PCI_CONFIG_DATA + 0), sunrise_libuser::caps::ioport(PCI_CONFIG_DATA + 1), sunrise_libuser::caps::ioport(PCI_CONFIG_DATA + 2), sunrise_libuser::caps::ioport(PCI_CONFIG_DATA + 3), - sunrise_libuser::caps::irq_pair(0xb, 0x3FF) + sunrise_libuser::caps::irq_pair(19, 0x3FF) ] }); From 713de5b0b361168d12ed6ef28d64e543ab734bd5 Mon Sep 17 00:00:00 2001 From: roblabla Date: Fri, 28 Jun 2019 18:46:00 +0000 Subject: [PATCH 11/16] PCI: Enable bus mastering when enabling MSI-X --- Cargo.lock | 1 + libuser/Cargo.toml | 1 + libuser/src/pci.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3455dde19..eed5a796d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -597,6 +597,7 @@ dependencies = [ "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "bit_field 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "bitfield 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "font-rs 0.1.3 (git+https://github.com/SunriseOS/font-rs)", diff --git a/libuser/Cargo.toml b/libuser/Cargo.toml index ceb3b7526..a7a656832 100644 --- a/libuser/Cargo.toml +++ b/libuser/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] linked_list_allocator = "0.6.4" +bitflags = "1.1.0" bitfield = "0.13" bit_field = "0.10" spin = "0.5" diff --git a/libuser/src/pci.rs b/libuser/src/pci.rs index 7e220b5e9..038dd89bb 100644 --- a/libuser/src/pci.rs +++ b/libuser/src/pci.rs @@ -16,6 +16,7 @@ use crate::types::MappedSharedMemory; use crate::error::{LibuserError, KernelError, Error}; use byteorder::ByteOrder; use capabilities::Capability; +use bitflags::bitflags; pub mod capabilities; @@ -233,6 +234,40 @@ pub enum BAR { Io(BARIo) } +bitflags! { + /// Command Register Layout + /// + /// See Chapter 6.2.2: Device Control + pub struct Command: u16 { + /// Controls a device's response to I/O Space accesses. A value of 0 + /// disables the device response. A value of 1 allows the device to + /// respond to I/O Space accesses. State after RST# is 0. + const IO_SPACE = 1 << 0; + /// Controls a device's response to Memory Space accesses. A value of 0 + /// disables the device response. A value of 1 allows the device to + /// respond to Memory Space accesses. State after RST# is 0. + const MEMORY_SPACE = 1 << 1; + /// Controls a device's ability to act as a master on the PCI bus. A + /// value of 0 disables the device from generating PCI accesses. A value + /// of 1 allows the device to behave as a bus master. State after RST# + /// is 0. + /// + /// Bus Mastering is basically DMA for PCI. + const BUS_MASTER = 1 << 2; + /// Controls a device's action on Special Cycle operations. A value of 0 + /// causes the device to ignore all Special Cycle operations. A value of + /// 1 allows the device to monitor Special Cycle operations. State after + /// RST# is 0. + const SPECIAL_CYCLES = 1 << 3; + const MEMORY_WRITE_AND_INVALIDATE_ENABLE = 1 << 4; + const VGA_PALETTE_SNOOP = 1 << 5; + const PARITY_ERROR_RESPONSE = 1 << 6; + const SERR_ENABLE = 1 << 8; + const FAST_BACK_TO_BACK_ENABLE = 1 << 9; + const INTERRUPT_DISABLE = 1 << 10; + } +} + impl BAR { pub fn map(&self) -> Result { match self { @@ -570,8 +605,25 @@ impl PciDevice { } /// Reads the command register. - fn command(&self) -> u16 { - (self.read_config_register(4) >> 0) as u16 + /// + /// The Command register provides coarse control over a device's ability to + /// generate and respond to PCI cycles. + /// + /// See Chapter 6.2.2: Device Control. + fn command(&self) -> Command { + Command::from_bits_truncate((self.read_config_register(4) >> 0) as u16) + } + + /// Writes to the command register. + /// + /// The Command register provides coarse control over a device's ability to + /// generate and respond to PCI cycles. + /// + /// See Chapter 6.2.2: Device Control. + fn set_command(&self, command: Command) { + let mut config = self.read_config_register(4); + config.set_bits(0..16, command.bits() as u32); + self.write_config_register(4, config); } pub fn capabilities(&self) -> impl Iterator { @@ -588,6 +640,8 @@ impl PciDevice { pub fn enable_msix(&self, val: bool) -> Result<(), ()> { let msix = self.capabilities().find(|v| if let Capability::MsiX(..) = v { true } else { false }); if let Some(Capability::MsiX(msix)) = msix { + // Enable Bus Mastering, necessary for MsiX to work. + self.set_command(self.command() | Command::BUS_MASTER | Command::MEMORY_SPACE | Command::IO_SPACE); msix.enable_msix(val); Ok(()) } else { From 948d8f8b2f2c5efde8c038c650d94a361200e216 Mon Sep 17 00:00:00 2001 From: roblabla Date: Fri, 28 Jun 2019 18:48:49 +0000 Subject: [PATCH 12/16] PCI: Remove len-checking from RWCapability. --- libuser/src/pci/capabilities.rs | 17 ++++++----------- virtio/src/main.rs | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/libuser/src/pci/capabilities.rs b/libuser/src/pci/capabilities.rs index 8b9fbe018..6f3289150 100644 --- a/libuser/src/pci/capabilities.rs +++ b/libuser/src/pci/capabilities.rs @@ -88,22 +88,15 @@ impl<'a> MsiX<'a> { pub struct RWCapability<'a> { device: &'a PciDevice, offset: u8, - len: u8 } impl<'a> RWCapability<'a> { pub fn read_u32(&self, offset: u8) -> u32 { - assert!(offset < self.len, "Attempted to read at offset {}, but capability only has {} bytes", offset, self.len); pci_config_read_word(self.device.bus, self.device.slot, self.device.function, (self.offset + offset) & 0xFC) } pub fn write_u32(&self, offset: u8, value: u32) { - assert!(offset < self.len, "Attempted to write at offset {}, but capability only has {} bytes", offset, self.len); pci_config_write_word(self.device.bus, self.device.slot, self.device.function, (self.offset + offset) & 0xFC, value); } - - pub fn len(&self) -> usize { - self.len as usize - } } #[derive(Debug)] @@ -117,7 +110,7 @@ pub enum Capability<'a> { CompactPciHotSwap, PciX, HyperTransport, - VendorSpecific(RWCapability<'a>), + VendorSpecific(RWCapability<'a>, u8), DebugPort, CompactPciCentralResourceControl, PciHotPlug, @@ -152,10 +145,9 @@ impl<'a> Capability<'a> { let mut next = word.get_bits(8..16) as u8; // 6.7: Get rid of the lower 2 bits, they are reserved for future use. next.set_bits(0..2, 0); - let len = word.get_bits(16..24) as u8; let rw_cap = RWCapability { - device, offset: register, len + device, offset: register }; let cap = match ty { @@ -168,7 +160,10 @@ impl<'a> Capability<'a> { 0x06 => Capability::CompactPciHotSwap, 0x07 => Capability::PciX, 0x08 => Capability::HyperTransport, - 0x09 => Capability::VendorSpecific(rw_cap), + 0x09 => { + let len = word.get_bits(16..24) as u8; + Capability::VendorSpecific(rw_cap, len) + }, 0x0A => Capability::DebugPort, 0x0B => Capability::CompactPciCentralResourceControl, 0x0C => Capability::PciHotPlug, diff --git a/virtio/src/main.rs b/virtio/src/main.rs index 8c6b222c6..263a8a1ee 100755 --- a/virtio/src/main.rs +++ b/virtio/src/main.rs @@ -485,7 +485,7 @@ fn main() { let mut notify_cfg = None; for capability in device.capabilities() { match capability { - Capability::VendorSpecific(data) => { + Capability::VendorSpecific(data, size) => { if let Ok(Some(cap)) = pci::Cap::read(header.bars(), &data) { info!("{:?}", cap); match cap { From 1df96c482c9ec6dfa9512edafd7de78381085b50 Mon Sep 17 00:00:00 2001 From: roblabla Date: Fri, 28 Jun 2019 18:50:49 +0000 Subject: [PATCH 13/16] Virtio: Move test code to own file, use TCP instead of ICMP --- virtio/src/main.rs | 213 ++++----------------------------------------- virtio/src/ping.rs | 110 +++++++++++++++++++++++ 2 files changed, 128 insertions(+), 195 deletions(-) create mode 100644 virtio/src/ping.rs diff --git a/virtio/src/main.rs b/virtio/src/main.rs index 263a8a1ee..2fd105277 100755 --- a/virtio/src/main.rs +++ b/virtio/src/main.rs @@ -1,5 +1,13 @@ +//! Virtio Driver +//! +//! This binary contains the common bits of a virtio driver. It implements the +//! [virtio spec 1.1](https://web.archive.org/web/20190628162805/https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.html). +//! +//! It does **not** support legacy or transitional interfaces. Only the modern +//! interfaces are implemented. + #![no_std] -#![feature(alloc, underscore_const_names)] +#![feature(underscore_const_names, slice_concat_ext)] #[macro_use] extern crate alloc; @@ -17,11 +25,10 @@ use sunrise_libuser::error::{VirtioError, Error}; use log::*; use bitflags::bitflags; use crate::pci::{CommonCfg, NotificationCfg, Config}; -use crate::net::VirtioNet; -use alloc::vec::Vec; use bitfield::bitfield; use virtqueue::VirtQueue; use core::sync::atomic::{fence, Ordering}; +use alloc::vec::Vec; mod pci; mod net; @@ -239,25 +246,24 @@ impl VirtioDevice { // that it updates the idx field before checking for notification // suppression. fence(Ordering::SeqCst); - info!("{:#?}", queue); if !queue.device_notif_suppressed() { let queue_notify_off = self.common_cfg.queue_notify_off(vq) as usize; if self.common_features.contains(CommonFeatures::NOTIFICATION_DATA) { - info!("Notifying {}", vq); + debug!("Notifying {}", vq); let mut notif = Notification(0); notif.set_virtqueue_idx(vq.into()); notif.set_next_off_split(queue.get_available_idx().into()); self.notif_cfg.notify_with_notification(queue_notify_off as usize, notif); } else { - info!("Notifying {}", vq); + debug!("Notifying {}", vq); self.notif_cfg.notify_with_virtqueue(queue_notify_off as usize, vq); } } else { - info!("Notifications for {} suppressed", vq); + debug!("Notifications for {} suppressed", vq); } } else { - info!("Queue {} does not exist", vq); + error!("Queue {} does not exist", vq); } } @@ -266,190 +272,7 @@ impl VirtioDevice { } } -macro_rules! send_icmp_ping { - ( $repr_type:ident, $packet_type:ident, $ident:expr, $seq_no:expr, - $echo_payload:expr, $socket:expr, $remote_addr:expr ) => {{ - let icmp_repr = $repr_type::EchoRequest { - ident: $ident, - seq_no: $seq_no, - data: &$echo_payload, - }; - - let icmp_payload = $socket - .send(icmp_repr.buffer_len(), $remote_addr) - .unwrap(); - - let mut icmp_packet = $packet_type::new_unchecked(icmp_payload); - (icmp_repr, icmp_packet) - }} -} - -macro_rules! get_icmp_pong { - ( $repr_type:ident, $repr:expr, $payload:expr, $waiting_queue:expr, $remote_addr:expr, - $timestamp:expr, $received:expr ) => {{ - if let $repr_type::EchoReply { seq_no, data, .. } = $repr { - if let Some(_) = $waiting_queue.get(&seq_no) { - let packet_timestamp_ms = NetworkEndian::read_i64(data); - info!("{} bytes from {}: icmp_seq={}, time={}ms", - data.len(), $remote_addr, seq_no, - $timestamp.total_millis() - packet_timestamp_ms); - $waiting_queue.remove(&seq_no); - $received += 1; - } - } - }} -} - -fn ping(device: VirtioNet) { - use smoltcp::time::{Duration, Instant}; - use smoltcp::phy::Device; - //use smoltcp::phy::wait as phy_wait; - use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, - Ipv6Address, Icmpv6Repr, Icmpv6Packet, - Ipv4Address, Icmpv4Repr, Icmpv4Packet}; - use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder, Routes}; - use smoltcp::socket::{SocketSet, IcmpSocket, IcmpSocketBuffer, IcmpPacketMetadata, IcmpEndpoint}; - use byteorder::{NetworkEndian, ByteOrder}; - use alloc::collections::BTreeMap; - - let count = 5; - let interval = Duration::from_millis(50); - let timeout = Duration::from_millis(5000); - let device_caps = device.capabilities(); - - let neighbor_cache = NeighborCache::new(BTreeMap::new()); - - let remote_addr = IpAddress::v4(10, 0, 2, 2); - - let icmp_rx_buffer = IcmpSocketBuffer::new(vec![IcmpPacketMetadata::EMPTY], vec![0; 256]); - let icmp_tx_buffer = IcmpSocketBuffer::new(vec![IcmpPacketMetadata::EMPTY], vec![0; 256]); - let icmp_socket = IcmpSocket::new(icmp_rx_buffer, icmp_tx_buffer); - - let ethernet_addr = EthernetAddress(device.mac()); - let src_ipv4 = IpCidr::new(IpAddress::v4(10, 0, 2, 15), 24); - let ip_addrs = [src_ipv4]; - let default_v4_gw = Ipv4Address::new(10, 0, 2, 2); - let mut routes_storage = [None; 1]; - let mut routes = Routes::new(&mut routes_storage[..]); - routes.add_default_ipv4_route(default_v4_gw).unwrap(); - let mut iface = EthernetInterfaceBuilder::new(device) - .ethernet_addr(ethernet_addr) - .ip_addrs(ip_addrs) - .routes(routes) - .neighbor_cache(neighbor_cache) - .finalize(); - - let mut sockets = SocketSet::new(vec![]); - let icmp_handle = sockets.add(icmp_socket); - - let mut send_at = Instant::from_millis(0); - let mut seq_no = 0; - let mut received = 0; - let mut echo_payload = [0xffu8; 40]; - let mut waiting_queue = BTreeMap::new(); - let ident = 0x22b; - - let mut timestamp = Instant::from_millis(0); - loop { - timestamp += interval; - //info!("{:#?}", iface); - match iface.poll(&mut sockets, timestamp) { - Ok(_) => {}, - Err(e) => { - debug!("poll error: {}", e); - } - } - - { - let timestamp = timestamp + Duration::from_millis(50); - let mut socket = sockets.get::(icmp_handle); - if !socket.is_open() { - socket.bind(IcmpEndpoint::Ident(ident)).unwrap(); - send_at = timestamp; - } - - if socket.can_send() && seq_no < count as u16 && - send_at <= timestamp { - NetworkEndian::write_i64(&mut echo_payload, timestamp.total_millis()); - - match remote_addr { - IpAddress::Ipv4(_) => { - let (icmp_repr, mut icmp_packet) = send_icmp_ping!( - Icmpv4Repr, Icmpv4Packet, ident, seq_no, - echo_payload, socket, remote_addr); - icmp_repr.emit(&mut icmp_packet, &device_caps.checksum); - }, - /*IpAddress::Ipv6(_) => { - let (icmp_repr, mut icmp_packet) = send_icmp_ping!( - Icmpv6Repr, Icmpv6Packet, ident, seq_no, - echo_payload, socket, remote_addr); - icmp_repr.emit(&src_ipv6, &remote_addr, - &mut icmp_packet, &device_caps.checksum); - },*/ - _ => unimplemented!() - } - - waiting_queue.insert(seq_no, timestamp); - seq_no += 1; - send_at += interval; - } - - if socket.can_recv() { - let (payload, _) = socket.recv().unwrap(); - - match remote_addr { - IpAddress::Ipv4(_) => { - let icmp_packet = Icmpv4Packet::new_checked(&payload).unwrap(); - let icmp_repr = - Icmpv4Repr::parse(&icmp_packet, &device_caps.checksum).unwrap(); - get_icmp_pong!(Icmpv4Repr, icmp_repr, payload, - waiting_queue, remote_addr, timestamp, received); - } - /*IpAddress::Ipv6(_) => { - let icmp_packet = Icmpv6Packet::new_checked(&payload).unwrap(); - let icmp_repr = Icmpv6Repr::parse(&remote_addr, &src_ipv6, - &icmp_packet, &device_caps.checksum).unwrap(); - get_icmp_pong!(Icmpv6Repr, icmp_repr, payload, - waiting_queue, remote_addr, timestamp, received); - },*/ - _ => unimplemented!() - } - } - - let mut to_remove = Vec::new(); - - for (seq, from) in &waiting_queue { - if timestamp - *from >= timeout { - info!("From {} icmp_seq={} timeout", remote_addr, seq); - to_remove.push(seq.clone()); - } - } - - for item in to_remove { - waiting_queue.remove(&item); - } - - - if seq_no == count as u16 && waiting_queue.is_empty() { - break - } - } - - let timestamp = timestamp + Duration::from_millis(50); - match iface.poll_at(&sockets, timestamp) { - Some(poll_at) if timestamp < poll_at => { - let resume_at = core::cmp::min(poll_at, send_at); - let ns = (resume_at - timestamp).total_millis() * 0x1_000_000; - syscalls::wait_synchronization(&[iface.device().device.irq_event.0.as_ref()], Some(ns as usize)).unwrap(); - }, - Some(_) => (), - None => { - let ns = (send_at - timestamp).total_millis() * 0x1_000_000; - syscalls::wait_synchronization(&[iface.device().device.irq_event.0.as_ref()], Some(ns as usize)).unwrap(); - } - } - } -} +mod ping; fn main() { debug!("Virtio driver starting up"); @@ -514,7 +337,6 @@ fn main() { } for device in devices.iter_mut() { - info!("Pinging on device {:#x?}", device); device.acknowledge(); } @@ -526,8 +348,8 @@ fn main() { info!("Initializing"); device.init().unwrap(); - info!("Pinging on device {:#x?}", device); - ping(device); + info!("Pinging"); + ping::ping(device); }, id => info!("Unsupported did {}", id) } @@ -547,6 +369,7 @@ capabilities!(CAPABILITIES = Capabilities { sunrise_libuser::syscalls::nr::CloseHandle, sunrise_libuser::syscalls::nr::WaitSynchronization, sunrise_libuser::syscalls::nr::OutputDebugString, + sunrise_libuser::syscalls::nr::GetSystemTick, sunrise_libuser::syscalls::nr::SetHeapSize, sunrise_libuser::syscalls::nr::QueryMemory, diff --git a/virtio/src/ping.rs b/virtio/src/ping.rs new file mode 100644 index 000000000..8ba83bfa8 --- /dev/null +++ b/virtio/src/ping.rs @@ -0,0 +1,110 @@ +use crate::net::VirtioNet; +use log::*; +use alloc::vec::Vec; +use sunrise_libuser::syscalls; +use sunrise_libuser::error::KernelError; +use sunrise_libuser::types::HandleRef; +use smoltcp::time::Duration; +use alloc::borrow::ToOwned; +use alloc::slice::SliceConcatExt; +use core::str; + +pub fn ping(device: VirtioNet) { + use smoltcp::time::{Duration, Instant}; + use smoltcp::phy::Device; + //use smoltcp::phy::wait as phy_wait; + use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, + Ipv4Address, TcpRepr, TcpPacket}; + use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder, Routes}; + use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer}; + use byteorder::{NetworkEndian, ByteOrder}; + use alloc::collections::BTreeMap; + + let neighbor_cache = NeighborCache::new(BTreeMap::new()); + + let remote_addr = IpAddress::v4(10, 0, 2, 2); + + let tcp_rx_buffer = TcpSocketBuffer::new(vec![0; 64]); + let tcp_tx_buffer = TcpSocketBuffer::new(vec![0; 128]); + let tcp_socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); + + let ethernet_addr = EthernetAddress(device.mac()); + let src_ipv4 = IpCidr::new(IpAddress::v4(10, 0, 2, 15), 24); + let ip_addrs = [src_ipv4]; + let default_v4_gw = Ipv4Address::new(10, 0, 2, 2); + let mut routes_storage = [None; 1]; + let mut routes = Routes::new(&mut routes_storage[..]); + routes.add_default_ipv4_route(default_v4_gw).unwrap(); + let mut iface = EthernetInterfaceBuilder::new(device) + .ethernet_addr(ethernet_addr) + .ip_addrs(ip_addrs) + .routes(routes) + .neighbor_cache(neighbor_cache) + .finalize(); + + let mut sockets = SocketSet::new(vec![]); + let tcp_handle = sockets.add(tcp_socket); + + { + let mut socket = sockets.get::(tcp_handle); + socket.connect((remote_addr, 10000), 49500).unwrap(); + } + let mut tcp_active = false; + + debug!("Starting loop:"); + loop { + let timestamp = Instant::from_millis(syscalls::get_system_tick().wrapping_mul(12) as i64 / 625_000_000); + match iface.poll(&mut sockets, timestamp) { + Ok(_) => {}, + Err(e) => { + debug!("poll error: {}", e); + } + } + + { + let mut socket = sockets.get::(tcp_handle); + if socket.is_active() && !tcp_active { + debug!("connected"); + } else if !socket.is_active() && tcp_active { + debug!("disconnected"); + break; + } + tcp_active = socket.is_active(); + + if socket.may_recv() { + + let data = socket.recv(|data| { + let mut data = data.to_owned(); + if data.len() > 0 { + debug!("recv data: {:?}", + str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)")); + data = data.split(|&b| b == b'\n').collect::>().concat(); + data.reverse(); + data.extend(b"\n"); + } + (data.len(), data) + }).unwrap(); + if socket.can_send() && data.len() > 0 { + debug!("send data: {:?}", + str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)")); + socket.send_slice(&data[..]).unwrap(); + } + } else if socket.may_send() { + socket.send_slice(b"Ohi!").unwrap(); + } + } + + wait(iface.device().device.irq_event.0.as_ref(), iface.poll_delay(&sockets, timestamp)); + } +} + +fn wait(handle: HandleRef<'_>, duration: Option) -> Result<(), ()> { + match syscalls::wait_synchronization(&[handle], duration.map(|v| v.millis() as usize * 1_000_000)) { + Ok(_) => { + debug!("Handle got signaled!"); + Ok(()) + }, + Err(KernelError::Timeout) => Ok(()), + Err(err) => Err(()) + } +} \ No newline at end of file From db4a9e8d71491ff90918cd10186d4b25089c470d Mon Sep 17 00:00:00 2001 From: roblabla Date: Fri, 28 Jun 2019 18:51:30 +0000 Subject: [PATCH 14/16] Virtio: Fix reception of packet by skipping the NetHdr --- virtio/src/net.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/virtio/src/net.rs b/virtio/src/net.rs index ee3c72b65..e9f77ef66 100644 --- a/virtio/src/net.rs +++ b/virtio/src/net.rs @@ -12,7 +12,7 @@ use smoltcp::time::Instant; use core::fmt; use sunrise_libuser::error::Error; use bit_field::BitField; -use log::info; +use log::*; bitflags! { /// Features supported by the Virtio-Net driver. @@ -280,6 +280,7 @@ impl<'a> Device<'a> for VirtioNet { fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> { let buf = self.receive_queues().nth(0).unwrap().pop_buffer_w()?; + debug!("{:#?}", buf); let rx = VirtioNetRxToken(buf); let tx = VirtioNetTxToken(self); Some((rx, tx)) @@ -311,7 +312,8 @@ impl RxToken for VirtioNetRxToken { where F: FnOnce(&[u8]) -> smoltcp::Result { - f(&self.0[..]) + debug!("Consuming the buffer"); + f(&self.0[core::mem::size_of::()..]) } } From 4fa436ce6001cf8d253fea101a9db928a5576a8d Mon Sep 17 00:00:00 2001 From: roblabla Date: Thu, 27 Jun 2019 17:27:43 +0000 Subject: [PATCH 15/16] swipc-gen: Fix multiple codegen bug in trait generation --- swipc-gen/src/gen_rust_code.rs | 41 ++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/swipc-gen/src/gen_rust_code.rs b/swipc-gen/src/gen_rust_code.rs index 490eedb9e..0fff1f907 100644 --- a/swipc-gen/src/gen_rust_code.rs +++ b/swipc-gen/src/gen_rust_code.rs @@ -133,11 +133,11 @@ fn format_args(args: &[(Alias, Option)], ret: &[(Alias, Option)] /// arguments. /// /// See [get_type] to find the mapping of a SwIPC type to a Rust type. -fn format_ret_ty(ret: &[(Alias, Option)]) -> Result { +fn format_ret_ty(ret: &[(Alias, Option)], server: bool) -> Result { let mut v = Vec::new(); for (ty, _name) in named_iterator(ret, true) { - v.push(get_type(true, ty, false)?); + v.push(get_type(true, ty, server)?); } match v.len() { @@ -217,6 +217,8 @@ fn get_type(output: bool, ty: &Alias, is_server: bool) -> Result Alias::Handle(is_copy, ty) => if let Some(s) = get_handle_type(ty) { Ok(format!("{}{}", if *is_copy && !is_server && !output { "&" } else { "" }, s)) + } else if *is_copy && is_server && output { + Ok("sunrise_libuser::types::HandleRef<'static>".to_string()) } else { Ok(format!("sunrise_libuser::types::{}", if *is_copy && !is_server && !output { "HandleRef" } else { "Handle" })) }, @@ -270,7 +272,7 @@ fn format_cmd(cmd: &Func) -> Result { for line in cmd.doc.lines() { writeln!(s, " /// {}", line).unwrap(); } - writeln!(s, " pub fn {}(&mut self, {}) -> Result<{}, Error> {{", &cmd.name, format_args(&cmd.args, &cmd.ret, false)?, format_ret_ty(&cmd.ret)?).unwrap(); + writeln!(s, " pub fn {}(&mut self, {}) -> Result<{}, Error> {{", &cmd.name, format_args(&cmd.args, &cmd.ret, false)?, format_ret_ty(&cmd.ret, false)?).unwrap(); writeln!(s, " use sunrise_libuser::ipc::Message;").unwrap(); writeln!(s, " let mut buf__ = [0; 0x100];").unwrap(); writeln!(s).unwrap(); @@ -512,7 +514,7 @@ fn gen_call(cmd: &Func) -> Result { match ty { Alias::Array(..) | Alias::Buffer(..) => true, _ => false })) { match item { - Alias::Array(_, bufty) | Alias::Buffer(_, bufty, _) => { + Alias::Array(underlying_ty, bufty) | Alias::Buffer(underlying_ty, bufty, _) => { let (ismut,direction, ty) = match (bufty.get_bits(0..2), bufty.get_bits(2..4)) { (0b01, 0b01) => ("", "in", "buffer"), (0b01, 0b10) => ("", "in", "pointer"), @@ -520,7 +522,18 @@ fn gen_call(cmd: &Func) -> Result { (0b10, 0b10) => ("mut", "out", "pointer"), _ => panic!("Invalid bufty") }; - args += &format!("&{} *msg__.pop_{}_{}().unwrap(), ", ismut, direction, ty); + let realty = get_type(false, underlying_ty, false)?; + if let Alias::Array(..) = item { + // TODO: Make pop_out_buffer and co safe to call. + // BODY: Currently, pop_out_buffer (and other functions of + // BODY: that family) are unsafe to call as they basically + // BODY: allow transmuting variables. We should use a crate + // BODY: like `plain` to ensure that said functions are only + // BODY: callable when it is safe. + args += &format!("unsafe {{ &{} *msg__.pop_{}_{}::<[{}]>().unwrap() }}, ", ismut, direction, ty, realty); + } else { + args += &format!("unsafe {{ &{} *msg__.pop_{}_{}::<{}>().unwrap() }}, ", ismut, direction, ty, realty); + } }, Alias::Object(ty) => { args += &format!("{}Proxy(sunrise_libuser::types::ClientSession(msg__.pop_handle_move().unwrap())), ", ty); @@ -564,7 +577,7 @@ fn gen_call(cmd: &Func) -> Result { writeln!(s, " match ret__ {{").unwrap(); writeln!(s, " Ok(ret) => {{").unwrap(); - let retcount = named_iterator(&cmd.ret, false).count(); + let retcount = named_iterator(&cmd.ret, true).count(); for (idx, (item, _)) in named_iterator(&cmd.ret, true).enumerate().filter(|(_, (ty, _))| !is_raw(ty)) { let ret = if retcount == 1 { @@ -578,14 +591,14 @@ fn gen_call(cmd: &Func) -> Result { }, Alias::Handle(is_copy, ty) => { let (is_ref, handle) = if *is_copy { - ("&", "copy") + (".as_ref()", "copy") } else { ("", "move") }; match (get_handle_type(ty), ty) { - (_, Some(HandleType::ClientSession)) => writeln!(s, "msg__.push_handle_{}({}({}).into_handle());", handle, is_ref, ret).unwrap(), - (Some(_), _) => writeln!(s, "msg__.push_handle_{}({}({}).0);", handle, is_ref, ret).unwrap(), - _ => writeln!(s, "msg__.push_handle_{}({}{})", handle, is_ref, ret).unwrap(), + (_, Some(HandleType::ClientSession)) => writeln!(s, "msg__.push_handle_{}(({}).into_handle(){});", handle, ret, is_ref).unwrap(), + (Some(_), _) => writeln!(s, "msg__.push_handle_{}(({}).0{});", handle, ret, is_ref).unwrap(), + _ => writeln!(s, "msg__.push_handle_{}({});", handle, ret).unwrap(), }; }, Alias::Pid => { @@ -595,9 +608,9 @@ fn gen_call(cmd: &Func) -> Result { } } - if raw_iterator(&cmd.ret, false).count() > 0 { - if named_iterator(&cmd.ret, false).count() == 1 { - let (_, name) = raw_iterator(&cmd.ret, false).next().unwrap(); + if raw_iterator(&cmd.ret, true).count() > 0 { + if named_iterator(&cmd.ret, true).count() == 1 { + let (_, name) = raw_iterator(&cmd.ret, true).next().unwrap(); writeln!(s, "msg__.push_raw({} {{ {}: ret }});", out_raw, name).unwrap(); } else { writeln!(s, "msg__.push_raw({} {{", out_raw).unwrap(); @@ -631,7 +644,7 @@ pub fn generate_trait(ifacename: &str, interface: &Interface) -> String { } writeln!(s, "pub trait {} {{", trait_name).unwrap(); for cmd in &interface.funcs { - match format_args(&cmd.args, &cmd.ret, true).and_then(|v| format_ret_ty(&cmd.ret).map(|u| (v, u))) { + match format_args(&cmd.args, &cmd.ret, true).and_then(|v| format_ret_ty(&cmd.ret, true).map(|u| (v, u))) { Ok((args, ret)) => { for line in cmd.doc.lines() { writeln!(s, "/// {}", line).unwrap(); From 67901a3be1743f606a643e5d4dd9b3d8e0fb9041 Mon Sep 17 00:00:00 2001 From: roblabla Date: Thu, 4 Jul 2019 14:35:10 +0000 Subject: [PATCH 16/16] Libuser: Implement smart buffers We now support auto-selected (smart) buffers. When the user fetches an interface containing methods with smart-buffers, it will fetch the Pointer Buffer Size. Depending on whether the smart buffer fits the pointer buffer or not, it will be passed as either a Pointer or a Buffer. In order to implement this, a simple dispatch for Control messages was implemented. The QueryPointerBufferSize function was implemented as well. The pointer buffer size is fetched and cached on interface creation. Only interfaces actually containing smart buffers will fetch this information, to avoid doing a bunch of useless IPC requests. --- libuser/src/ipc/mod.rs | 111 +++++++++++++++++++++++++++------ libuser/src/ipc/server.rs | 25 ++++++++ libuser/src/types.rs | 13 ++++ swipc-gen/src/gen_rust_code.rs | 77 +++++++++++++++-------- 4 files changed, 181 insertions(+), 45 deletions(-) diff --git a/libuser/src/ipc/mod.rs b/libuser/src/ipc/mod.rs index 4cb917157..2b4606992 100644 --- a/libuser/src/ipc/mod.rs +++ b/libuser/src/ipc/mod.rs @@ -196,7 +196,7 @@ impl SizedIPCBuffer for T { fn is_cool(addr: usize, size: usize) -> bool { size == core::mem::size_of::() && - (addr % core::mem::align_of::()) == 0 + (addr % core::mem::align_of::()) == 0 && addr != 0 } unsafe fn from_raw_parts<'a>(addr: usize, _size: usize) -> &'a Self { @@ -215,7 +215,7 @@ impl SizedIPCBuffer for [T] { fn is_cool(addr: usize, size: usize) -> bool { size % core::mem::size_of::() == 0 && size != 0 && - (addr % core::mem::align_of::()) == 0 + (addr % core::mem::align_of::()) == 0 && addr != 0 } unsafe fn from_raw_parts<'a>(addr: usize, size: usize) -> &'a Self { @@ -229,11 +229,11 @@ impl SizedIPCBuffer for [T] { impl<'a> IPCBuffer<'a> { /// Creates a Type-A IPCBuffer from the given reference. - fn out_buffer(data: &T, flags: u8) -> IPCBuffer { + fn out_buffer(data: Option<&T>, flags: u8) -> IPCBuffer { IPCBuffer { - addr: data as *const T as *const u8 as usize as u64, + addr: data.map(|v| v as *const T as *const u8 as usize as u64).unwrap_or(0), // The dereference is necessary because &T implements SizedIPCBuffer too... - size: (*data).size() as u64, + size: data.map(|v| (*v).size() as u64).unwrap_or(0), ty: IPCBufferType::A { flags }, @@ -242,11 +242,11 @@ impl<'a> IPCBuffer<'a> { } /// Creates a Type-B IPCBuffer from the given reference. - fn in_buffer(data: &mut T, flags: u8) -> IPCBuffer { + fn in_buffer(mut data: Option<&mut T>, flags: u8) -> IPCBuffer { IPCBuffer { - addr: data as *mut T as *const u8 as usize as u64, + addr: data.as_mut().map(|v| *v as *mut T as *const u8 as usize as u64).unwrap_or(0), // The dereference is necessary because &T implements SizedIPCBuffer too... - size: (*data).size() as u64, + size: data.as_mut().map(|v| (**v).size() as u64).unwrap_or(0), ty: IPCBufferType::B { flags }, @@ -258,11 +258,11 @@ impl<'a> IPCBuffer<'a> { /// /// If has_u16_size is true, the size of the pointer will be written after /// the raw data. This is only used when sending client-sized arrays. - fn in_pointer(data: &mut T, has_u16_size: bool) -> IPCBuffer { + fn in_pointer(mut data: Option<&mut T>, has_u16_size: bool) -> IPCBuffer { IPCBuffer { - addr: data as *mut T as *const u8 as usize as u64, + addr: data.as_mut().map(|v| *v as *mut T as *const u8 as usize as u64).unwrap_or(0), // The dereference is necessary because &T implements SizedIPCBuffer too... - size: (*data).size() as u64, + size: data.as_mut().map(|v| (**v).size() as u64).unwrap_or(0), ty: IPCBufferType::C { has_u16_size }, @@ -273,11 +273,11 @@ impl<'a> IPCBuffer<'a> { /// Creates a Type-X IPCBuffer from the given reference. /// /// The counter defines which type-C buffer this should be copied into. - fn out_pointer(data: &T, counter: u8) -> IPCBuffer { + fn out_pointer(data: Option<&T>, counter: u8) -> IPCBuffer { IPCBuffer { - addr: data as *const T as *const u8 as usize as u64, + addr: data.map(|v| v as *const T as *const u8 as usize as u64).unwrap_or(0), // The dereference is necessary because &T implements SizedIPCBuffer too... - size: (*data).size() as u64, + size: data.map(|v| (*v).size() as u64).unwrap_or(0), ty: IPCBufferType::X { counter }, @@ -606,13 +606,13 @@ where /// Push an OutBuffer (type-A buffer) backed by the specified data. pub fn push_out_buffer(&mut self, data: &'a T) -> &mut Self { - self.buffers.push(IPCBuffer::out_buffer(data, 0)); + self.buffers.push(IPCBuffer::out_buffer(Some(data), 0)); self } /// Push an InBuffer (type-B buffer) backed by the specified data. pub fn push_in_buffer(&mut self, data: &'a mut T) -> &mut Self { - self.buffers.push(IPCBuffer::in_buffer(data, 0)); + self.buffers.push(IPCBuffer::in_buffer(Some(data), 0)); self } @@ -622,7 +622,7 @@ where /// to the Raw Data. See Buffer 0xA on the IPC Marshalling page of /// switchbrew. pub fn push_in_pointer(&mut self, data: &'a mut T, has_u16_count: bool) -> &mut Self { - self.buffers.push(IPCBuffer::in_pointer(data, has_u16_count)); + self.buffers.push(IPCBuffer::in_pointer(Some(data), has_u16_count)); self } @@ -633,10 +633,35 @@ where /// switchbrew. pub fn push_out_pointer(&mut self, data: &'a T) -> &mut Self { let index = self.buffers.iter().filter(|buf| buf.buftype().is_type_x()).count(); - self.buffers.push(IPCBuffer::out_pointer(data, index as u8)); + self.buffers.push(IPCBuffer::out_pointer(Some(data), index as u8)); self } + pub fn push_in_smart_pointer(&mut self, pointer_buffer_size: u16, data: &'a mut T) -> &mut Self { + if pointer_buffer_size != 0 && (*data).size() <= usize::from(pointer_buffer_size) { + self.buffers.push(IPCBuffer::in_pointer(Some(data), false)); + self.buffers.push(IPCBuffer::in_buffer::(None, 0)); + } else { + self.buffers.push(IPCBuffer::in_pointer::(None, false)); + self.buffers.push(IPCBuffer::in_buffer(Some(data), 0)); + } + self + } + + pub fn push_out_smart_pointer(&mut self, pointer_buffer_size: u16, data: &'a T) -> &mut Self { + let index = self.buffers.iter().filter(|buf| buf.buftype().is_type_x()).count(); + if pointer_buffer_size != 0 && (*data).size() <= usize::from(pointer_buffer_size) { + self.buffers.push(IPCBuffer::out_pointer(Some(data), index as u8)); + self.buffers.push(IPCBuffer::out_buffer::(None, 0)); + } else { + self.buffers.push(IPCBuffer::out_pointer::(None, index as u8)); + self.buffers.push(IPCBuffer::out_buffer(Some(data), 0)); + } + self + } + + + /// Send a Pid with this IPC request. /// /// If `pid` is None, sends the current process' Pid. If it's Some, then it @@ -675,6 +700,56 @@ where } } + pub unsafe fn pop_in_smart_buffer<'b, T: SizedIPCBuffer + ?Sized>(&mut self) -> Result<&'b T, Error> { + let pointer = self.buffers.iter().position(|buf| buf.buftype().is_type_x()) + .and_then(|pos| self.buffers.pop_at(pos)) + .ok_or_else(|| LibuserError::InvalidIpcBufferCount)?; + + let buffer = self.buffers.iter().position(|buf| buf.buftype().is_type_a()) + .and_then(|pos| self.buffers.pop_at(pos)) + .ok_or_else(|| LibuserError::InvalidIpcBufferCount)?; + + let (addr, size) = if pointer.addr != 0 { + (pointer.addr, pointer.size) + } else { + (buffer.addr, buffer.size) + }; + + let addr = usize::try_from(addr).map_err(|_| LibuserError::InvalidIpcBuffer)?; + let size = usize::try_from(size).map_err(|_| LibuserError::InvalidIpcBuffer)?; + + if T::is_cool(addr, size) { + Ok(T::from_raw_parts(addr, size)) + } else { + Err(LibuserError::InvalidIpcBuffer.into()) + } + } + + pub unsafe fn pop_out_smart_buffer<'b, T: SizedIPCBuffer + ?Sized>(&mut self) -> Result<&'b mut T, Error> { + let pointer = self.buffers.iter().position(|buf| buf.buftype().is_type_x()) + .and_then(|pos| self.buffers.pop_at(pos)) + .ok_or_else(|| LibuserError::InvalidIpcBufferCount)?; + + let buffer = self.buffers.iter().position(|buf| buf.buftype().is_type_a()) + .and_then(|pos| self.buffers.pop_at(pos)) + .ok_or_else(|| LibuserError::InvalidIpcBufferCount)?; + + let (addr, size) = if pointer.addr != 0 { + (pointer.addr, pointer.size) + } else { + (buffer.addr, buffer.size) + }; + + let addr = usize::try_from(addr).map_err(|_| LibuserError::InvalidIpcBuffer)?; + let size = usize::try_from(size).map_err(|_| LibuserError::InvalidIpcBuffer)?; + + if T::is_cool(addr, size) { + Ok(T::from_raw_parts_mut(addr, size)) + } else { + Err(LibuserError::InvalidIpcBuffer.into()) + } + } + // TODO: Move pack to a non-generic function // BODY: Right now the pack and unpack functions are duplicated for every // BODY: instanciation of Message. This probably has a huge penalty on diff --git a/libuser/src/ipc/server.rs b/libuser/src/ipc/server.rs index 80dc2b597..8d080ed07 100644 --- a/libuser/src/ipc/server.rs +++ b/libuser/src/ipc/server.rs @@ -292,6 +292,31 @@ where Ok(false) }, Some((2, _)) => Ok(true), + Some((5, 0)) | Some((7, 0)) => { + // ConvertCurrentObjectToDomain, unsupported + Ok(true) + }, + Some((5, 1)) | Some((7, 1)) => { + // CopyFromCurrentDomain, unsupported + Ok(true) + }, + Some((5, 2)) | Some((7, 2)) => { + // CloneCurrentObject, unsupported + Ok(true) + }, + Some((5, 3)) | Some((7, 3)) => { + // QueryPointerBufferSize + let mut msg__ = Message::::new_response(None); + msg__.push_raw(self.pointer_buf.len() as u16); + msg__.pack(&mut self.buf[..]); + self.handle.reply(&mut self.buf[..])?; + Ok(false) + }, + Some((5, 4)) | Some((7, 4)) => { + // CloneCurrentObjectEx, unsupported + Ok(true) + }, + _ => Ok(true) } } diff --git a/libuser/src/types.rs b/libuser/src/types.rs index a69fd6cc2..717742146 100644 --- a/libuser/src/types.rs +++ b/libuser/src/types.rs @@ -107,6 +107,19 @@ impl ClientSession { .map_err(|v| v.into()) } + pub fn query_pointer_buffer(&self) -> Result { + // Use a very small buffer to avoid stackoverflows - we really don't + // need a big one here anyways. + let mut data = [0; 0x10]; + let mut msg = Message::<(), [_; 0], [_; 0], [_; 0]>::new_request(None, 3); + msg.set_ty(MessageTy::Control); + msg.pack(&mut data[..]); + self.send_sync_request_with_user_buffer(&mut data[..])?; + let msg = Message::::unpack(&data[..]); + msg.error()?; + Ok(msg.raw()) + } + /// Consumes the session, returning the underlying handle. Note that closing /// a Handle without sending a close IPC message will leak the object in the /// sysmodule. You should always reconstruct the ClientSession from the diff --git a/swipc-gen/src/gen_rust_code.rs b/swipc-gen/src/gen_rust_code.rs index 0fff1f907..d2333453e 100644 --- a/swipc-gen/src/gen_rust_code.rs +++ b/swipc-gen/src/gen_rust_code.rs @@ -319,9 +319,9 @@ fn format_cmd(cmd: &Func) -> Result { // C Buffer (2, 2, false) => writeln!(s, " msg__.push_in_pointer({}, {});", argname, !ty.get_bit(4)).unwrap(), // Smart A+X - (1, 0, true) => return Err(Error::UnsupportedStruct), + (1, 0, true) => writeln!(s, " msg__.push_out_smart_pointer(self.1, {});", argname).unwrap(), // Smart B+C - (2, 0, true) => return Err(Error::UnsupportedStruct), + (2, 0, true) => writeln!(s, " msg__.push_in_smart_pointer(self.1, {});", argname).unwrap(), _ => panic!("Illegal buffer type: {}", ty) } }, @@ -336,9 +336,9 @@ fn format_cmd(cmd: &Func) -> Result { // C Buffer (2, 2, false) => writeln!(s, " msg__.push_in_pointer({}, {});", argname, !ty.get_bit(4)).unwrap(), // Smart A+X - (1, 0, true) => return Err(Error::UnsupportedStruct), + (1, 0, true) => writeln!(s, " msg__.push_out_smart_pointer(self.1, {});", argname).unwrap(), // Smart B+C - (2, 0, true) => return Err(Error::UnsupportedStruct), + (2, 0, true) => writeln!(s, " msg__.push_in_smart_pointer(self.1, {});", argname).unwrap(), _ => panic!("Illegal buffer type: {}", ty) } }, @@ -514,29 +514,35 @@ fn gen_call(cmd: &Func) -> Result { match ty { Alias::Array(..) | Alias::Buffer(..) => true, _ => false })) { match item { - Alias::Array(underlying_ty, bufty) | Alias::Buffer(underlying_ty, bufty, _) => { - let (ismut,direction, ty) = match (bufty.get_bits(0..2), bufty.get_bits(2..4)) { - (0b01, 0b01) => ("", "in", "buffer"), - (0b01, 0b10) => ("", "in", "pointer"), - (0b10, 0b01) => ("mut", "out", "buffer"), - (0b10, 0b10) => ("mut", "out", "pointer"), - _ => panic!("Invalid bufty") + Alias::Array(_, bufty) | Alias::Buffer(_, bufty, _) => { + let (ismut,direction, ty) = match (bufty.get_bits(0..2), bufty.get_bits(2..4), bufty.get_bit(5)) { + (0b01, _, true) => ("", "in", "smart_buffer"), + (0b10, _, true) => ("mut", "out", "smart_buffer"), + (0b01, 0b01, false) => ("", "in", "buffer"), + (0b01, 0b10, false) => ("", "in", "pointer"), + (0b10, 0b01, false) => ("mut", "out", "buffer"), + (0b10, 0b10, false) => ("mut", "out", "pointer"), + (direction, ty, smart) => panic!("Invalid bufty while handling {:?}: {:x}, {:x} {} {:x}", cmd, direction, ty, smart, bufty) }; - let realty = get_type(false, underlying_ty, false)?; - if let Alias::Array(..) = item { - // TODO: Make pop_out_buffer and co safe to call. - // BODY: Currently, pop_out_buffer (and other functions of - // BODY: that family) are unsafe to call as they basically - // BODY: allow transmuting variables. We should use a crate - // BODY: like `plain` to ensure that said functions are only - // BODY: callable when it is safe. - args += &format!("unsafe {{ &{} *msg__.pop_{}_{}::<[{}]>().unwrap() }}, ", ismut, direction, ty, realty); + let realty = get_type(false, item, false)?; + let realty = if realty.starts_with("&mut") { + realty.trim_start_matches("&mut") + } else if realty.starts_with("&") { + realty.trim_start_matches("&") } else { - args += &format!("unsafe {{ &{} *msg__.pop_{}_{}::<{}>().unwrap() }}, ", ismut, direction, ty, realty); - } + &*realty + }; + + // TODO: Make pop_out_buffer and co safe to call. + // BODY: Currently, pop_out_buffer (and other functions of + // BODY: that family) are unsafe to call as they basically + // BODY: allow transmuting variables. We should use a crate + // BODY: like `plain` to ensure that said functions are only + // BODY: callable when it is safe. + args += &format!("unsafe {{ &{} *msg__.pop_{}_{}::<{}>().unwrap() }}, ", ismut, direction, ty, realty); }, Alias::Object(ty) => { - args += &format!("{}Proxy(sunrise_libuser::types::ClientSession(msg__.pop_handle_move().unwrap())), ", ty); + args += &format!("{}Proxy::from(sunrise_libuser::types::ClientSession(msg__.pop_handle_move().unwrap())), ", ty); }, Alias::Handle(is_copy, ty) => { let handle = if *is_copy { @@ -693,7 +699,14 @@ pub fn generate_proxy(ifacename: &str, interface: &Interface) -> String { writeln!(s, "/// {}", line).unwrap(); } writeln!(s, "#[derive(Debug)]").unwrap(); - writeln!(s, "pub struct {}(ClientSession);", struct_name).unwrap(); + // Detect the presence of smart buffers on this interface. If there is one + // present, we want to cache the pointer buffer size in the structure. + let has_smart_buffer = interface.funcs.iter().any(|cmd| cmd.args.iter().chain(cmd.ret.iter()).any(|(arg, _)| if let Alias::Buffer(_, ty, _) | Alias::Array(_, ty) = arg { ty.get_bit(5) } else { false } )); + if has_smart_buffer { + writeln!(s, "pub struct {}(ClientSession, u16);", struct_name).unwrap(); + } else { + writeln!(s, "pub struct {}(ClientSession);", struct_name).unwrap(); + } writeln!(s).unwrap(); writeln!(s, "impl From<{}> for ClientSession {{", struct_name).unwrap(); writeln!(s, " fn from(sess: {}) -> ClientSession {{", struct_name).unwrap(); @@ -703,7 +716,17 @@ pub fn generate_proxy(ifacename: &str, interface: &Interface) -> String { writeln!(s).unwrap(); writeln!(s, "impl From for {} {{", struct_name).unwrap(); writeln!(s, " fn from(sess: ClientSession) -> {} {{", struct_name).unwrap(); - writeln!(s, " {}(sess)", struct_name).unwrap(); + + // Cache the pointer buffer size in the structure only if we have smart + // buffers on the interface. + if has_smart_buffer { + // Assume that if there's an error, the remote doesn't support the + // pointer buffer at all. + writeln!(s, " let pointer_buffer = sess.query_pointer_buffer().unwrap_or(0);").unwrap(); + writeln!(s, " {}(sess, pointer_buffer)", struct_name).unwrap(); + } else { + writeln!(s, " {}(sess)", struct_name).unwrap(); + } writeln!(s, " }}").unwrap(); writeln!(s, "}}").unwrap(); @@ -728,7 +751,7 @@ pub fn generate_proxy(ifacename: &str, interface: &Interface) -> String { let mut service_name = service.to_string(); service_name += &"\\0"; writeln!(s, r#" let _ = match syscalls::connect_to_named_port("{}") {{"#, service_name).unwrap(); - writeln!(s, " Ok(s) => return Ok({}(s)),", struct_name).unwrap(); + writeln!(s, " Ok(s) => return Ok({}::from(s)),", struct_name).unwrap(); writeln!(s, " Err(KernelError::NoSuchEntry) => syscalls::sleep_thread(0),").unwrap(); writeln!(s, " Err(err) => Err(err)?").unwrap(); writeln!(s, " }};").unwrap(); @@ -744,7 +767,7 @@ pub fn generate_proxy(ifacename: &str, interface: &Interface) -> String { writeln!(s, r#" core::mem::transmute(*b"{}")"#, service_name).unwrap(); writeln!(s, " }};").unwrap(); writeln!(s, " let _ = match sunrise_libuser::sm::IUserInterfaceProxy::raw_new()?.get_service(svcname) {{").unwrap(); - writeln!(s, " Ok(s) => return Ok({}(s)),", struct_name).unwrap(); + writeln!(s, " Ok(s) => return Ok({}::from(s)),", struct_name).unwrap(); writeln!(s, " Err(Error::Sm(SmError::ServiceNotRegistered, ..)) => syscalls::sleep_thread(0),").unwrap(); writeln!(s, " Err(err) => return Err(err)").unwrap(); writeln!(s, " }};").unwrap();