blob: ac0de48582ed6661107fcc7d75171583f814c43d [file]
// Copyright 2026 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#![no_std]
use cbuf::Cbuf;
use core::ffi::{c_char, c_void};
use core::sync::atomic::{AtomicU32, Ordering};
use pin_init::stack_pin_init;
use zx_status::Status;
use zx_types::ZX_TIME_INFINITE;
const ZX_ERR_INTERNAL_INTR_KILLED: i32 = -502;
#[unsafe(no_mangle)]
pub extern "C" fn test_cbuf_constructor() -> bool {
stack_pin_init!(let cbuf = Cbuf::init());
if !cbuf.full() {
return false;
}
let mut buf = [0u8; 4];
// SAFETY: `buf` is valid for cbuf lifetime.
unsafe {
if cbuf.initialize(buf.len(), buf.as_mut_ptr()).is_err() {
return false;
}
}
if cbuf.full() {
return false;
}
true
}
#[unsafe(no_mangle)]
pub extern "C" fn test_cbuf_read_write() -> bool {
stack_pin_init!(let cbuf = Cbuf::init());
let mut buf = [0u8; 4];
// SAFETY: `buf` is valid for cbuf lifetime.
unsafe {
if cbuf.initialize(buf.len(), buf.as_mut_ptr()).is_err() {
return false;
}
}
if cbuf.full() {
return false;
}
// Nothing to read, don't wait.
if cbuf.read_char(false) != Err(Status::SHOULD_WAIT) {
return false;
}
// Write some characters.
let data = b"ABC";
for &c in data {
if cbuf.write_char(c) != 1 {
return false;
}
}
if !cbuf.full() {
return false;
}
// Writing when full should return 0.
if cbuf.write_char(b'D') != 0 {
return false;
}
// Read them back.
for (i, &expected) in data.iter().enumerate() {
match cbuf.read_char_with_context(true) {
Ok(res) => {
if res.transitioned_from_full != (i == 0) {
return false;
}
if res.c != expected {
return false;
}
}
Err(_) => return false,
}
}
if cbuf.full() {
return false;
}
true
}
extern "C" fn reader_thread_entry(arg: *mut c_void) -> i32 {
// SAFETY: arg is a valid pointer to a Cbuf pinned on the parent thread's stack.
let cbuf = unsafe { &*(arg as *const Cbuf) };
loop {
match cbuf.read_char(true) {
Ok(_) => {}
Err(status) => return status.into_raw(),
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn test_cbuf_read_write_race() -> bool {
stack_pin_init!(let cbuf = Cbuf::init());
let mut buf = [0u8; 4];
// SAFETY: `buf` is valid for cbuf lifetime.
unsafe {
if cbuf.initialize(buf.len(), buf.as_mut_ptr()).is_err() {
return false;
}
}
let thread_name = b"cbuf_rust_race\0".as_ptr() as *const c_char;
let cbuf_ptr = &*cbuf as *const Cbuf as *mut c_void;
// SAFETY: we pass reader_thread_entry and valid pointers. The thread is joined
// before `cbuf` (and `buf`) goes out of scope.
unsafe {
let thread = match kernel::thread::spawn(thread_name, reader_thread_entry, cbuf_ptr) {
Ok(t) => t,
Err(_) => return false,
};
for _ in 0..1000 {
while cbuf.write_char(b'A') == 0 {
kernel::thread::r#yield();
}
}
thread.kill();
let ret = match thread.join(ZX_TIME_INFINITE) {
Ok(r) => r,
Err(_) => return false,
};
if ret != ZX_ERR_INTERNAL_INTR_KILLED {
return false;
}
}
true
}
#[unsafe(no_mangle)]
pub extern "C" fn test_cbuf_init_limits() -> bool {
stack_pin_init!(let cbuf = Cbuf::init());
let mut buf = [0u8; 4];
// Size 0 should fail.
unsafe {
if cbuf.initialize(0, buf.as_mut_ptr()) != Err(Status::INVALID_ARGS) {
return false;
}
}
// Non-power of two should fail.
unsafe {
if cbuf.initialize(3, buf.as_mut_ptr()) != Err(Status::INVALID_ARGS) {
return false;
}
if cbuf.initialize(5, buf.as_mut_ptr()) != Err(Status::INVALID_ARGS) {
return false;
}
}
// Power of two should succeed.
unsafe {
if cbuf.initialize(4, buf.as_mut_ptr()).is_err() {
return false;
}
}
true
}
#[unsafe(no_mangle)]
pub extern "C" fn test_cbuf_uninitialized() -> bool {
stack_pin_init!(let cbuf = Cbuf::init());
if !cbuf.full() {
return false;
}
if cbuf.write_char(b'A') != 0 {
return false;
}
if cbuf.read_char(false) != Err(Status::SHOULD_WAIT) {
return false;
}
true
}
#[unsafe(no_mangle)]
pub extern "C" fn test_cbuf_wrap_around() -> bool {
stack_pin_init!(let cbuf = Cbuf::init());
let mut buf = [0u8; 4];
unsafe {
if cbuf.initialize(buf.len(), buf.as_mut_ptr()).is_err() {
return false;
}
}
// Write 3 chars (capacity is 3)
if cbuf.write_char(b'A') != 1 {
return false;
}
if cbuf.write_char(b'B') != 1 {
return false;
}
if cbuf.write_char(b'C') != 1 {
return false;
}
if !cbuf.full() {
return false;
}
// Read 3 chars
if cbuf.read_char(false) != Ok(b'A') {
return false;
}
if cbuf.read_char(false) != Ok(b'B') {
return false;
}
if cbuf.read_char(false) != Ok(b'C') {
return false;
}
if cbuf.full() {
return false;
}
// Write 2 chars (wraps pointers)
if cbuf.write_char(b'D') != 1 {
return false;
}
if cbuf.write_char(b'E') != 1 {
return false;
}
// Read 2 chars (wraps pointers)
if cbuf.read_char(false) != Ok(b'D') {
return false;
}
if cbuf.read_char(false) != Ok(b'E') {
return false;
}
// Should be empty
if cbuf.read_char(false) != Err(Status::SHOULD_WAIT) {
return false;
}
true
}
struct BlockingReadContext {
cbuf: *mut Cbuf,
state: *const AtomicU32, // 0: init, 1: about to read, 2: read done, 3: error
read_char: *mut u8,
}
// SAFETY: We only pass valid pointers and don't share mutability unsafely.
unsafe impl Send for BlockingReadContext {}
extern "C" fn blocking_reader_entry(arg: *mut c_void) -> i32 {
let ctx = unsafe { &*(arg as *const BlockingReadContext) };
let cbuf = unsafe { &*ctx.cbuf };
let state = unsafe { &*ctx.state };
state.store(1, Ordering::SeqCst);
let c = cbuf.read_char(true); // Should block until written.
match c {
Ok(val) => {
unsafe { *ctx.read_char = val };
state.store(2, Ordering::SeqCst);
0
}
Err(status) => {
state.store(3, Ordering::SeqCst); // error
status.into_raw()
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn test_cbuf_blocking_read() -> bool {
stack_pin_init!(let cbuf = Cbuf::init());
let mut buf = [0u8; 4];
// SAFETY: `buf` is valid for cbuf lifetime.
unsafe {
if cbuf.initialize(buf.len(), buf.as_mut_ptr()).is_err() {
return false;
}
}
let state = AtomicU32::new(0);
let mut read_char = 0u8;
let mut ctx = BlockingReadContext {
cbuf: &*cbuf as *const Cbuf as *mut Cbuf,
state: &state,
read_char: &mut read_char,
};
let thread_name = b"cbuf_blocking_read\0".as_ptr() as *const c_char;
let ctx_ptr = &mut ctx as *mut BlockingReadContext as *mut c_void;
unsafe {
let thread = match kernel::thread::spawn(thread_name, blocking_reader_entry, ctx_ptr) {
Ok(t) => t,
Err(_) => return false,
};
// Wait until the reader thread is about to read.
while state.load(Ordering::SeqCst) < 1 {
kernel::thread::r#yield();
}
// Wait until the reader thread is actually blocked.
while !thread.is_blocked() {
kernel::thread::r#yield();
// If it failed and exited, break.
if state.load(Ordering::SeqCst) == 3 {
break;
}
}
if state.load(Ordering::SeqCst) == 3 {
thread.join(ZX_TIME_INFINITE).ok();
return false;
}
// Double check it is indeed blocked and state is 1.
if !thread.is_blocked() || state.load(Ordering::SeqCst) != 1 {
thread.join(ZX_TIME_INFINITE).ok();
return false;
}
// Now write a char. This should wake it up.
if cbuf.write_char(b'X') != 1 {
thread.join(ZX_TIME_INFINITE).ok();
return false;
}
// Wait for reader thread to complete.
let ret = match thread.join(ZX_TIME_INFINITE) {
Ok(r) => r,
Err(_) => return false,
};
if ret != 0 {
return false;
}
if state.load(Ordering::SeqCst) != 2 {
return false;
}
if read_char != b'X' {
return false;
}
}
true
}