blob: e29149414f759dfb01ef5d380f9f2742524af669 [file]
// Copyright 2018 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
//! BufferChain is a list of buffers allocated from the PMM.
//!
//! It's designed for use with channel messages. Pages backing a BufferChain are marked as
//! vm_page_state::IPC.
//!
//! The BufferChain object itself lives *inside* its first buffer. Here's what it looks like:
//!
//! +--------------------------------+ +--------------------------------+
//! | page | | page |
//! |+------------------------------+| |+------------------------------+|
//! || Buffer |+---->|| Buffer ||
//! || raw_data: +---------------+|| || raw_data: +--------------+||
//! || | BufferChain ||| || | message data |||
//! || reserved -> | ~~~~~~~~~~~~~ ||| || reserved = 0 | continued |||
//! || | message data ||| || | |||
//! || +---------------+|| || +--------------+||
//! |+------------------------------+| |+------------------------------+|
//! +--------------------------------+ +--------------------------------+
//!
//! BufferChain does not dynamically allocate. An initial Alloc() call allocates a list of
//! pages that will be used to build the BufferChain. This allocation can exceed the size
//! actually used by the buffer chain and the excess buffers can be freed with a call to
//! FreeUnusedBuffers(). The motivation for sometimes allocating more than needed and later
//! freeing is that the number of needed buffers is sometimes initially unknown and it is
//! presumed to be more efficient to do a single allocation than multiple allocations.
//!
//! BufferChain uses a private PageCache to improve performance under load by
//! avoiding contention on the PMM. The page cache is tunable by the kernel
//! command line parameter kernel.bufferchain.reserve-pages.
use crate::page_cache::{PageCache, PageList};
use crate::user_copy::{UserInPtr, UserOutPtr};
use crate::vm::page::{VmPage, VmPageDoublyLinkedList};
use crate::vm::page_state::VmPageState;
use crate::vm::physmap;
use boot_options::BootOptions;
use core::mem::{self, MaybeUninit, size_of};
use core::pin::Pin;
use core::ptr::{self, NonNull};
use core::{cmp, slice};
use fbl::{Canary, SinglyLinkedList, SinglyLinkedListContainable, SinglyLinkedListNode};
use lazy_init::LazyInit;
use page_bindings::vm_page_state;
use pin_init::{PinInit, pin_data, pin_init, pinned_drop, stack_pin_init};
use zr::static_assert;
use zx_status::Status;
static PAGE_CACHE: LazyInit<PageCache> = LazyInit::uninit();
fn page_cache() -> &'static PageCache {
&PAGE_CACHE
}
fn initialize_page_cache(_level: init::LkInitLevel) {
let reserve_pages = BootOptions::get().bufferchain_reserve_pages as usize;
let cache =
PageCache::new(reserve_pages).expect("failed to initialize buffer chain page cache");
// SAFETY: Single-threaded initialization during early boot before concurrency.
unsafe {
PAGE_CACHE.init(cache);
}
}
// Initialize the cache after the percpu data structures are initialized.
init::lk_init_hook!(
buffer_chain_page_cache_init,
initialize_page_cache,
init::LK_INIT_LEVEL_KERNEL
);
/// Size of a buffer backing `BufferChain`.
const BUFFER_SIZE: usize = page::SIZE;
/// Size of the header fields in a `Buffer`.
const BUFFER_FIELDS_SIZE: usize = 16;
/// `RAW_DATA_SIZE` is the maximum number of bytes that can fit in a single `Buffer`.
///
/// However, not every `Buffer` in a `BufferChain` can store this many bytes. The first `Buffer`
/// in a `BufferChain` is special because it also stores the chain itself. See also
/// `CONTIGUOUS_SIZE`.
const RAW_DATA_SIZE: usize = BUFFER_SIZE - BUFFER_FIELDS_SIZE;
/// A single fixed-size buffer within a `BufferChain`, occupying an entire physical page.
#[repr(C)]
#[derive(SinglyLinkedListContainable)]
struct Buffer {
#[sll_node]
node: SinglyLinkedListNode<Buffer>,
canary: Canary<{ fbl::magic(b"BUFC") }>,
reserved: u32,
raw_data: MaybeUninit<[u8; RAW_DATA_SIZE]>,
}
static_assert!(size_of::<Buffer>() == BUFFER_SIZE);
impl Buffer {
/// Returns a raw pointer to the usable data payload in this buffer.
fn data(&self) -> *const u8 {
self.canary.assert();
// SAFETY: `self.raw_data` has length `RAW_DATA_SIZE` and `reserved <= RAW_DATA_SIZE`.
unsafe { self.raw_data.as_ptr().cast::<u8>().add(self.reserved as usize) }
}
/// Returns a mutable raw pointer to the usable data payload in this buffer.
fn data_mut(&mut self) -> *mut u8 {
self.canary.assert();
// SAFETY: `self.raw_data` has length `RAW_DATA_SIZE` and `reserved <= RAW_DATA_SIZE`.
unsafe { self.raw_data.as_mut_ptr().cast::<u8>().add(self.reserved as usize) }
}
/// Returns the capacity in bytes of the usable data payload in this buffer.
fn size(&self) -> usize {
RAW_DATA_SIZE - (self.reserved as usize)
}
/// Returns a mutable slice of the usable data payload in this buffer as maybe-uninitialized
/// bytes.
fn as_mut_slice(&mut self) -> &mut [MaybeUninit<u8>] {
self.canary.assert();
// SAFETY: `self.raw_data` has length `RAW_DATA_SIZE` and `reserved <= RAW_DATA_SIZE`.
unsafe {
slice::from_raw_parts_mut(
self.raw_data.as_mut_ptr().cast::<MaybeUninit<u8>>().add(self.reserved as usize),
self.size(),
)
}
}
/// Initializes an uninitialized `Buffer` in the physical map of the given `page`.
///
/// # Safety
///
/// `page_raw` must be a valid pointer to an allocated `VmPage` with `ALLOC` state.
unsafe fn init_in_page(page_raw: NonNull<VmPage>, reserved: u32) -> NonNull<Buffer> {
// SAFETY: Caller guarantees `page_raw` is a valid allocated VmPage.
let page = unsafe { page_raw.as_ref() };
debug_assert_eq!(page.state(), VmPageState(vm_page_state::ALLOC));
// SAFETY: `page` is owned and in the `ALLOC` state.
unsafe { page.set_state(VmPageState(vm_page_state::IPC)) };
let va =
ptr::with_exposed_provenance_mut::<Buffer>(physmap::paddr_to_physmap(page.paddr()).0);
// SAFETY: `va` is mapped in the kernel physmap for this physical page.
unsafe {
va.write(Buffer {
node: SinglyLinkedListNode::new(),
canary: Canary::new(),
reserved,
raw_data: MaybeUninit::uninit(),
});
NonNull::new_unchecked(va)
}
}
/// Returns a slice of the usable data payload in this buffer as maybe-uninitialized bytes.
#[cfg(ktest)]
fn as_slice(&self) -> &[MaybeUninit<u8>] {
self.canary.assert();
// SAFETY: `self.raw_data` has length `RAW_DATA_SIZE` and `reserved <= RAW_DATA_SIZE`.
unsafe {
slice::from_raw_parts(
self.raw_data.as_ptr().cast::<MaybeUninit<u8>>().add(self.reserved as usize),
self.size(),
)
}
}
}
/// `BufferChain` is a list of buffers allocated from the PMM, designed for channel messages.
///
/// The `BufferChain` object itself lives *inside* its first buffer and contains intrusive lists
/// (`used_pages`, `unused_pages`) that track physical memory pages backing the buffer chain.
/// Because intrusive list heads contain self-referential pointers, `BufferChain` is pinned
/// (`#[pin_data(PinnedDrop)]`) and cannot be moved in memory. It has no independent heap or
/// stack allocation; its storage is located directly within the `raw_data` payload of the first
/// physical page `Buffer`.
// Take care when adding fields as BufferChain lives inside the first buffer of buffers_.
#[pin_data(PinnedDrop)]
#[repr(C)]
pub struct BufferChain {
buffers: SinglyLinkedList<NonNull<Buffer>>,
// Iterator pointing to the last valid element in the buffer.
buffer_tail: Option<NonNull<Buffer>>,
// Position of the next byte to write in the Buffer pointed to by |buffer_tail|.
buffer_offset: usize,
// used_pages is a list of VmPage descriptors for the pages that back buffers.
#[pin]
used_pages: VmPageDoublyLinkedList,
// unused_pages is a list of VmPage descriptors for pages that have been allocated but have
// not yet been used. These pages will be migrated to used_pages once they are needed for
// buffers.
#[pin]
unused_pages: VmPageDoublyLinkedList,
}
// SAFETY: `BufferChain` owns its backing memory and can be transferred across threads.
unsafe impl Send for BufferChain {}
// SAFETY: Concurrent reads to const methods are safe.
unsafe impl Sync for BufferChain {}
/// Size of `BufferChain` which lives inside the first buffer.
const BUFFER_CHAIN_SIZE: usize = size_of::<BufferChain>();
/// `CONTIGUOUS_SIZE` is the number of bytes guaranteed to be stored contiguously in any buffer.
pub const CONTIGUOUS_SIZE: usize = RAW_DATA_SIZE - BUFFER_CHAIN_SIZE;
#[pinned_drop]
impl PinnedDrop for BufferChain {
fn drop(self: Pin<&mut Self>) {
// SAFETY: We have pinned mutable access during drop.
let this = unsafe { self.get_unchecked_mut() };
debug_assert!(this.used_pages.is_empty());
debug_assert!(this.unused_pages.is_empty());
}
}
/// Trait abstracting reading from either user memory or kernel memory for append operations.
// |PTR_IN| is a user_in_ptr-like type.
trait SourceReader {
fn copy_to(&mut self, dst: &mut [MaybeUninit<u8>]) -> Result<(), Status>;
}
/// Adapter for copying from userspace pointers.
struct UserSource(UserInPtr<u8>);
impl SourceReader for UserSource {
fn copy_to(&mut self, dst: &mut [MaybeUninit<u8>]) -> Result<(), Status> {
let len = dst.len();
self.0.copy_slice_from_user(dst)?;
self.0 = self.0.byte_offset(len as isize);
Ok(())
}
}
/// Adapter for copying from kernel slices.
// Makes a &[u8] look like a SourceReader.
// Sometimes we need to copy data from kernel space. KernelSource allows us to implement the copy
// logic once for both &[u8] and UserInPtr<u8>.
struct KernelSource<'a>(&'a [u8]);
impl SourceReader for KernelSource<'_> {
fn copy_to(&mut self, dst: &mut [MaybeUninit<u8>]) -> Result<(), Status> {
let len = dst.len();
let src = &self.0[..len];
// SAFETY: `dst` has length `len` and `src` has length `len`.
unsafe {
ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr().cast::<u8>(), len);
}
self.0 = &self.0[len..];
Ok(())
}
}
impl BufferChain {
/// Creates a `BufferChain` with enough buffers to store `size` bytes.
///
/// The chain is initialized in-place inside the first buffer. It must be freed with
/// `BufferChain::free`.
///
/// Returns `Err(Status)` on error.
pub fn alloc(size: usize) -> Result<NonNull<Self>, Status> {
let size = size.checked_add(BUFFER_CHAIN_SIZE).ok_or(Status::OUT_OF_RANGE)?;
let num_buffers = size.div_ceil(RAW_DATA_SIZE);
// Allocate a list of pages. If `init_in_pages` fails, `temp_pages` automatically frees
// any remaining pages back to the PMM upon drop.
stack_pin_init!(let temp_pages = PageList::new());
page_cache().alloc(num_buffers, temp_pages.as_mut())?;
// SAFETY: `temp_pages` contains the allocated pages from the page cache.
let chain = unsafe {
let pages = temp_pages.get_unchecked_mut();
Self::init_in_pages(pages.list_mut())?
};
Ok(chain)
}
/// Frees `chain` and all its backing buffers and pages.
///
/// # Safety
///
/// `chain` must be a valid, uniquely owned pointer returned from `BufferChain::alloc`.
pub unsafe fn free(mut chain: NonNull<Self>) {
// SAFETY: Caller guarantees `chain` is valid and uniquely owned.
let this = unsafe { chain.as_mut() };
// Remove the buffers and vm_page_t's from the chain *before* destroying it.
let mut buffers = mem::replace(&mut this.buffers, SinglyLinkedList::new());
stack_pin_init!(let all_pages = PageList::new());
// SAFETY: `all_pages` is pinned on the stack; mutating list does not move `all_pages`.
let pages = unsafe { all_pages.as_mut().get_unchecked_mut() };
pages.splice(&mut this.used_pages);
pages.splice(&mut this.unused_pages);
// Now both `this.used_pages` and `this.unused_pages` are completely empty.
// SAFETY: `chain` points to initialized `BufferChain`.
unsafe {
ptr::drop_in_place(chain.as_ptr());
}
while let Some(buf) = buffers.pop_front() {
// SAFETY: `buf` points to an initialized `Buffer` allocated in page.
unsafe {
ptr::drop_in_place(buf.as_ptr());
}
}
page_cache().free(all_pages);
}
/// Initializes `BufferChain` in place inside the first page of `pages`.
///
/// # Safety
///
/// `pages` must contain valid allocated pages with `ALLOC` state.
unsafe fn init_in_pages(pages: &mut VmPageDoublyLinkedList) -> Result<NonNull<Self>, Status> {
let first_page = pages.pop_front().ok_or(Status::NO_MEMORY)?;
// SAFETY: `first_page` was popped from `pages` and is a valid allocated page.
let first_buffer = unsafe { Buffer::init_in_page(first_page, BUFFER_CHAIN_SIZE as u32) };
// SAFETY: `first_buffer` is valid and contains space for `BufferChain`.
let chain_ptr = unsafe { (*first_buffer.as_ptr()).raw_data.as_mut_ptr().cast::<Self>() };
let init = pin_init!(Self {
buffers: {
let mut list = SinglyLinkedList::new();
// SAFETY: `first_buffer` is an unlinked Buffer pointer.
unsafe { list.push_front_raw(first_buffer) };
list
},
buffer_tail: Some(first_buffer),
buffer_offset: 0,
used_pages <- VmPageDoublyLinkedList::new(),
unused_pages <- VmPageDoublyLinkedList::new(),
});
// SAFETY: `chain_ptr` is properly aligned and sized inside the first buffer.
if unsafe { init.__pinned_init(chain_ptr) }.is_err() {
// SAFETY: Push `first_page` back onto `pages` so caller can free it.
unsafe {
pages.push_front_raw(first_page);
}
return Err(Status::NO_MEMORY);
}
// SAFETY: `chain_ptr` is now initialized, `first_page` was allocated for this chain,
// and `chain_ptr` is non-null.
unsafe {
let chain = &mut *chain_ptr;
chain.used_pages.push_back_raw(first_page);
chain.unused_pages.splice(pages);
Ok(NonNull::new_unchecked(chain_ptr))
}
}
/// Takes an unused page, initializes a new `Buffer` in it, and appends it to `buffers`.
fn add_next_buffer(&mut self) -> Result<(), Status> {
let page_raw = self.unused_pages.pop_front().ok_or(Status::OUT_OF_RANGE)?;
let tail = self.buffer_tail.ok_or(Status::OUT_OF_RANGE)?;
// SAFETY: `page_raw` was popped from `unused_pages` and is a valid page.
unsafe {
self.used_pages.push_back_raw(page_raw);
let va = Buffer::init_in_page(page_raw, 0);
self.buffers.insert_after_raw(tail.as_ptr(), va);
self.buffer_tail = Some(va);
}
Ok(())
}
/// Skips the specified number of bytes, so they won't be consumed by `append_user` or
/// `append_kernel`.
///
/// Assumes that it is called only at the beginning of the buffer chain.
pub fn skip(self: Pin<&mut Self>, size: usize) {
// SAFETY: Modifying unpinned field `buffer_offset` does not move `Self`.
let this = unsafe { self.get_unchecked_mut() };
debug_assert_eq!(this.buffer_offset, 0);
debug_assert!(size <= CONTIGUOUS_SIZE);
this.buffer_offset += size;
}
/// Appends |size| bytes from |src| to this chain.
///
/// If there is insufficient remaining space in the buffer chain, ZX_ERR_OUT_OF_RANGE will be
/// returned.
pub fn append_user(
self: Pin<&mut Self>,
src: UserInPtr<u8>,
size: usize,
) -> Result<(), Status> {
self.append_common(UserSource(src), size)
}
/// Same as Append except |src| can be in kernel space.
///
/// If there is insufficient remaining space in the buffer chain, ZX_ERR_OUT_OF_RANGE will be
/// returned.
pub fn append_kernel(self: Pin<&mut Self>, src: &[u8]) -> Result<(), Status> {
self.append_common(KernelSource(src), src.len())
}
/// Copies |size| bytes from this chain starting at offset |src_offset| to |dst|.
///
/// |src_offset| must be in the range [0, CONTIGUOUS_SIZE).
pub fn copy_out(
&self,
mut dst: UserOutPtr<u8>,
src_offset: usize,
size: usize,
) -> Result<(), Status> {
debug_assert!(src_offset < self.buffers.front().unwrap().size());
let mut copy_offset = src_offset;
let mut rem = size;
for buf in self.buffers.iter() {
if rem == 0 {
break;
}
let copy_len = cmp::min(rem, buf.size() - copy_offset);
// SAFETY: `copy_len` bytes starting at `copy_offset` were written by append.
let src = unsafe { slice::from_raw_parts(buf.data().add(copy_offset), copy_len) };
dst.copy_slice_to_user(src)?;
dst = dst.byte_offset(copy_len as isize);
rem -= copy_len;
copy_offset = 0;
}
Ok(())
}
/// Free unused pages.
pub fn free_unused_buffers(self: Pin<&mut Self>) {
// SAFETY: Mutating `unused_pages` does not move `Self`.
let this = unsafe { self.get_unchecked_mut() };
stack_pin_init!(let temp_pages = PageList::new());
// SAFETY: `temp_pages` is pinned on the stack; mutating list does not move `temp_pages`.
let pages = unsafe { temp_pages.as_mut().get_unchecked_mut() };
pages.splice(&mut this.unused_pages);
page_cache().free(temp_pages);
}
/// Common implementation for appending bytes from a source reader.
fn append_common<R: SourceReader>(
self: Pin<&mut Self>,
mut src: R,
size: usize,
) -> Result<(), Status> {
if size == 0 {
return Ok(());
}
// SAFETY: We hold `Pin<&mut Self>` and obtain `&mut Self` to perform buffer writes without
// moving `Self`.
let this = unsafe { self.get_unchecked_mut() };
let mut rem = size;
while rem > 0 {
let Some(mut tail) = this.buffer_tail else {
return Err(Status::OUT_OF_RANGE);
};
// SAFETY: `tail` is verified non-null and points to a valid Buffer.
let buf = unsafe { tail.as_mut() };
let copy_len = cmp::min(rem, buf.size() - this.buffer_offset);
let dst = &mut buf.as_mut_slice()[this.buffer_offset..this.buffer_offset + copy_len];
if let Err(status) = src.copy_to(dst) {
this.buffer_tail = None;
return Err(status);
}
this.buffer_offset += copy_len;
rem -= copy_len;
if rem > 0 {
if let Err(status) = this.add_next_buffer() {
this.buffer_tail = None;
return Err(status);
}
this.buffer_offset = 0;
}
}
Ok(())
}
/// Returns a mutable pointer to the usable data payload in the first buffer.
pub fn first_buffer_data_mut(self: Pin<&mut Self>) -> *mut u8 {
// SAFETY: Inspecting buffers list does not move `Self`.
let this = unsafe { self.get_unchecked_mut() };
this.buffers.front_mut().unwrap().data_mut()
}
/// Returns true if the buffer chain has no buffers.
pub fn is_empty(&self) -> bool {
self.buffers.is_empty()
}
/// Returns the number of buffers currently in the chain.
#[cfg(ktest)]
pub fn buffer_count(&self) -> usize {
self.buffers.size_slow()
}
/// Returns an iterator over byte slices of each buffer in the chain.
#[cfg(ktest)]
pub fn buffers_data(&self) -> impl Iterator<Item = &[MaybeUninit<u8>]> {
self.buffers.iter().map(Buffer::as_slice)
}
}
/// In-tree kernel unit tests for `BufferChain`.
#[cfg(ktest)]
#[unittest::suite(name = "buffer_chain_rust")]
mod tests {
use super::{BufferChain, CONTIGUOUS_SIZE, RAW_DATA_SIZE};
use crate::user_memory::UserMemory;
use core::ops::Deref;
use unittest::{expect_eq, expect_false, expect_ok, expect_true, unwrap_ok};
fn make_user_in_byte(size: usize, val: u8) -> Option<(UserMemory, UserInPtr<u8>)> {
let alloc_size = size.max(1);
let mem = UserMemory::create(alloc_size)?;
mem.commit_and_map(0..alloc_size).ok()?;
let chunk = [val; 256];
let mut offset = 0;
while offset < size {
let to_write = cmp::min(chunk.len(), size - offset);
mem.vmo_write(&chunk[..to_write], offset as u64).ok()?;
offset += to_write;
}
let ptr = UserInPtr::new(ptr::with_exposed_provenance::<u8>(mem.base()));
Some((mem, ptr))
}
fn make_user_out(size: usize) -> Option<(UserMemory, UserOutPtr<u8>)> {
let alloc_size = size.max(1);
let mem = UserMemory::create(alloc_size)?;
mem.commit_and_map(0..alloc_size).ok()?;
let ptr = UserOutPtr::new(ptr::with_exposed_provenance_mut::<u8>(mem.base()));
Some((mem, ptr))
}
fn verify_user_mem_byte(mem: &UserMemory, offset: usize, size: usize, expected: u8) -> bool {
let mut chunk = [MaybeUninit::<u8>::uninit(); 256];
let mut curr_offset = offset;
let end = offset + size;
while curr_offset < end {
let to_read = cmp::min(chunk.len(), end - curr_offset);
let Ok(read_bytes) = mem.vmo_read(&mut chunk[..to_read], curr_offset as u64) else {
return false;
};
for &mut b in read_bytes {
if b != expected {
return false;
}
}
curr_offset += to_read;
}
true
}
struct TestChain(NonNull<BufferChain>);
impl TestChain {
fn alloc(size: usize) -> Result<Self, Status> {
BufferChain::alloc(size).map(Self)
}
fn pin(&mut self) -> Pin<&mut BufferChain> {
// SAFETY: `self.0` is valid and pinned in its first buffer.
unsafe { Pin::new_unchecked(self.0.as_mut()) }
}
fn skip(&mut self, size: usize) {
self.pin().skip(size);
}
fn append_user(&mut self, src: UserInPtr<u8>, size: usize) -> Result<(), Status> {
self.pin().append_user(src, size)
}
fn append_kernel(&mut self, src: &[u8]) -> Result<(), Status> {
self.pin().append_kernel(src)
}
fn free_unused_buffers(&mut self) {
self.pin().free_unused_buffers();
}
}
impl Deref for TestChain {
type Target = BufferChain;
fn deref(&self) -> &Self::Target {
// SAFETY: `self.0` is non-null and valid.
unsafe { self.0.as_ref() }
}
}
impl Drop for TestChain {
fn drop(&mut self) {
// SAFETY: `self.0` is uniquely owned by `TestChain`.
unsafe { BufferChain::free(self.0) };
}
}
/// Tests basic allocation and deallocation of BufferChain.
#[test]
fn test_alloc_free_basic() {
// An empty chain requires one buffer
let bc = unwrap_ok!(BufferChain::alloc(0));
let bc_ref = unsafe { bc.as_ref() };
expect_false!(bc_ref.is_empty());
expect_eq!(bc_ref.buffer_count(), 1);
unsafe { BufferChain::free(bc) };
// One Buffer is enough to hold one byte.
let bc = unwrap_ok!(BufferChain::alloc(1));
let bc_ref = unsafe { bc.as_ref() };
expect_false!(bc_ref.is_empty());
expect_eq!(bc_ref.buffer_count(), 1);
unsafe { BufferChain::free(bc) };
// One Buffer is still enough.
let bc = unwrap_ok!(BufferChain::alloc(CONTIGUOUS_SIZE));
let bc_ref = unsafe { bc.as_ref() };
expect_false!(bc_ref.is_empty());
expect_eq!(bc_ref.buffer_count(), 1);
unsafe { BufferChain::free(bc) };
// Two pages allocated, only one used for the buffer.
let bc = unwrap_ok!(BufferChain::alloc(CONTIGUOUS_SIZE + 1));
let bc_ref = unsafe { bc.as_ref() };
expect_false!(bc_ref.is_empty());
expect_eq!(bc_ref.buffer_count(), 1);
unsafe { BufferChain::free(bc) };
// Several pages allocated, only one used for the buffer.
let bc = unwrap_ok!(BufferChain::alloc(10000 * RAW_DATA_SIZE));
let bc_ref = unsafe { bc.as_ref() };
expect_false!(bc_ref.is_empty());
expect_eq!(bc_ref.buffer_count(), 1);
unsafe { BufferChain::free(bc) };
}
/// Tests appending data from user memory and copying it back out.
#[test]
fn test_append_copy_out() {
const OFFSET: usize = 24;
const FIRST_COPY: usize = CONTIGUOUS_SIZE + 8;
const SECOND_COPY: usize = RAW_DATA_SIZE + 16;
const SIZE: usize = OFFSET + FIRST_COPY + SECOND_COPY;
let (_mem_a, mem_a_in) = make_user_in_byte(FIRST_COPY, b'A').unwrap();
let (_mem_b, mem_b_in) = make_user_in_byte(SECOND_COPY, b'B').unwrap();
let (mem_out_holder, mem_out) = make_user_out(SIZE).unwrap();
let mut bc = unwrap_ok!(TestChain::alloc(SIZE));
expect_eq!(bc.buffer_count(), 1);
bc.skip(OFFSET);
// Fill the chain with 'A'.
expect_ok!(bc.append_user(mem_a_in, FIRST_COPY));
// Verify it.
{
let mut bufs = bc.buffers_data();
let buf0 = bufs.next().unwrap();
for &byte in &buf0[OFFSET..CONTIGUOUS_SIZE] {
// SAFETY: Byte in this range was written by append_user.
expect_eq!(unsafe { byte.assume_init() }, b'A');
}
let buf1 = bufs.next().unwrap();
for &byte in &buf1[..(OFFSET + FIRST_COPY - CONTIGUOUS_SIZE)] {
// SAFETY: Byte in this range was written by append_user.
expect_eq!(unsafe { byte.assume_init() }, b'A');
}
}
// Write a chunk of 'B' straddling all three buffers.
expect_ok!(bc.append_user(mem_b_in, SECOND_COPY));
// Verify it.
{
let mut bufs = bc.buffers_data();
let buf0 = bufs.next().unwrap();
for &byte in &buf0[OFFSET..CONTIGUOUS_SIZE] {
// SAFETY: Byte in this range was written by append.
expect_eq!(unsafe { byte.assume_init() }, b'A');
}
let buf1 = bufs.next().unwrap();
for &byte in &buf1[..(OFFSET + FIRST_COPY - CONTIGUOUS_SIZE)] {
// SAFETY: Byte in this range was written by append.
expect_eq!(unsafe { byte.assume_init() }, b'A');
}
for &byte in &buf1[(OFFSET + FIRST_COPY - CONTIGUOUS_SIZE)..RAW_DATA_SIZE] {
// SAFETY: Byte in this range was written by append_user.
expect_eq!(unsafe { byte.assume_init() }, b'B');
}
let buf2 = bufs.next().unwrap();
for &byte in
&buf2[..(OFFSET + FIRST_COPY + SECOND_COPY - CONTIGUOUS_SIZE - RAW_DATA_SIZE)]
{
// SAFETY: Byte in this range was written by append_user.
expect_eq!(unsafe { byte.assume_init() }, b'B');
}
expect_true!(bufs.next().is_none());
}
// Copy it all out.
expect_ok!(bc.copy_out(mem_out, 0, SIZE));
// Verify it.
expect_true!(verify_user_mem_byte(&mem_out_holder, OFFSET, FIRST_COPY, b'A'));
expect_true!(verify_user_mem_byte(&mem_out_holder, OFFSET + FIRST_COPY, SECOND_COPY, b'B'));
}
/// Tests appending data from kernel memory and copying it back out.
#[test]
fn test_append_kernel() {
const SIZE: usize = 8192;
static KERNEL_DATA: [u8; SIZE] = [0x5A; SIZE];
let mut bc = unwrap_ok!(TestChain::alloc(SIZE));
expect_ok!(bc.append_kernel(&KERNEL_DATA));
expect_eq!(bc.buffer_count(), 3);
let (mem_out_holder, mem_out) = make_user_out(SIZE).unwrap();
expect_ok!(bc.copy_out(mem_out, 0, SIZE));
expect_true!(verify_user_mem_byte(&mem_out_holder, 0, SIZE, 0x5A));
}
/// Tests freeing unused pages from the chain.
#[test]
fn test_free_unused_pages() {
const SIZE: usize = 8 * page::SIZE;
const WRITE_SIZE: usize = CONTIGUOUS_SIZE + 1;
let (_mem, mem_in) = make_user_in_byte(WRITE_SIZE, 0).unwrap();
let mut bc = unwrap_ok!(TestChain::alloc(SIZE));
expect_eq!(bc.buffer_count(), 1);
expect_ok!(bc.append_user(mem_in, WRITE_SIZE));
expect_eq!(bc.buffer_count(), 2);
bc.free_unused_buffers();
expect_eq!(bc.buffer_count(), 2);
}
/// Tests that appending more than allocated returns OUT_OF_RANGE.
#[test]
fn test_append_more_than_allocated() {
const ALLOC_SIZE: usize = 2 * page::SIZE;
const WRITE_SIZE: usize = 2 * ALLOC_SIZE;
let (_mem, mem_in) = make_user_in_byte(WRITE_SIZE, 0).unwrap();
let mut bc = unwrap_ok!(TestChain::alloc(ALLOC_SIZE));
expect_eq!(bc.buffer_count(), 1);
let res = bc.append_user(mem_in, WRITE_SIZE);
expect_true!(matches!(res, Err(Status::OUT_OF_RANGE)));
}
/// Tests that append after a failed append also fails.
#[test]
fn test_append_after_fail_fails() {
const ALLOC_SIZE: usize = 2 * page::SIZE;
const WRITE_SIZE: usize = page::SIZE;
let (_mem, mem_in) = make_user_in_byte(WRITE_SIZE, 0).unwrap();
let mut bc = unwrap_ok!(TestChain::alloc(ALLOC_SIZE));
expect_eq!(bc.buffer_count(), 1);
let bad_in = UserInPtr::new(ptr::null());
let res = bc.append_user(bad_in, WRITE_SIZE);
expect_true!(matches!(res, Err(Status::INVALID_ARGS)));
let res2 = bc.append_user(mem_in, WRITE_SIZE);
expect_true!(matches!(res2, Err(Status::OUT_OF_RANGE)));
}
}