use bitflags::bitflags;
use consortium_core::descriptor::{DescriptorHeader, DescriptorMagic};
use crate::packet::{PACKET_HEADER_SIZE, Packet};
use crate::utils::{rv_u16, wv_u16};
#[repr(C)]
pub struct Descriptor {
pub header: DescriptorHeader,
pub write_ptr: u16,
pub read_ptr: u16,
pub capacity: u16,
pub flags: Flags,
_reserved1: [u8; 2],
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Flags: u16 {
const Writing = 0b0000_0000_0000_0001;
const Error = 0b0000_0000_0001_0000;
const Overflow = 0b0000_0000_0010_0000;
const Panic = 0b0000_0000_0100_0000;
}
}
pub const MAGIC: DescriptorMagic = DescriptorMagic::new(b"CDBG");
pub const VERSION: u8 = 0;
pub const WRITE_PTR_OFFSET: usize = 0x06;
pub const READ_PTR_OFFSET: usize = 0x08;
pub const CAPACITY_OFFSET: usize = 0x0A;
pub const FLAGS_OFFSET: usize = 0x0C;
pub const DESCRIPTOR_SIZE: usize = core::mem::size_of::<Descriptor>();
const DATA_START: u16 = DESCRIPTOR_SIZE as u16;
pub const MAX_FRAME_LEN: usize = u8::MAX as usize;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ReadOutcome {
Empty,
Frame(usize),
TooSmall {
needed: usize,
},
}
impl Descriptor {
pub const fn new(capacity: u16) -> Self {
Self {
header: DescriptorHeader::new(MAGIC, VERSION),
write_ptr: DATA_START,
read_ptr: DATA_START,
capacity,
flags: Flags::empty(),
_reserved1: [0; 2],
}
}
pub fn check(&self) -> bool {
self.header.check(MAGIC, VERSION).is_ok()
}
pub unsafe fn from_raw_ptr(ptr: *const u8) -> Self {
let header = unsafe { DescriptorHeader::from_raw_ptr(ptr) };
let write_ptr = unsafe { rv_u16(ptr, WRITE_PTR_OFFSET) };
let read_ptr = unsafe { rv_u16(ptr, READ_PTR_OFFSET) };
let capacity = unsafe { rv_u16(ptr, CAPACITY_OFFSET) };
let flags = Flags::from_bits_retain(unsafe { rv_u16(ptr, FLAGS_OFFSET) });
Self {
header,
write_ptr,
read_ptr,
capacity,
flags,
_reserved1: [0; 2],
}
}
pub unsafe fn write_to_raw_ptr(&self, ptr: *mut u8) {
unsafe { DescriptorHeader::new(MAGIC, VERSION).write_to_raw_ptr(ptr) };
unsafe { wv_u16(ptr, WRITE_PTR_OFFSET, self.write_ptr) };
unsafe { wv_u16(ptr, READ_PTR_OFFSET, self.read_ptr) };
unsafe { wv_u16(ptr, CAPACITY_OFFSET, self.capacity) };
unsafe { wv_u16(ptr, FLAGS_OFFSET, self.flags.bits()) };
}
pub unsafe fn try_read_frame(&mut self, base: *mut u8, buf: &mut [u8]) -> ReadOutcome {
for _ in 0..2 {
if self.read_ptr == self.write_ptr {
return ReadOutcome::Empty;
}
let packet_ptr = unsafe { base.add(self.read_ptr as usize) };
let frame_header = unsafe { Packet::try_from_raw_ptr(packet_ptr) };
let in_ring = |header: &Packet| {
self.read_ptr as usize + PACKET_HEADER_SIZE + header.len as usize
<= self.capacity as usize
};
let mut frame_header = match frame_header {
Some(header) if header.is_valid() && in_ring(&header) => header,
_ => {
if self.read_ptr == DATA_START {
return ReadOutcome::Empty;
}
self.read_ptr = DATA_START;
unsafe { wv_u16(base, READ_PTR_OFFSET, self.read_ptr) };
continue;
}
};
let len = frame_header.len as usize;
let Some(payload) = buf.get_mut(..len) else {
return ReadOutcome::TooSmall { needed: len };
};
unsafe { frame_header.read_payload(packet_ptr, payload) };
unsafe { frame_header.mark_as_consumed(packet_ptr) };
self.read_ptr = unsafe { self.next_read_ptr(base) };
unsafe { wv_u16(base, READ_PTR_OFFSET, self.read_ptr) };
return ReadOutcome::Frame(len);
}
ReadOutcome::Empty
}
#[cfg(feature = "host")]
pub unsafe fn read_one_frame(&mut self, base: *mut u8) -> Option<Vec<u8>> {
let mut buf = [0u8; MAX_FRAME_LEN];
match unsafe { self.try_read_frame(base, &mut buf) } {
ReadOutcome::Frame(len) => Some(buf[..len].to_vec()),
ReadOutcome::Empty | ReadOutcome::TooSmall { .. } => None,
}
}
#[cfg(feature = "host")]
pub unsafe fn read_bulk_frames(&mut self, base: *mut u8) -> Vec<Vec<u8>> {
let mut frames = Vec::new();
while let Some(frame) = unsafe { self.read_one_frame(base) } {
frames.push(frame);
}
frames
}
pub unsafe fn write_one_frame(&mut self, base: *mut u8, frame: &[u8]) -> bool {
match unsafe { self.allocate_write_ptr(base, frame.len()) } {
Some(p) => {
let packet_ptr = unsafe { base.add(p as usize) };
unsafe { Packet::write_full_packet_to_raw_ptr(packet_ptr, frame) };
self.flags.remove(Flags::Overflow);
self.write_ptr = self.advance_ptr(p, frame.len());
unsafe { wv_u16(base, WRITE_PTR_OFFSET, self.write_ptr) };
unsafe { wv_u16(base, FLAGS_OFFSET, self.flags.bits()) };
true
}
None => {
self.flags.insert(Flags::Overflow);
unsafe { wv_u16(base, FLAGS_OFFSET, self.flags.bits()) };
false
}
}
}
pub(crate) unsafe fn next_read_ptr(&mut self, base: *const u8) -> u16 {
let cursor = unsafe { base.add(self.read_ptr as usize) };
let Some(frame_header) = (unsafe { Packet::try_from_raw_ptr(cursor) }) else {
return DESCRIPTOR_SIZE as u16;
};
let next_padding = frame_header.len as usize + PACKET_HEADER_SIZE;
let next_ptr = self.read_ptr.wrapping_add(next_padding as u16);
if next_ptr >= self.capacity {
DATA_START
} else {
next_ptr
}
}
pub(crate) unsafe fn allocate_write_ptr(
&mut self,
base: *mut u8,
frame_len: usize,
) -> Option<u16> {
if frame_len > u8::MAX as usize {
return None;
}
let packet_len = PACKET_HEADER_SIZE + frame_len;
if packet_len > self.capacity as usize - DATA_START as usize {
return None;
}
let mut ptr = self.write_ptr;
if ptr as usize + packet_len > self.capacity as usize {
ptr = DATA_START;
}
let cursor = unsafe { base.add(ptr as usize) };
if unsafe { Packet::try_from_raw_ptr(cursor) }.is_some_and(|packet| packet.is_valid()) {
None
} else {
Some(ptr)
}
}
fn advance_ptr(&self, ptr: u16, frame_len: usize) -> u16 {
let next_ptr = ptr.wrapping_add((PACKET_HEADER_SIZE + frame_len) as u16);
if next_ptr >= self.capacity {
DATA_START
} else {
next_ptr
}
}
#[cfg(feature = "logger")]
pub(crate) unsafe fn flush(&mut self, base: *mut u8) {
let cursor = unsafe { base.add(self.read_ptr as usize) };
let Some(mut frame_header) = (unsafe { Packet::try_from_raw_ptr(cursor) }) else {
return;
};
while self.read_ptr != self.write_ptr && self.read_ptr != DESCRIPTOR_SIZE as u16 {
while frame_header.is_valid() {
core::hint::spin_loop();
}
self.read_ptr = unsafe { self.next_read_ptr(base) };
let cursor = unsafe { base.add(self.read_ptr as usize) };
match unsafe { Packet::try_from_raw_ptr(cursor) } {
Some(header) => frame_header = header,
None => return,
}
}
}
}