Skip to content

Commit

Permalink
Libuser: Implement smart buffers
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
roblabla committed Jul 4, 2019
1 parent 4fa436c commit 67901a3
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 45 deletions.
111 changes: 93 additions & 18 deletions libuser/src/ipc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ impl<T> SizedIPCBuffer for T {

fn is_cool(addr: usize, size: usize) -> bool {
size == core::mem::size_of::<T>() &&
(addr % core::mem::align_of::<T>()) == 0
(addr % core::mem::align_of::<T>()) == 0 && addr != 0
}

unsafe fn from_raw_parts<'a>(addr: usize, _size: usize) -> &'a Self {
Expand All @@ -215,7 +215,7 @@ impl<T> SizedIPCBuffer for [T] {

fn is_cool(addr: usize, size: usize) -> bool {
size % core::mem::size_of::<T>() == 0 && size != 0 &&
(addr % core::mem::align_of::<T>()) == 0
(addr % core::mem::align_of::<T>()) == 0 && addr != 0
}

unsafe fn from_raw_parts<'a>(addr: usize, size: usize) -> &'a Self {
Expand All @@ -229,11 +229,11 @@ impl<T> SizedIPCBuffer for [T] {

impl<'a> IPCBuffer<'a> {
/// Creates a Type-A IPCBuffer from the given reference.
fn out_buffer<T: SizedIPCBuffer + ?Sized>(data: &T, flags: u8) -> IPCBuffer {
fn out_buffer<T: SizedIPCBuffer + ?Sized>(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
},
Expand All @@ -242,11 +242,11 @@ impl<'a> IPCBuffer<'a> {
}

/// Creates a Type-B IPCBuffer from the given reference.
fn in_buffer<T: SizedIPCBuffer + ?Sized>(data: &mut T, flags: u8) -> IPCBuffer {
fn in_buffer<T: SizedIPCBuffer + ?Sized>(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
},
Expand All @@ -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<T: SizedIPCBuffer + ?Sized>(data: &mut T, has_u16_size: bool) -> IPCBuffer {
fn in_pointer<T: SizedIPCBuffer + ?Sized>(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
},
Expand All @@ -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<T: SizedIPCBuffer + ?Sized>(data: &T, counter: u8) -> IPCBuffer {
fn out_pointer<T: SizedIPCBuffer + ?Sized>(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
},
Expand Down Expand Up @@ -606,13 +606,13 @@ where

/// Push an OutBuffer (type-A buffer) backed by the specified data.
pub fn push_out_buffer<T: SizedIPCBuffer + ?Sized>(&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<T: SizedIPCBuffer + ?Sized>(&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
}

Expand All @@ -622,7 +622,7 @@ where
/// to the Raw Data. See Buffer 0xA on the IPC Marshalling page of
/// switchbrew.
pub fn push_in_pointer<T: SizedIPCBuffer + ?Sized>(&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
}

Expand All @@ -633,10 +633,35 @@ where
/// switchbrew.
pub fn push_out_pointer<T: SizedIPCBuffer + ?Sized>(&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<T: SizedIPCBuffer + ?Sized>(&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::<T>(None, 0));
} else {
self.buffers.push(IPCBuffer::in_pointer::<T>(None, false));
self.buffers.push(IPCBuffer::in_buffer(Some(data), 0));
}
self
}

pub fn push_out_smart_pointer<T: SizedIPCBuffer + ?Sized>(&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::<T>(None, 0));
} else {
self.buffers.push(IPCBuffer::out_pointer::<T>(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
Expand Down Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions libuser/src/ipc/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<u16, [_; 0], [_; 0], [_; 0]>::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)
}
}
Expand Down
13 changes: 13 additions & 0 deletions libuser/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,19 @@ impl ClientSession {
.map_err(|v| v.into())
}

pub fn query_pointer_buffer(&self) -> Result<u16, Error> {
// 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::<u16, [_; 0], [_; 0], [_; 0]>::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
Expand Down
77 changes: 50 additions & 27 deletions swipc-gen/src/gen_rust_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,9 +319,9 @@ fn format_cmd(cmd: &Func) -> Result<String, Error> {
// 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)
}
},
Expand All @@ -336,9 +336,9 @@ fn format_cmd(cmd: &Func) -> Result<String, Error> {
// 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)
}
},
Expand Down Expand Up @@ -514,29 +514,35 @@ fn gen_call(cmd: &Func) -> Result<String, Error> {
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 {
Expand Down Expand Up @@ -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();
Expand All @@ -703,7 +716,17 @@ pub fn generate_proxy(ifacename: &str, interface: &Interface) -> String {
writeln!(s).unwrap();
writeln!(s, "impl From<ClientSession> 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();

Expand All @@ -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();
Expand All @@ -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();
Expand Down

0 comments on commit 67901a3

Please sign in to comment.