diff --git a/issues/fstats-answer-is-declared-twice.md b/issues/fstats-answer-is-declared-twice.md new file mode 100644 index 00000000000..21e99aaef8d --- /dev/null +++ b/issues/fstats-answer-is-declared-twice.md @@ -0,0 +1,28 @@ +--- +status: open +kind: defect +opened: 2026-10-07 +--- + +# `fstat`'s answer is declared twice, and nothing holds the two layouts together + +`SYS_FSTAT` copies out `kernel/src/object/ops.rs`'s `Stat`, three `u64`s, and +`toyos_abi::syscall::fstat` reads the answer into `toyos-abi/src/syscall.rs`'s +`Stat`, whose first field is `FileType`, a `#[repr(u64)]` enum. The kernel's +copy exists because a struct holding the enum is not valid for every bit +pattern and so cannot be `UserSafe`; it is sound, since the kernel only +writes it, with `FileType::… as u64`. + +Read, nothing run: the two declarations agree today field for field. A field +added to one and not the other compiles on both sides, and userland then reads +a layout the kernel did not write. + +## Exit condition + +One declaration of the answer's layout that both the kernel's copy-out and +`toyos_abi::syscall::fstat` read, or a compile-time assertion beside one of +them that fails when the two differ in size or in any field's offset. + +## Owner + +`toyos-abi/src/syscall.rs`, `kernel/src/object/ops.rs`. Nobody holds it. diff --git a/issues/three-kernel-byte-views-still-rest-on-a-hand-layout-claim.md b/issues/three-kernel-byte-views-still-rest-on-a-hand-layout-claim.md new file mode 100644 index 00000000000..0c4f3f609fa --- /dev/null +++ b/issues/three-kernel-byte-views-still-rest-on-a-hand-layout-claim.md @@ -0,0 +1,41 @@ +--- +status: open +kind: defect +opened: 2026-10-08 +--- + +# Three kernel byte views still rest on a layout claim made by hand + +`toyos_abi::usersafe::bytes` is the one view of a `UserSafe` value as bytes, +and `user_safe!` is what proves no byte of the value is padding. Three structs +the kernel copies out are not declared through the macro, and each reaches +user memory through an `unsafe` `from_raw_parts` of the kernel's own whose +`SAFETY` comment cites an assertion beside the declaration: + +| struct (`toyos-abi/src/pci.rs`) | the kernel's view | what the comment rests on | +|---|---|---| +| `DmaGrant` | `grant_bytes`, `kernel/src/syscall/device.rs` | `size_of == 4 + 4 + 8 + 8`, a sum written by hand | +| `DmaMapping` | inline in `sys_device_dma_map`, the same file | `size_of == 8 + 8`, a sum written by hand | +| `DeviceIrqRecord` | `record_bytes`, `kernel/src/object/ops.rs` | `SIZE == 4`, a total written by hand; the struct is one `u32` | + +Read, nothing run: none of the three has padding today. Each assertion +compares the struct's size with a number a person wrote, and nothing ties +that number to the fields: a field added to any of the three fails the build +until the number is moved, and passes once it is moved to the new size, +whether or not that size holds a gap. The bytes of a gap are the kernel's +stack. + +Found while the eleven `as_bytes` in `toyos-abi` moved to `usersafe::bytes`; +these three were outside that change's brief. + +## Exit condition + +The three are declared through `user_safe!`, so a byte of any that belongs to +no field fails the build with the macro's message; the kernel's three +`from_raw_parts` over them are `usersafe::bytes`, and the two hand sums are +deleted. + +## Owner + +`toyos-abi/src/pci.rs`, `kernel/src/syscall/device.rs`, +`kernel/src/object/ops.rs`. Nobody holds it. diff --git a/issues/usersafe-layouts-are-checked-by-hand.md b/issues/usersafe-layouts-are-checked-by-hand.md deleted file mode 100644 index f57c6cef374..00000000000 --- a/issues/usersafe-layouts-are-checked-by-hand.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -status: open -kind: defect -opened: 2026-10-07 ---- - -# `UserSafe` layouts are checked by hand, and one impl already states a size its type does not have - -`kernel/src/user_ptr.rs`'s `UserSafe` is an `unsafe trait` whose contract is -`#[repr(C)]`, `Copy`, no padding and every bit pattern valid. `copy_in` reads a -`T: UserSafe` out of user memory with `read_volatile` and `copy_out` writes one -with `write_volatile`, so a padding byte in `T` is kernel stack written to -userland, and a field with an invalid bit pattern is undefined behaviour chosen -by the caller. The file says of its own impls that each is hand-checked and that -Rust cannot verify it. - -Read at `f260e0b98`, nothing run: - -- **The hand check has already drifted.** The `SAFETY` comment on - `unsafe impl UserSafe for toyos_abi::log::LogCursor` says 88 bytes; - `toyos-abi/src/log.rs` asserts `size_of::() == 16 + 8 * - MAX_LOG_SHARDS` with `MAX_LOG_SHARDS = 8`, and its test asserts 80. -- **Four of the structs carry no size assertion at all**: `Stat` - (`kernel/src/object/ops.rs`), `SchedInfo`, `ProcessStats` and - `FramebufferInfo`. `TraceCursor` has none either and needs none, being - `repr(transparent)` over `LogCursor`. `SpawnArgs`, `NamespaceBuild` and - `InboxSetup` each have a `const _` on a literal total, which a field added - together with its new total still satisfies with a gap inside. `LogCursor`, - `RawKeyEvent` and `MouseEvent` assert a sum of field sizes, which a field - added with its own size fails on any gap. -- **The second list is a copy.** `toyos-userbound/src/span.rs`'s test table is - headed "Every `UserSafe` type" and is thirteen name strings with a size and - an alignment written beside each; it names neither `LogCursor` nor - `TraceCursor`, and nothing ties a row to the type it names. - -No impl is known to be wrong today: every field read is an integer or a -`repr(transparent)` wrapper over one. The defect is that a field added to an -ABI struct in `toyos-abi` compiles clean in the kernel whatever it does to the -layout, on a boundary root `CLAUDE.md` wants unrepresentable before it is -checked. - -## Exit condition - -The compiler refuses a `UserSafe` impl whose type has a padding byte or a field -that is not valid for every bit pattern, by a derive or by one macro that -writes the impl and its assertions together, so no impl is written by hand. -Shown on two structs of their own that no code builds or writes, handed to that -derive or macro, each beside a twin it accepts, so nothing but the checker -refuses them: `#[repr(C)] { a: u64, b: u32 }` fails the build for its four tail -bytes where `{ a: u64, b: u64 }` builds, and `#[repr(C)] { a: u32, b: char }` -fails it for `char`, which has `u32`'s size and alignment, where -`{ a: u32, b: u32 }` builds. `span.rs`'s table is -then generated from the same list or deleted, and this file with it. Whether -that is a published crate or the kernel's own is the builder's to argue against -root `CLAUDE.md`'s rule on kernel dependencies. - -## Owner - -`kernel/src/user_ptr.rs`, `toyos-abi`. Nobody holds it. diff --git a/kernel/src/log/user.rs b/kernel/src/log/user.rs index be121825fcf..2d1a3c62f5a 100644 --- a/kernel/src/log/user.rs +++ b/kernel/src/log/user.rs @@ -4,8 +4,9 @@ //! //! [`Rights::LOG`]: toyos_abi::handle::Rights::LOG -use toyos_abi::log::{LogCursor, LogRecord}; +use toyos_abi::log::LogCursor; use toyos_abi::syscall::SyscallError; +use toyos_abi::UserSafe; use crate::watch::Watch; use crate::user_ptr::UserBytesMut; @@ -24,19 +25,18 @@ pub fn post_readiness() { } // Fixed stride, never packed: the caller indexes by shift, so the kernel does no length arithmetic. -struct UserRecords<'a, 'b, R> { +struct UserRecords<'a, 'b> { out: &'a mut UserBytesMut<'b>, written: usize, capacity: usize, - bytes: fn(&R) -> &[u8], } -impl RecordSink for UserRecords<'_, '_, R> { +impl RecordSink for UserRecords<'_, '_> { fn put(&mut self, record: &R) -> bool { if self.written >= self.capacity { return false; } - let bytes = (self.bytes)(record); + let bytes = toyos_abi::usersafe::bytes(record); self.out.write_at(self.written * bytes.len(), bytes); self.written += 1; true @@ -49,20 +49,19 @@ pub fn read( out: &mut UserBytesMut, capacity: usize, ) -> Result { - read_rings(&super::shards(), cursor, out, capacity, LogRecord::as_bytes) + read_rings(&super::shards(), cursor, out, capacity) } -/// Copies records `cursor` has not seen in `rings` into `out`, oldest first, -/// each as `bytes` writes it; never blocks. +/// Copies records `cursor` has not seen in `rings` into `out`, oldest first; +/// never blocks. pub fn read_rings( rings: &Rings, cursor: &mut LogCursor, out: &mut UserBytesMut, capacity: usize, - bytes: fn(& as Stream>::Record) -> &[u8], ) -> Result where - Ring: Stream, + Ring: Stream, { let shards = rings.iter().flatten().count() as u32; // Refused, not truncated: a capacity below one record per ring cannot hold what a single call may have to merge. @@ -73,7 +72,7 @@ where let Some(mut walk) = Cursor::from_reader(cursor, rings) else { return Err(SyscallError::InvalidArgument); }; - let mut sink = UserRecords { out, written: 0, capacity, bytes }; + let mut sink = UserRecords { out, written: 0, capacity }; drain_ordered(rings, &mut walk, &mut sink); let written = sink.written; diff --git a/kernel/src/object/device.rs b/kernel/src/object/device.rs index 2b5edbde788..54145354633 100644 --- a/kernel/src/object/device.rs +++ b/kernel/src/object/device.rs @@ -6,6 +6,7 @@ use core::sync::atomic::{AtomicBool, Ordering}; use toyos_abi::handle::{RawHandle, Rights}; use toyos_abi::syscall::SyscallError; +use toyos_abi::usersafe::bytes; use toyos_abi::FramebufferInfo; use crate::device::{Claim, DeviceType}; @@ -70,23 +71,23 @@ impl DeviceInfo { )?; info.scanout = [h[0], h[1]]; info.cursor = h[2]; - info.as_bytes().into() + bytes(&info).into() } // Nothing to install: every address in it is a size, and the memory // is what `SYS_DEVICE_DMA_ALLOC` answers later. - Self::PciFunction(info, _) => info.as_bytes().into(), - Self::Partition(info) => info.as_bytes().into(), + Self::PciFunction(info, _) => bytes(info).into(), + Self::Partition(info) => bytes(info).into(), Self::Isa(set, _) => set.wire().iter().flat_map(|word| word.to_ne_bytes()).collect(), - Self::Acpi(info, _) => info.as_bytes().into(), + Self::Acpi(info, _) => bytes(info).into(), Self::Hda(info, pcm) => { let mut info = *info; info.pcm = install_buffers(table, &[pcm])?[0]; - info.as_bytes().into() + bytes(&info).into() } Self::VirtioSound(info, dma) => { let mut info = *info; info.dma = install_buffers(table, &[dma])?[0]; - info.as_bytes().into() + bytes(&info).into() } }) } diff --git a/kernel/src/object/ops.rs b/kernel/src/object/ops.rs index e0803305fba..3ed962a6869 100644 --- a/kernel/src/object/ops.rs +++ b/kernel/src/object/ops.rs @@ -395,7 +395,7 @@ pub fn read_device( let mut count = 0; while count + event_size <= buf.len() { let Some(event) = keyboard::try_read_event() else { break }; - buf.write_at(count, event.as_bytes()); + buf.write_at(count, toyos_abi::usersafe::bytes(&event)); count += event_size; } if count > 0 { Some(count as u64) } else { None } @@ -405,7 +405,7 @@ pub fn read_device( let mut count = 0; while count + event_size <= buf.len() { let Some(event) = mouse::try_read_event() else { break }; - buf.write_at(count, event.as_bytes()); + buf.write_at(count, toyos_abi::usersafe::bytes(&event)); count += event_size; } if count > 0 { Some(count as u64) } else { None } @@ -608,12 +608,13 @@ pub fn seek(object: &KObjectRef, pos: SeekFrom) -> u64 { }) } -#[repr(C)] -#[derive(Clone, Copy)] -pub struct Stat { - pub file_type: u64, - pub size: u64, - pub mtime: u64, +toyos_abi::user_safe! { + #[derive(Clone, Copy)] + pub struct Stat { + pub file_type: u64, + pub size: u64, + pub mtime: u64, + } } /// What kind of thing this is, and how big. diff --git a/kernel/src/syscall/machine.rs b/kernel/src/syscall/machine.rs index c9ee138b0a4..c0efc356003 100644 --- a/kernel/src/syscall/machine.rs +++ b/kernel/src/syscall/machine.rs @@ -42,7 +42,7 @@ pub(super) fn sys_trace_read( /// A read of records on a cursor the caller holds, under `need`. /// /// A copy-out failure after a successful read costs the caller those records; the cursor round-trips through the caller's own memory. -fn read_on_cursor( +fn read_on_cursor( ctx: &SyscallContext, syscap: RawHandle, need: Rights, diff --git a/kernel/src/syscall/vm.rs b/kernel/src/syscall/vm.rs index c6450755b12..729f0af9ec4 100644 --- a/kernel/src/syscall/vm.rs +++ b/kernel/src/syscall/vm.rs @@ -514,7 +514,7 @@ pub(super) fn sys_query_modules(out: &mut UserBytesMut) -> u64 { path_offset, path_len: exe_path_bytes.len() as u32, }; - out.write_at(0, exe_info.as_bytes()); + out.write_at(0, toyos_abi::usersafe::bytes(&exe_info)); out.write_at(path_offset as usize, exe_path_bytes); path_offset += exe_path_bytes.len() as u32; @@ -534,7 +534,7 @@ pub(super) fn sys_query_modules(out: &mut UserBytesMut) -> u64 { path_offset, path_len: lib_path_bytes.len() as u32, }; - out.write_at((1 + i) * info_size, lib_info.as_bytes()); + out.write_at((1 + i) * info_size, toyos_abi::usersafe::bytes(&lib_info)); out.write_at(path_offset as usize, lib_path_bytes); path_offset += lib_path_bytes.len() as u32; } diff --git a/kernel/src/trace.rs b/kernel/src/trace.rs index 9aeef3a287d..65822231419 100644 --- a/kernel/src/trace.rs +++ b/kernel/src/trace.rs @@ -125,7 +125,7 @@ fn rings() -> Rings { /// Copies records `cursor` has not seen into `out`, oldest first; never blocks. pub fn read(cursor: &mut TraceCursor, out: &mut UserBytesMut, capacity: usize) -> Result { - crate::log::user::read_rings(&rings(), &mut cursor.0, out, capacity, TraceRecord::as_bytes) + crate::log::user::read_rings(&rings(), &mut cursor.0, out, capacity) } /// [`Kind::Mark`] records `count` of them on this CPU, `data` counting up, and diff --git a/kernel/src/user_ptr.rs b/kernel/src/user_ptr.rs index ed78f14b25b..efa2ba395ef 100644 --- a/kernel/src/user_ptr.rs +++ b/kernel/src/user_ptr.rs @@ -18,6 +18,7 @@ use alloc::vec::Vec; use core::marker::PhantomData; use toyos_abi::syscall::SyscallError; +use toyos_abi::UserSafe; use toyos_userbound::{Pinned, Pins, Segment}; use crate::UserAddr; @@ -25,48 +26,6 @@ use crate::UserAddr; /// Longest string, in bytes, the kernel accepts from userspace; one bound for every string syscall, since each copies or tokenizes rather than streaming. pub const MAX_USER_STR: u64 = 64 * 1024; -/// Marker for types safe to interpret from / write to validated user pointers. -/// # Safety -/// Must be `#[repr(C)]`, `Copy`, have no padding, and be valid for any bit pattern. -pub unsafe trait UserSafe: Copy {} - -// Every impl below is hand-checked: `#[repr(C)]`, `Copy`, integer fields only, explicit `_pad` for every alignment gap — Rust cannot verify this mechanically. A padding byte would leak kernel stack out through `copy_out` or accept an unwritten value through `copy_in`. - -// SAFETY: a primitive integer (and an array of them) is `#[repr(C)]`, has no padding, and every bit pattern is a value. -unsafe impl UserSafe for u32 {} -// SAFETY: see `u32`. -unsafe impl UserSafe for u64 {} -// SAFETY: see `u32` — an array adds no padding between elements. -unsafe impl UserSafe for [u32; 2] {} -// SAFETY: see `u32`. -unsafe impl UserSafe for [u64; 2] {} - -// SAFETY: `#[repr(C)] Copy`, three `u64`s, no padding; `file_type` is a `u64`, not the enum it names, so every bit pattern stays valid. -unsafe impl UserSafe for crate::object::ops::Stat {} - -// SAFETY: `#[repr(C)] Copy`, fourteen `u64`s, no padding; every field is validated where it is used, not here. -unsafe impl UserSafe for toyos_abi::syscall::SpawnArgs {} -// SAFETY: `#[repr(C)] Copy`, `RawHandle`, a `flags: u32`, then six `u64`s — no padding. -unsafe impl UserSafe for toyos_abi::syscall::NamespaceBuild {} -// SAFETY: `#[repr(C)] Copy`, `u64`, `u64`, `i64` — 24 bytes, no padding. -unsafe impl UserSafe for toyos_abi::syscall::SchedInfo {} -// SAFETY: `#[repr(C)] Copy`; the two `u32` pairs keep every `u64` 8-aligned, so there is no padding. -unsafe impl UserSafe for toyos_abi::syscall::ProcessStats {} -// SAFETY: `#[repr(C)] Copy`, `[RawHandle; 2]` then six `u32`s — align 4, no padding. -unsafe impl UserSafe for toyos_abi::FramebufferInfo {} -// SAFETY: `#[repr(C)] Copy`, `RawHandle`, an explicit `_pad: u32`, `u64` — no padding. -unsafe impl UserSafe for toyos_abi::syscall::InboxSetup {} - -// SAFETY: `#[repr(C)] Copy`, two `u8`s, no padding; `keycode`/`modifiers` are plain `u8`, not enums, so any bit pattern is valid. -unsafe impl UserSafe for toyos_abi::input::RawKeyEvent {} -// SAFETY: `#[repr(C)] Copy`, `u8`, `i8`, `u16`, `u16` — 2-aligned, no padding. -unsafe impl UserSafe for toyos_abi::input::MouseEvent {} - -// SAFETY: `#[repr(C)] Copy`, 88 bytes with no padding (checked by a compile-time size assertion); every field is clamped where it is used, not here. -unsafe impl UserSafe for toyos_abi::log::LogCursor {} -// SAFETY: `#[repr(transparent)]` over the `LogCursor` above. -unsafe impl UserSafe for toyos_abi::trace::TraceCursor {} - pub(crate) use toyos_userbound::Access; fn translate_now( diff --git a/toyos-abi/src/acpi.rs b/toyos-abi/src/acpi.rs index ee29c76aa8b..24d65de557f 100644 --- a/toyos-abi/src/acpi.rs +++ b/toyos-abi/src/acpi.rs @@ -10,12 +10,13 @@ //! holder clears the status bits behind it and then acknowledges the claim //! ([`ACK`]); a line acknowledged with a status bit still set is taken again. -/// A run of ports a register block occupies; `len` 0 is no block. -#[repr(C)] -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct Block { - pub port: u16, - pub len: u16, +crate::user_safe! { + /// A run of ports a register block occupies; `len` 0 is no block. + #[derive(Clone, Copy, PartialEq, Eq, Debug)] + pub struct Block { + pub port: u16, + pub len: u16, + } } impl Block { @@ -32,35 +33,25 @@ impl Block { /// `PWRBTN_EN` in the PM1 event block. pub const FIXED_POWER_BUTTON: u16 = 1 << 0; -/// The claim's description. -#[repr(C)] -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct AcpiInfo { - /// The PM1a event block, its status half then its enable half. - pub pm1_event: Block, - /// The GPE0 block, its status half then its enable half. - pub gpe0: Block, - /// The embedded controller's command/status and data ports, as the ECDT - /// names them; `len` 0 where the machine named none. - pub ec_command: Block, - pub ec_data: Block, - /// The GPE the embedded controller raises, inside [`Self::gpe0`]. - pub ec_gpe: u16, - pub flags: u16, +crate::user_safe! { + /// The claim's description. + #[derive(Clone, Copy, PartialEq, Eq, Debug)] + pub struct AcpiInfo { + /// The PM1a event block, its status half then its enable half. + pub pm1_event: Block, + /// The GPE0 block, its status half then its enable half. + pub gpe0: Block, + /// The embedded controller's command/status and data ports, as the ECDT + /// names them; `len` 0 where the machine named none. + pub ec_command: Block, + pub ec_data: Block, + /// The GPE the embedded controller raises, inside [`Self::gpe0`]. + pub ec_gpe: u16, + pub flags: u16, + } } -/// Every byte belongs to a field: this crosses the boundary through -/// `as_bytes`, so a gap would publish whatever the kernel stack held. -const _: () = assert!(core::mem::size_of::() == 4 * 4 + 2 + 2); - impl AcpiInfo { - pub fn as_bytes(&self) -> &[u8] { - // SAFETY: `self` is a valid `&Self`, and the const assert above proves - // the `repr(C)` layout of `u16`s has no padding, so every byte the - // slice exposes is an initialized field. - unsafe { core::slice::from_raw_parts(self as *const Self as *const u8, core::mem::size_of::()) } - } - pub fn has_ec(&self) -> bool { self.ec_command.len != 0 } diff --git a/toyos-abi/src/handle.rs b/toyos-abi/src/handle.rs index 85e16841057..d6cac4d37d6 100644 --- a/toyos-abi/src/handle.rs +++ b/toyos-abi/src/handle.rs @@ -6,15 +6,16 @@ //! up from zero, resolves to that process's own slot or to nothing at all — so //! holding a number is never the authority. -/// One entry in a process's handle table. -/// -/// Twelve bits of slot and twenty of generation, in one `u32` so it costs a -/// register at the syscall boundary. **A slot at generation 0 encodes as the -/// bare index**, which is what keeps stdio literally `0`, `1`, `2` and the std -/// fork's stdio plumbing untouched. -#[repr(transparent)] -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] -pub struct RawHandle(pub u32); +crate::user_safe! { + /// One entry in a process's handle table. + /// + /// Twelve bits of slot and twenty of generation, in one `u32` so it costs a + /// register at the syscall boundary. **A slot at generation 0 encodes as the + /// bare index**, which is what keeps stdio literally `0`, `1`, `2` and the std + /// fork's stdio plumbing untouched. + #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] + pub struct RawHandle(pub u32); +} impl RawHandle { const SLOT_BITS: u32 = 12; diff --git a/toyos-abi/src/hda.rs b/toyos-abi/src/hda.rs index b094b0158ac..d71960cc5b5 100644 --- a/toyos-abi/src/hda.rs +++ b/toyos-abi/src/hda.rs @@ -18,54 +18,33 @@ //! [`syscall::device_reg_read`]: crate::syscall::device_reg_read //! [`syscall::device_reg_write`]: crate::syscall::device_reg_write -/// The controller and stream the kernel brought up, as the driver needs to see -/// them. -/// -/// No register window and no physical address: everything here is a shared -/// memory token, a shape the driver has to know to fill the ring, or a number -/// it has to send a codec. -#[repr(C)] -#[derive(Clone, Copy)] -pub struct HdaInfo { - /// The PCM ring, mapped writable. `periods` buffers of `period_bytes` laid - /// end to end from the start of the region, with the buffer descriptor - /// list already pointing at them. - pub pcm: crate::RawHandle, - pub period_bytes: u32, - /// Byte offset of the output stream descriptor inside the register window, - /// so the driver names `SDnCTL` and `SDnFMT` by the same arithmetic the - /// allow-list does. - pub stream_offset: u32, - /// Every codec address `STATESTS` reported, one bit per link address. The - /// driver enumerates all of them and chooses by capability; the kernel - /// read this register to know the link is alive and decided nothing with - /// it. - pub statests: u16, - /// The stream tag the kernel put in the descriptor. It has to reach the - /// codec's converter, and sending that verb is the driver's. - pub stream_tag: u8, - pub periods: u8, -} - -/// Every byte belongs to a field: this crosses the boundary through -/// `as_bytes`, so a gap would publish whatever the kernel stack held. -const _: () = { - let named = 4 + 4 + 4 + 2 + 1 + 1; - assert!(core::mem::size_of::() == named); -}; - -impl HdaInfo { - pub fn as_bytes(&self) -> &[u8] { - // SAFETY: `self` is a valid `&Self` (non-null, aligned, readable for - // `size_of::()` bytes), and the const assert above proves the - // `repr(C)` layout has no padding, so every byte the slice exposes is - // an initialized field, not a gap. - unsafe { - core::slice::from_raw_parts( - self as *const Self as *const u8, - core::mem::size_of::(), - ) - } +crate::user_safe! { + /// The controller and stream the kernel brought up, as the driver needs to see + /// them. + /// + /// No register window and no physical address: everything here is a shared + /// memory token, a shape the driver has to know to fill the ring, or a number + /// it has to send a codec. + #[derive(Clone, Copy)] + pub struct HdaInfo { + /// The PCM ring, mapped writable. `periods` buffers of `period_bytes` laid + /// end to end from the start of the region, with the buffer descriptor + /// list already pointing at them. + pub pcm: crate::RawHandle, + pub period_bytes: u32, + /// Byte offset of the output stream descriptor inside the register window, + /// so the driver names `SDnCTL` and `SDnFMT` by the same arithmetic the + /// allow-list does. + pub stream_offset: u32, + /// Every codec address `STATESTS` reported, one bit per link address. The + /// driver enumerates all of them and chooses by capability; the kernel + /// read this register to know the link is alive and decided nothing with + /// it. + pub statests: u16, + /// The stream tag the kernel put in the descriptor. It has to reach the + /// codec's converter, and sending that verb is the driver's. + pub stream_tag: u8, + pub periods: u8, } } diff --git a/toyos-abi/src/input.rs b/toyos-abi/src/input.rs index 102e3649e20..68524c03069 100644 --- a/toyos-abi/src/input.rs +++ b/toyos-abi/src/input.rs @@ -9,28 +9,25 @@ pub const MOD_ALT: u8 = 4; pub const MOD_GUI: u8 = 8; pub const MOD_RELEASED: u8 = 0x10; -/// One key transition, as the kernel delivers it through the keyboard handle. -/// -/// The whole of what the kernel knows about a key: which physical key moved, -/// which way, and what the machine's modifiers were when it did. There is no -/// layout in the kernel, so there is nothing here that a layout could change. -/// -/// **`modifiers` is the union across every keyboard the machine has**, which -/// is why it is here rather than derived by whoever translates: Shift held on -/// one keyboard and a letter typed on another must produce a capital, and a -/// surface that reconstructs the mask from the transitions it saw has not seen -/// the ones that arrived while another surface had the focus. -#[repr(C)] -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct RawKeyEvent { - pub keycode: u8, - pub modifiers: u8, +crate::user_safe! { + /// One key transition, as the kernel delivers it through the keyboard handle. + /// + /// The whole of what the kernel knows about a key: which physical key moved, + /// which way, and what the machine's modifiers were when it did. There is no + /// layout in the kernel, so there is nothing here that a layout could change. + /// + /// **`modifiers` is the union across every keyboard the machine has**, which + /// is why it is here rather than derived by whoever translates: Shift held on + /// one keyboard and a letter typed on another must produce a capital, and a + /// surface that reconstructs the mask from the transitions it saw has not seen + /// the ones that arrived while another surface had the focus. + #[derive(Clone, Copy, PartialEq, Eq, Debug)] + pub struct RawKeyEvent { + pub keycode: u8, + pub modifiers: u8, + } } -/// Every byte belongs to a field: this crosses the boundary through -/// `as_bytes`, so a gap would publish whatever the kernel stack held. -const _: () = assert!(core::mem::size_of::() == 1 + 1); - impl RawKeyEvent { pub fn pressed(&self) -> bool { self.modifiers & MOD_RELEASED == 0 } pub fn released(&self) -> bool { self.modifiers & MOD_RELEASED != 0 } @@ -38,41 +35,16 @@ impl RawKeyEvent { pub fn ctrl(&self) -> bool { self.modifiers & MOD_CTRL != 0 } pub fn alt(&self) -> bool { self.modifiers & MOD_ALT != 0 } pub fn gui(&self) -> bool { self.modifiers & MOD_GUI != 0 } - - pub fn as_bytes(&self) -> &[u8] { - // SAFETY: `self` is a valid `&Self` (non-null, aligned, readable for - // `size_of::()` bytes), and the const assert above proves the - // `repr(C)` layout has no padding, so every byte the slice exposes is - // an initialized field, not a gap. - unsafe { - core::slice::from_raw_parts(self as *const Self as *const u8, core::mem::size_of::()) - } - } } -/// A mouse/tablet event as delivered by the kernel through the mouse handle. -/// Carries absolute coordinates (0–32767) from the USB tablet. -#[repr(C)] -#[derive(Clone, Copy)] -pub struct MouseEvent { - pub buttons: u8, - pub scroll: i8, - pub abs_x: u16, - pub abs_y: u16, -} - -/// Every byte belongs to a field: this crosses the boundary through -/// `as_bytes`, so a gap would publish whatever the kernel stack held. -const _: () = assert!(core::mem::size_of::() == 1 + 1 + 2 + 2); - -impl MouseEvent { - pub fn as_bytes(&self) -> &[u8] { - // SAFETY: `self` is a valid `&Self` (non-null, aligned, readable for - // `size_of::()` bytes), and the const assert above proves the - // `repr(C)` layout has no padding, so every byte the slice exposes is - // an initialized field, not a gap. - unsafe { - core::slice::from_raw_parts(self as *const Self as *const u8, core::mem::size_of::()) - } +crate::user_safe! { + /// A mouse/tablet event as delivered by the kernel through the mouse handle. + /// Carries absolute coordinates (0–32767) from the USB tablet. + #[derive(Clone, Copy)] + pub struct MouseEvent { + pub buttons: u8, + pub scroll: i8, + pub abs_x: u16, + pub abs_y: u16, } } diff --git a/toyos-abi/src/lib.rs b/toyos-abi/src/lib.rs index 70b26c0be7e..469b1c72882 100644 --- a/toyos-abi/src/lib.rs +++ b/toyos-abi/src/lib.rs @@ -31,9 +31,11 @@ pub mod pci; pub mod ring; pub mod syscall; pub mod trace; +pub mod usersafe; pub mod virtio_sound; pub use handle::{RawHandle, Rights, HANDLE_INVALID}; +pub use usersafe::UserSafe; /// A process ID. Identifies a process — owns address space, handles, vruntime. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -73,37 +75,23 @@ impl core::ops::Add for Tid { fn add(self, rhs: Self) -> Self { Tid(self.0 + rhs.0) } } -/// GPU framebuffer info passed between kernel and userland. -/// Shared definition so both sides agree on the layout. -/// -/// **The three handles are installed by the read that answers this.** A -/// description is a set of buffers, and the process being told about them is -/// the one that must be able to map them — which is never the process that -/// minted the claim, because the supervisor mints every claim and holds none. -#[repr(C)] -#[derive(Clone, Copy)] -pub struct FramebufferInfo { - pub scanout: [RawHandle; 2], - pub cursor: RawHandle, - pub width: u32, - pub height: u32, - pub stride: u32, - pub pixel_format: u32, - pub flags: u32, -} - -/// Every byte belongs to a field: this crosses the boundary through -/// `as_bytes`, so a gap would publish whatever the kernel stack held. Every -/// field here is a `u32` or a `repr(transparent)` wrapper over one, so the -/// `repr(C)` layout has no padding without needing a separate size check. -impl FramebufferInfo { - pub fn as_bytes(&self) -> &[u8] { - // SAFETY: `self` is a valid `&Self` (non-null, aligned, readable for - // `size_of::()` bytes) and, per the doc comment above, every - // byte the slice exposes is an initialized field, not a padding gap. - unsafe { - core::slice::from_raw_parts(self as *const Self as *const u8, core::mem::size_of::()) - } +crate::user_safe! { + /// GPU framebuffer info passed between kernel and userland. + /// Shared definition so both sides agree on the layout. + /// + /// **The three handles are installed by the read that answers this.** A + /// description is a set of buffers, and the process being told about them is + /// the one that must be able to map them — which is never the process that + /// minted the claim, because the supervisor mints every claim and holds none. + #[derive(Clone, Copy)] + pub struct FramebufferInfo { + pub scanout: [RawHandle; 2], + pub cursor: RawHandle, + pub width: u32, + pub height: u32, + pub stride: u32, + pub pixel_format: u32, + pub flags: u32, } } diff --git a/toyos-abi/src/log.rs b/toyos-abi/src/log.rs index 0cf486e0d75..1ee089f74be 100644 --- a/toyos-abi/src/log.rs +++ b/toyos-abi/src/log.rs @@ -80,41 +80,35 @@ impl Severity { /// counter at, so its `at_ns` is no time and its line says so ([`UNTIMED`]). pub const FLAG_UNTIMED: u8 = 1 << 0; -/// What a reader gets. Plain POD, `Copy`, no interior mutability. -#[repr(C, align(64))] -#[derive(Clone, Copy)] -pub struct LogRecord { - /// The record's identity. In the kernel's slot this same word is also the - /// validity word; by the time a reader holds a copy it is just the sequence - /// number, and it is what [`LogCursor::next`] counts in. - pub seq: u64, - /// Nanoseconds since the counter's zero: [`crate::clock::stamp_ns`]'s reading. - pub at_ns: u64, - pub pid: u32, - pub tid: u32, - pub cpu: u16, - /// Message bytes present in `msg`, never above [`MAX_RECORD_MESSAGE`]. - pub len: u16, - /// Bytes the message would have had past [`MAX_RECORD_MESSAGE`], - /// saturating. **Never a silent truncation** — this is the difference - /// between a bound and a lie. - pub elided: u16, - pub severity: u8, - /// [`FLAG_UNTIMED`] and nothing else yet. - pub flags: u8, - pub msg: [u8; MAX_RECORD_MESSAGE], +crate::user_safe! { + /// What a reader gets. Plain POD, `Copy`, no interior mutability. + #[repr(align(64))] + #[derive(Clone, Copy)] + pub struct LogRecord { + /// The record's identity. In the kernel's slot this same word is also the + /// validity word; by the time a reader holds a copy it is just the sequence + /// number, and it is what [`LogCursor::next`] counts in. + pub seq: u64, + /// Nanoseconds since the counter's zero: [`crate::clock::stamp_ns`]'s reading. + pub at_ns: u64, + pub pid: u32, + pub tid: u32, + pub cpu: u16, + /// Message bytes present in `msg`, never above [`MAX_RECORD_MESSAGE`]. + pub len: u16, + /// Bytes the message would have had past [`MAX_RECORD_MESSAGE`], + /// saturating. **Never a silent truncation** — this is the difference + /// between a bound and a lie. + pub elided: u16, + pub severity: u8, + /// [`FLAG_UNTIMED`] and nothing else yet. + pub flags: u8, + pub msg: [u8; MAX_RECORD_MESSAGE], + } } const _: () = assert!(core::mem::size_of::() == RECORD_BYTES); const _: () = assert!(core::mem::align_of::() == 64); -/// Every byte belongs to a field: this crosses the boundary through -/// [`LogRecord::as_bytes`], so a gap would publish whatever the kernel stack -/// held. Spelled as the sum of the field widths rather than as -/// [`RECORD_BYTES`], which is the *other* claim about this struct — a padded -/// layout that happened to reach 1024 bytes would satisfy that one. -const _: () = assert!( - core::mem::size_of::() == 8 + 8 + 4 + 4 + 2 + 2 + 2 + 1 + 1 + MAX_RECORD_MESSAGE -); /// The kernel's slot is this layout with the first word made atomic, so the /// body it copies is everything past that word and must start where it does. const _: () = assert!(core::mem::offset_of!(LogRecord, at_ns) == core::mem::size_of::()); @@ -160,22 +154,6 @@ impl LogRecord { } } - /// The record's own bytes, which is what goes on the wire. - /// - /// Here rather than at the kernel's copy-out, the shape every ABI struct in - /// this crate has: the `unsafe` belongs beside the layout assertion that - /// discharges it, not beside the caller that happens to need it. - #[inline] - pub fn as_bytes(&self) -> &[u8] { - // SAFETY: `self` is a valid `&Self` (non-null, aligned, readable for - // `size_of::()` bytes), and the const assert above proves the - // `repr(C)` layout has no padding, so every byte the slice exposes is - // an initialized field, not a gap. - unsafe { - core::slice::from_raw_parts(self as *const Self as *const u8, core::mem::size_of::()) - } - } - pub fn severity(&self) -> Option { Severity::from_u8(self.severity) } @@ -295,34 +273,33 @@ impl core::fmt::Display for Head<'_> { } } -/// Per-reader state. **The kernel holds none.** -/// -/// No object, no handle lifecycle, no cursor to leak or go stale, and a second -/// reader costs nothing. The stream is not consumed either: `logkeeper` and a -/// `log-follow` tool coexist with no coordination. -#[repr(C)] -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub struct LogCursor { - /// Out: how many shards the machine has. A caller passes a zeroed cursor - /// the first time and reads it back. - pub shards: u32, - pub _pad: u32, - /// Out: records this read skipped because they were overwritten. The - /// kernel never reads it, so a reader's total is the reader's own sum. +crate::user_safe! { + /// Per-reader state. **The kernel holds none.** /// - /// **Derived, never counted by a producer.** The kernel computes it from - /// `head` and `next`, which both have to be right anyway, so no counter can - /// drift from the ring. It lives here so a reader that ignores loss has to - /// actively ignore a field it is already passing. - pub lost: u64, - /// In/out: the next sequence number wanted from each shard. A number past - /// the one the shard issues next is refused, and a shard not yet published - /// issues 1 next. - pub next: [u64; MAX_LOG_SHARDS], + /// No object, no handle lifecycle, no cursor to leak or go stale, and a second + /// reader costs nothing. The stream is not consumed either: `logkeeper` and a + /// `log-follow` tool coexist with no coordination. + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] + pub struct LogCursor { + /// Out: how many shards the machine has. A caller passes a zeroed cursor + /// the first time and reads it back. + pub shards: u32, + pub _pad: u32, + /// Out: records this read skipped because they were overwritten. The + /// kernel never reads it, so a reader's total is the reader's own sum. + /// + /// **Derived, never counted by a producer.** The kernel computes it from + /// `head` and `next`, which both have to be right anyway, so no counter can + /// drift from the ring. It lives here so a reader that ignores loss has to + /// actively ignore a field it is already passing. + pub lost: u64, + /// In/out: the next sequence number wanted from each shard. A number past + /// the one the shard issues next is refused, and a shard not yet published + /// issues 1 next. + pub next: [u64; MAX_LOG_SHARDS], + } } -const _: () = assert!(core::mem::size_of::() == 16 + 8 * MAX_LOG_SHARDS); - impl LogCursor { /// A cursor that has read nothing. The kernel fills `shards` on the first /// call. @@ -350,15 +327,15 @@ mod tests { /// **The encoder is the wire, so the test decodes the wire.** /// - /// Not `as_bytes().len() == RECORD_BYTES`, which a padded struct passes: + /// Not `bytes(&r).len() == RECORD_BYTES`, which a padded struct passes: /// every field is read back out of the slice at the offset `#[repr(C)]` /// puts it at, and the tail is the message. A gap anywhere before `msg` /// shifts one of these and the assertion that catches it is the one whose /// field moved. #[test] - fn as_bytes_is_the_fields_and_nothing_between_them() { + fn its_bytes_are_the_fields_and_nothing_between_them() { let r = record("hello"); - let b = r.as_bytes(); + let b = crate::usersafe::bytes(&r); assert_eq!(b.len(), RECORD_BYTES); assert_eq!(u64::from_ne_bytes(b[0..8].try_into().unwrap()), 7); assert_eq!(u64::from_ne_bytes(b[8..16].try_into().unwrap()), 1_234_567_890); diff --git a/toyos-abi/src/part.rs b/toyos-abi/src/part.rs index d269d2f4994..9b82d9a5101 100644 --- a/toyos-abi/src/part.rs +++ b/toyos-abi/src/part.rs @@ -114,37 +114,22 @@ impl PartGuid { } } -/// The partition a claim holds, as its holder reads it once off the claim. -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct PartitionInfo { - /// The partition's length in [`BLOCK_BYTES`] blocks; block numbers - /// `0..blocks` are the whole of what the claim addresses. - pub blocks: u64, - pub unique_guid: [u8; 16], +crate::user_safe! { + /// The partition a claim holds, as its holder reads it once off the claim. + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub struct PartitionInfo { + /// The partition's length in [`BLOCK_BYTES`] blocks; block numbers + /// `0..blocks` are the whole of what the claim addresses. + pub blocks: u64, + pub unique_guid: [u8; 16], + } } -/// Every byte belongs to a field: this crosses the boundary through -/// `as_bytes`, so a gap would publish whatever the kernel stack held. -const _: () = assert!(core::mem::size_of::() == 8 + 16); - impl PartitionInfo { /// The partition's unique GUID, as its `part:` entry names it. pub const fn unique(&self) -> PartGuid { PartGuid(self.unique_guid) } - - pub fn as_bytes(&self) -> &[u8] { - // SAFETY: `self` is a valid `&Self`, readable for `size_of::()` - // bytes, and the const assert above proves the `repr(C)` layout has no - // padding, so every byte the slice exposes is an initialized field. - unsafe { - core::slice::from_raw_parts( - self as *const Self as *const u8, - core::mem::size_of::(), - ) - } - } } #[cfg(test)] diff --git a/toyos-abi/src/pci.rs b/toyos-abi/src/pci.rs index 2c41ee9cdec..3323e5a0e5c 100644 --- a/toyos-abi/src/pci.rs +++ b/toyos-abi/src/pci.rs @@ -19,49 +19,28 @@ /// every index in this module is checked against. pub const BARS: usize = 6; -/// The function the claim names, as its driver needs to see it before it has -/// mapped anything. -/// -/// No addresses: a BAR's *size* is what a driver bounds its own accesses with, -/// and where the window sits is [`syscall::device_bar_map`]'s answer, so the -/// kernel stays free to move a BAR to a boundary it can map. -/// -/// [`syscall::device_bar_map`]: crate::syscall::device_bar_map -#[repr(C)] -#[derive(Clone, Copy)] -pub struct PciFunctionInfo { - /// Byte size of each memory BAR; 0 where the function has none in that - /// slot, or where it is one the kernel will not map — the BAR holding this - /// function's MSI-X table or PBA reads 0 here. - pub bar_bytes: [u64; BARS], - /// Where firmware put it. Identity for a log line, never a name a claim is - /// looked up by: nothing in this ABI takes a bus/device/function. - pub bus: u8, - pub dev: u8, - pub func: u8, - /// Written, never read: the tail `repr(C)` would otherwise leave as a gap. - pub _pad: [u8; 5], -} - -/// Every byte belongs to a field: this crosses the boundary through -/// `as_bytes`, so a gap would publish whatever the kernel stack held. -const _: () = { - let named = BARS * 8 + 1 + 1 + 1 + 5; - assert!(core::mem::size_of::() == named); -}; - -impl PciFunctionInfo { - pub fn as_bytes(&self) -> &[u8] { - // SAFETY: `self` is a valid `&Self` (non-null, aligned, readable for - // `size_of::()` bytes), and the const assert above proves the - // `repr(C)` layout has no padding, so every byte the slice exposes is - // an initialized field, not a gap. - unsafe { - core::slice::from_raw_parts( - self as *const Self as *const u8, - core::mem::size_of::(), - ) - } +crate::user_safe! { + /// The function the claim names, as its driver needs to see it before it has + /// mapped anything. + /// + /// No addresses: a BAR's *size* is what a driver bounds its own accesses with, + /// and where the window sits is [`syscall::device_bar_map`]'s answer, so the + /// kernel stays free to move a BAR to a boundary it can map. + /// + /// [`syscall::device_bar_map`]: crate::syscall::device_bar_map + #[derive(Clone, Copy)] + pub struct PciFunctionInfo { + /// Byte size of each memory BAR; 0 where the function has none in that + /// slot, or where it is one the kernel will not map — the BAR holding this + /// function's MSI-X table or PBA reads 0 here. + pub bar_bytes: [u64; BARS], + /// Where firmware put it. Identity for a log line, never a name a claim is + /// looked up by: nothing in this ABI takes a bus/device/function. + pub bus: u8, + pub dev: u8, + pub func: u8, + /// Written, never read: the tail `repr(C)` would otherwise leave as a gap. + pub _pad: [u8; 5], } } diff --git a/toyos-abi/src/syscall.rs b/toyos-abi/src/syscall.rs index 7b23d20b1f3..3e3260d780e 100644 --- a/toyos-abi/src/syscall.rs +++ b/toyos-abi/src/syscall.rs @@ -298,71 +298,70 @@ const _: () = assert!(SYS_TRACE_READ < SYSCALL_PROFILE_OTHER as u64); pub const WNOHANG: u64 = 1; -/// Arguments for the `SYS_SPAWN` syscall, passed as a single pointer. -/// -/// **Two vectors, two verbs.** `slot_map` *duplicates* — the parent keeps its -/// stdout — and `endow` *moves*, so a parent that wants to keep what it endows -/// duplicates first. That is what makes endowing a device claim work with no -/// special case: a claim carries no [`Rights::DUP`], so the move is the only -/// expressible form and the parent provably no longer holds it. -/// -/// [`Rights::DUP`]: crate::handle::Rights::DUP -#[repr(C)] -#[derive(Clone, Copy)] -pub struct SpawnArgs { - /// The program: the file the kernel opens and pages the child from — its - /// libraries are found beside it — or, with an `image`, the path the caller - /// read that from. **Never `argv[0]`**, which is the child's first argument - /// and the name it goes by, and which nothing opens. - pub path_ptr: u64, - pub path_len: u64, - pub argv_ptr: u64, - pub argv_len: u64, - /// `[[child_slot: u32, parent_handle: RawHandle]]`, duplicated into the - /// child. Stdio and nothing else, in practice. - pub slot_map_ptr: u64, - pub slot_map_count: u64, - pub env_ptr: u64, - pub env_len: u64, - /// `[EndowEntry]`, moved out of the parent's table. - pub endow_ptr: u64, - pub endow_count: u64, - /// The label blob every [`EndowEntry`]'s `label_off`/`label_len` indexes. - pub labels_ptr: u64, - pub labels_len: u64, - /// The child's working directory, absolute, or the spawn is refused - /// `InvalidArgument`. Under a name the kernel serves (`/system`, `/tmp`) it - /// must be a directory there or the spawn is `NotFound`; under any other - /// name it is a file server's directory, which the caller's client asked - /// that server about and the kernel cannot. **Always the caller's - /// statement**: the kernel never substitutes the caller's own. - pub cwd_ptr: u64, - pub cwd_len: u64, - /// The program's bytes, in a shared memory object the caller made and - /// read the program into: `image` is a handle to it carrying `MAP`, and - /// `image_len` how many of its first bytes the program is — or 0, for a - /// program the kernel opens at `path` itself. `PermissionDenied` for a - /// handle without `MAP`; `InvalidArgument` for a length the object does not - /// hold, or an object that is no memory the kernel allocated. The object - /// stays the caller's: what the kernel reads of it, and when, is - /// `kernel/src/file_backing.rs`'s (`SharedImage`). `path` is then opened by - /// nobody, and the libraries are found in `/system/lib` alone. - pub image: u64, - pub image_len: u64, - /// The process the child is placed under, whose end takes it down: a - /// handle carrying [`Rights::WRITE`] to it — a copy of that process's - /// [`SELF_LABEL`] — or [`HANDLE_INVALID`] for the caller itself. - /// `PermissionDenied` for a handle without `WRITE` and `InvalidArgument` - /// for one to no process, since a place is a handle a peer sent; - /// `Gone` for a process being torn down, and `ResourceExhausted` for a - /// child more than `kernel::proclife::MAX_DEPTH` below the supervisor. +crate::user_safe! { + /// Arguments for the `SYS_SPAWN` syscall, passed as a single pointer. + /// + /// **Two vectors, two verbs.** `slot_map` *duplicates* — the parent keeps its + /// stdout — and `endow` *moves*, so a parent that wants to keep what it endows + /// duplicates first. That is what makes endowing a device claim work with no + /// special case: a claim carries no [`Rights::DUP`], so the move is the only + /// expressible form and the parent provably no longer holds it. /// - /// [`Rights::WRITE`]: crate::handle::Rights::WRITE - pub place: u64, + /// [`Rights::DUP`]: crate::handle::Rights::DUP + #[derive(Clone, Copy)] + pub struct SpawnArgs { + /// The program: the file the kernel opens and pages the child from — its + /// libraries are found beside it — or, with an `image`, the path the caller + /// read that from. **Never `argv[0]`**, which is the child's first argument + /// and the name it goes by, and which nothing opens. + pub path_ptr: u64, + pub path_len: u64, + pub argv_ptr: u64, + pub argv_len: u64, + /// `[[child_slot: u32, parent_handle: RawHandle]]`, duplicated into the + /// child. Stdio and nothing else, in practice. + pub slot_map_ptr: u64, + pub slot_map_count: u64, + pub env_ptr: u64, + pub env_len: u64, + /// `[EndowEntry]`, moved out of the parent's table. + pub endow_ptr: u64, + pub endow_count: u64, + /// The label blob every [`EndowEntry`]'s `label_off`/`label_len` indexes. + pub labels_ptr: u64, + pub labels_len: u64, + /// The child's working directory, absolute, or the spawn is refused + /// `InvalidArgument`. Under a name the kernel serves (`/system`, `/tmp`) it + /// must be a directory there or the spawn is `NotFound`; under any other + /// name it is a file server's directory, which the caller's client asked + /// that server about and the kernel cannot. **Always the caller's + /// statement**: the kernel never substitutes the caller's own. + pub cwd_ptr: u64, + pub cwd_len: u64, + /// The program's bytes, in a shared memory object the caller made and + /// read the program into: `image` is a handle to it carrying `MAP`, and + /// `image_len` how many of its first bytes the program is — or 0, for a + /// program the kernel opens at `path` itself. `PermissionDenied` for a + /// handle without `MAP`; `InvalidArgument` for a length the object does not + /// hold, or an object that is no memory the kernel allocated. The object + /// stays the caller's: what the kernel reads of it, and when, is + /// `kernel/src/file_backing.rs`'s (`SharedImage`). `path` is then opened by + /// nobody, and the libraries are found in `/system/lib` alone. + pub image: u64, + pub image_len: u64, + /// The process the child is placed under, whose end takes it down: a + /// handle carrying [`Rights::WRITE`] to it — a copy of that process's + /// [`SELF_LABEL`] — or [`HANDLE_INVALID`] for the caller itself. + /// `PermissionDenied` for a handle without `WRITE` and `InvalidArgument` + /// for one to no process, since a place is a handle a peer sent; + /// `Gone` for a process being torn down, and `ResourceExhausted` for a + /// child more than `kernel::proclife::MAX_DEPTH` below the supervisor. + /// + /// [`Rights::WRITE`]: crate::handle::Rights::WRITE + pub place: u64, + } } -const _: () = assert!(core::mem::size_of::() == 136); - /// One `(label, handle)` pair of a process's endowment table. /// /// `label_off`/`label_len` index the label blob that travels beside the @@ -1805,32 +1804,32 @@ pub struct NameRef { pub len: u32, } -/// Arguments for [`SYS_NAMESPACE_BUILD`], passed as a single pointer. -/// -/// A namespace is immutable once built: there is no insert, no remove and no -/// replace, so a narrower one is a *new* object built from this one and a -/// handle to a namespace is a handle to a fixed set. -#[repr(C)] -#[derive(Clone, Copy)] -pub struct NamespaceBuild { - /// [`HANDLE_INVALID`] for an empty base. +crate::user_safe! { + /// Arguments for [`SYS_NAMESPACE_BUILD`], passed as a single pointer. /// - /// [`HANDLE_INVALID`]: crate::handle::HANDLE_INVALID - pub base: RawHandle, - /// [`NAMESPACE_KEEP_ALL`], and nothing else. - pub flags: u32, - /// `[NameRef]` — the names to carry over from `base`. - pub keep_ptr: u64, - pub keep_n: u64, - /// `[NamespaceEntry]` — new bindings. - pub add_ptr: u64, - pub add_n: u64, - /// The blob every `off`/`len` above indexes into. - pub names_ptr: u64, - pub names_len: u64, -} - -const _: () = assert!(core::mem::size_of::() == 56); + /// A namespace is immutable once built: there is no insert, no remove and no + /// replace, so a narrower one is a *new* object built from this one and a + /// handle to a namespace is a handle to a fixed set. + #[derive(Clone, Copy)] + pub struct NamespaceBuild { + /// [`HANDLE_INVALID`] for an empty base. + /// + /// [`HANDLE_INVALID`]: crate::handle::HANDLE_INVALID + pub base: RawHandle, + /// [`NAMESPACE_KEEP_ALL`], and nothing else. + pub flags: u32, + /// `[NameRef]` — the names to carry over from `base`. + pub keep_ptr: u64, + pub keep_n: u64, + /// `[NamespaceEntry]` — new bindings. + pub add_ptr: u64, + pub add_n: u64, + /// The blob every `off`/`len` above indexes into. + pub names_ptr: u64, + pub names_len: u64, + } +} + const _: () = assert!(core::mem::size_of::() == 16); const _: () = assert!(core::mem::size_of::() == 8); @@ -2398,20 +2397,19 @@ pub fn tls_alloc_block(module_id: u64) -> Result { check(syscall(SYS_TLS_ALLOC_BLOCK, module_id, 0, 0, 0)) } -/// An inbox and where its page of rings is mapped. -/// -/// **The inbox owns its page and the kernel maps it at setup**, so the mapping -/// has no lifetime of its own: it ends when the last handle to the inbox closes. -#[repr(C)] -#[derive(Clone, Copy)] -pub struct InboxSetup { - pub handle: RawHandle, - pub _pad: u32, - pub vaddr: u64, +crate::user_safe! { + /// An inbox and where its page of rings is mapped. + /// + /// **The inbox owns its page and the kernel maps it at setup**, so the mapping + /// has no lifetime of its own: it ends when the last handle to the inbox closes. + #[derive(Clone, Copy)] + pub struct InboxSetup { + pub handle: RawHandle, + pub _pad: u32, + pub vaddr: u64, + } } -const _: () = assert!(core::mem::size_of::() == 16); - /// Create an [`inbox`](crate::inbox) with the given queue depth (a power of /// two, at most 256), and map its rings. /// @@ -2444,55 +2442,31 @@ pub fn inbox_submit(handle: RawHandle, to_submit: u32, min_complete: u32, timeou // Module info (for stack unwinding / backtraces) -/// Information about a loaded module (executable or shared library). -/// -/// Buffer layout returned by `SYS_QUERY_MODULES`: -/// `[ModuleInfo; count]` followed by packed path strings. -/// Each `ModuleInfo::path_offset` is relative to the start of the buffer. -#[repr(C)] -#[derive(Clone, Copy)] -pub struct ModuleInfo { - /// Load base address (bias) of this module. - pub base: u64, - /// End of the last mapped segment (base + vaddr_max). - pub text_end: u64, - /// Absolute virtual address of `.eh_frame_hdr` (0 if none). - pub eh_frame_hdr: u64, - /// Size of `.eh_frame_hdr` in bytes. - pub eh_frame_hdr_size: u64, - /// Absolute virtual address of the module's program header table: the - /// kernel loads no module whose table a `PT_LOAD` does not map. - pub phdr: u64, - /// Entries in that table, `e_phnum`: a `u64` so the record has no padding. - pub phnum: u64, - /// Byte offset of the module's path string within the buffer. - pub path_offset: u32, - /// Length of the path string in bytes. - pub path_len: u32, -} - -/// Every byte belongs to a field: this crosses the boundary through -/// [`ModuleInfo::as_bytes`], so a gap would publish whatever the kernel stack -/// held. **This is the type where that matters most**, because the buffer it -/// is written into is a user address. A field of any other width added here -/// reds here rather than publishing kernel stack bytes to userland. -const _: () = assert!(core::mem::size_of::() == 8 + 8 + 8 + 8 + 8 + 8 + 4 + 4); - -impl ModuleInfo { - /// The record's own bytes, which is what `SYS_QUERY_MODULES` writes. +crate::user_safe! { + /// Information about a loaded module (executable or shared library). /// - /// Here rather than at the kernel's copy-out, the shape every ABI struct in - /// this crate has: the `unsafe` belongs beside the layout assertion that - /// discharges it, not beside the caller that happens to need it. - #[inline] - pub fn as_bytes(&self) -> &[u8] { - // SAFETY: `self` is a valid `&Self` (non-null, aligned, readable for - // `size_of::()` bytes), and the const assert above proves the - // `repr(C)` layout has no padding, so every byte the slice exposes is - // an initialized field, not a gap. - unsafe { - core::slice::from_raw_parts(self as *const Self as *const u8, core::mem::size_of::()) - } + /// Buffer layout returned by `SYS_QUERY_MODULES`: + /// `[ModuleInfo; count]` followed by packed path strings. + /// Each `ModuleInfo::path_offset` is relative to the start of the buffer. + #[derive(Clone, Copy)] + pub struct ModuleInfo { + /// Load base address (bias) of this module. + pub base: u64, + /// End of the last mapped segment (base + vaddr_max). + pub text_end: u64, + /// Absolute virtual address of `.eh_frame_hdr` (0 if none). + pub eh_frame_hdr: u64, + /// Size of `.eh_frame_hdr` in bytes. + pub eh_frame_hdr_size: u64, + /// Absolute virtual address of the module's program header table: the + /// kernel loads no module whose table a `PT_LOAD` does not map. + pub phdr: u64, + /// Entries in that table, `e_phnum`: a `u64` so the record has no padding. + pub phnum: u64, + /// Byte offset of the module's path string within the buffer. + pub path_offset: u32, + /// Length of the path string in bytes. + pub path_len: u32, } } @@ -2533,22 +2507,23 @@ pub fn modules(answer: &[u8]) -> impl Iterator { }) } -/// Scheduler info for the calling process. -#[repr(C)] -#[derive(Clone, Copy, Debug)] -pub struct SchedInfo { - /// Current vruntime of this process (nanoseconds of virtual CPU time). - pub vruntime: u64, - /// Global min_vruntime frontier (monotonic non-decreasing). - pub min_vruntime: u64, - /// Signed contract lag, frozen at the most recent runnable-state - /// transition. Positive = process was behind the frontier when it last - /// woke / blocked (entitled to catch up); negative = process ran ahead - /// and will be throttled. Bounded to [-50ms, +50ms] (MAX_VRUNTIME_LAG_NS) - /// by construction. This is the scheduler's contract, not the live - /// `min_vruntime - vruntime` drift that accumulates while running on - /// multi-CPU systems — compute that at the call site if you need it. - pub lag: i64, +crate::user_safe! { + /// Scheduler info for the calling process. + #[derive(Clone, Copy, Debug)] + pub struct SchedInfo { + /// Current vruntime of this process (nanoseconds of virtual CPU time). + pub vruntime: u64, + /// Global min_vruntime frontier (monotonic non-decreasing). + pub min_vruntime: u64, + /// Signed contract lag, frozen at the most recent runnable-state + /// transition. Positive = process was behind the frontier when it last + /// woke / blocked (entitled to catch up); negative = process ran ahead + /// and will be throttled. Bounded to [-50ms, +50ms] (MAX_VRUNTIME_LAG_NS) + /// by construction. This is the scheduler's contract, not the live + /// `min_vruntime - vruntime` drift that accumulates while running on + /// multi-CPU systems — compute that at the call site if you need it. + pub lag: i64, + } } /// Get scheduler info for the calling process. @@ -2558,31 +2533,32 @@ pub fn sched_info() -> SchedInfo { info } -/// Per-process accounting statistics, as [`SYS_PROCESS_STATS`] answers them. -#[repr(C)] -#[derive(Clone, Copy, Default)] -pub struct ProcessStats { - pub wall_ns: u64, - pub cpu_ns: u64, - pub syscall_total: u64, - pub syscall_total_ns: u64, - pub fault_demand_count: u32, - pub fault_zero_count: u32, - pub fault_ns: u64, - pub io_read_ops: u32, - /// The process's own pid. Not authority — no syscall takes a pid — but it - /// is the name a diagnostic prints, and this is where a holder of a handle - /// reads it. - pub pid: u32, - pub io_read_bytes: u64, - pub blocked_io_ns: u64, - pub blocked_futex_ns: u64, - pub blocked_pipe_ns: u64, - pub blocked_ipc_ns: u64, - pub blocked_other_ns: u64, - pub runqueue_wait_ns: u64, - pub peak_memory: u64, - pub alloc_count: u64, +crate::user_safe! { + /// Per-process accounting statistics, as [`SYS_PROCESS_STATS`] answers them. + #[derive(Clone, Copy, Default)] + pub struct ProcessStats { + pub wall_ns: u64, + pub cpu_ns: u64, + pub syscall_total: u64, + pub syscall_total_ns: u64, + pub fault_demand_count: u32, + pub fault_zero_count: u32, + pub fault_ns: u64, + pub io_read_ops: u32, + /// The process's own pid. Not authority — no syscall takes a pid — but it + /// is the name a diagnostic prints, and this is where a holder of a handle + /// reads it. + pub pid: u32, + pub io_read_bytes: u64, + pub blocked_io_ns: u64, + pub blocked_futex_ns: u64, + pub blocked_pipe_ns: u64, + pub blocked_ipc_ns: u64, + pub blocked_other_ns: u64, + pub runqueue_wait_ns: u64, + pub peak_memory: u64, + pub alloc_count: u64, + } } /// Read accounting for the process a `Process` handle names, alive or exited. @@ -2604,13 +2580,11 @@ mod tests { /// **The encoder is the wire, so the test decodes the wire.** /// - /// Not `as_bytes().len() == size_of::()`, which a padded + /// Not `bytes(&info).len() == size_of::()`, which a padded /// struct passes: every field is read back out of the slice at the offset - /// `#[repr(C)]` puts it at. `path_offset` and `path_len` are the two the - /// `const _` above is about — a gap before them shifts both, and these are - /// the assertions that catch it. + /// `#[repr(C)]` puts it at. #[test] - fn module_info_as_bytes_is_the_fields_and_nothing_between_them() { + fn module_info_s_bytes_are_the_fields_and_nothing_between_them() { let info = ModuleInfo { base: 0x1122_3344_5566_7788, text_end: 0x2233_4455_6677_8899, @@ -2621,7 +2595,7 @@ mod tests { path_offset: 0x55, path_len: 0x66, }; - let b = info.as_bytes(); + let b = crate::usersafe::bytes(&info); assert_eq!(b.len(), 56); assert_eq!(u64::from_ne_bytes(b[0..8].try_into().unwrap()), info.base); assert_eq!(u64::from_ne_bytes(b[8..16].try_into().unwrap()), info.text_end); @@ -2656,8 +2630,8 @@ mod tests { let lib = record(0x200_0000_0000, 2 * size + 9, 13); let mut answer = [0u8; 1 + 2 * 56 + 9 + 13]; let wire = &mut answer[1..]; - wire[..56].copy_from_slice(exe.as_bytes()); - wire[56..112].copy_from_slice(lib.as_bytes()); + wire[..56].copy_from_slice(crate::usersafe::bytes(&exe)); + wire[56..112].copy_from_slice(crate::usersafe::bytes(&lib)); wire[112..121].copy_from_slice(b"/bin/prog"); wire[121..].copy_from_slice(b"/lib/libx.so\0"); let got: [(u64, u64, &[u8]); 2] = { @@ -2678,7 +2652,7 @@ mod tests { fn modules_refuses_a_path_past_the_answer() { let size = core::mem::size_of::() as u32; let mut answer = [0u8; 56 + 4]; - answer[..56].copy_from_slice(record(0, size, 5).as_bytes()); + answer[..56].copy_from_slice(crate::usersafe::bytes(&record(0, size, 5))); modules(&answer).for_each(drop); } diff --git a/toyos-abi/src/trace.rs b/toyos-abi/src/trace.rs index 9737406304d..fe74893199a 100644 --- a/toyos-abi/src/trace.rs +++ b/toyos-abi/src/trace.rs @@ -20,44 +20,30 @@ pub const RECORD_BYTES: usize = 32; /// What `pid` and `tid` read when the CPU was running no thread. pub const NO_THREAD: u32 = u32::MAX; -/// One diary record, as a read copies it out. -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct TraceRecord { - /// Its sequence number in its CPU's ring, which [`LogCursor::next`] counts in. - pub seq: u64, - /// The CPU's counter when the record was written. - pub stamp: u64, - /// A [`Kind`], undecoded: input until [`Kind::from_u16`] says otherwise. - pub kind: u16, - /// The CPU whose ring holds it, which is the CPU that wrote it. - pub cpu: u16, - pub data: u32, - pub pid: u32, - pub tid: u32, +crate::user_safe! { + /// One diary record, as a read copies it out. + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub struct TraceRecord { + /// Its sequence number in its CPU's ring, which [`LogCursor::next`] counts in. + pub seq: u64, + /// The CPU's counter when the record was written. + pub stamp: u64, + /// A [`Kind`], undecoded: input until [`Kind::from_u16`] says otherwise. + pub kind: u16, + /// The CPU whose ring holds it, which is the CPU that wrote it. + pub cpu: u16, + pub data: u32, + pub pid: u32, + pub tid: u32, + } } const _: () = assert!(core::mem::size_of::() == RECORD_BYTES); -/// Every byte belongs to a field: the record crosses the boundary through -/// [`TraceRecord::as_bytes`], so a gap would publish whatever the kernel stack -/// held. -const _: () = assert!(core::mem::size_of::() == 8 + 8 + 2 + 2 + 4 + 4 + 4); impl TraceRecord { /// A record no ring wrote, for sizing a read buffer: sequence numbers /// start at one, so this is no record. pub const EMPTY: Self = Self { seq: 0, stamp: 0, kind: 0, cpu: 0, data: 0, pid: 0, tid: 0 }; - - /// The record's own bytes, which is what goes on the wire. - #[inline] - pub fn as_bytes(&self) -> &[u8] { - // SAFETY: `self` is a valid `&Self`, and the const assert above proves - // the `repr(C)` layout has no padding, so every byte is an initialized - // field. - unsafe { - core::slice::from_raw_parts(self as *const Self as *const u8, core::mem::size_of::()) - } - } } /// What a record says happened. `pid`/`tid` name the thread running on the @@ -118,11 +104,12 @@ impl Kind { } } -/// A reader's position in the diary: the log's cursor, a type of its own so -/// that one is never walked over the other's rings. -#[repr(transparent)] -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub struct TraceCursor(pub LogCursor); +crate::user_safe! { + /// A reader's position in the diary: the log's cursor, a type of its own so + /// that one is never walked over the other's rings. + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] + pub struct TraceCursor(pub LogCursor); +} impl TraceCursor { /// A cursor that has read nothing. diff --git a/toyos-abi/src/usersafe.rs b/toyos-abi/src/usersafe.rs new file mode 100644 index 00000000000..da4c5f6db1c --- /dev/null +++ b/toyos-abi/src/usersafe.rs @@ -0,0 +1,108 @@ +//! Values the kernel copies whole across the user boundary: [`UserSafe`], and +//! [`user_safe!`](crate::user_safe), the only way a struct comes to be one. +//! +//! A struct declared through the macro is refused by the compiler when a byte +//! of it belongs to no field or a field's type is not itself `UserSafe`. The +//! impls written by hand are the ones below and no others: the fixed-width +//! integers and arrays, which the macro has no fields to check. + +/// A type every bit pattern is a value of and no byte of is padding, so a copy +/// in from user memory is a value whatever the caller wrote, and a copy out +/// publishes only what its fields hold. +/// +/// # Safety +/// Both halves of the sentence above hold. [`user_safe!`](crate::user_safe) +/// proves them of a struct; nothing else implements this. +pub unsafe trait UserSafe: Copy {} + +macro_rules! integers { + ($($int:ty)*) => {$( + // SAFETY: an integer has no padding and every bit pattern is one. + unsafe impl UserSafe for $int {} + )*}; +} +integers!(u8 i8 u16 i16 u32 i32 u64 i64); + +// SAFETY: an array is its elements end to end, with nothing between or after them. +unsafe impl UserSafe for [T; N] {} + +/// The bytes of a value, which are what it is on the wire. +pub fn bytes(value: &T) -> &[u8] { + // SAFETY: a `&T` is readable for `size_of::()` bytes, and `T: UserSafe` leaves none of them padding. + unsafe { core::slice::from_raw_parts(core::ptr::from_ref(value).cast(), core::mem::size_of::()) } +} + +/// The size of a field, for a field that is [`UserSafe`]. +#[doc(hidden)] +pub const fn field() -> usize { + core::mem::size_of::() +} + +/// Declares a struct as [`UserSafe`]: `#[repr(C)]` over named fields, or +/// `#[repr(transparent)]` over one unnamed field. +/// +/// A byte that belongs to no field fails the build, wherever the gap is: +/// +/// ``` +/// toyos_abi::user_safe! { +/// #[derive(Clone, Copy)] +/// struct Whole { a: u64, b: u64 } +/// } +/// ``` +/// +/// ```compile_fail +/// toyos_abi::user_safe! { +/// #[derive(Clone, Copy)] +/// struct Tail { a: u64, b: u32 } +/// } +/// ``` +/// +/// So does a field with a bit pattern that is no value of its type, though it +/// leaves no gap: +/// +/// ``` +/// toyos_abi::user_safe! { +/// #[derive(Clone, Copy)] +/// struct Words { a: u32, b: u32 } +/// } +/// ``` +/// +/// ```compile_fail +/// toyos_abi::user_safe! { +/// #[derive(Clone, Copy)] +/// struct Scalar { a: u32, b: char } +/// } +/// ``` +#[macro_export] +macro_rules! user_safe { + ( + $(#[$attr:meta])* + $vis:vis struct $name:ident { + $($(#[$field_attr:meta])* $field_vis:vis $field:ident: $ty:ty),* $(,)? + } + ) => { + $(#[$attr])* + #[repr(C)] + $vis struct $name { + $($(#[$field_attr])* $field_vis $field: $ty),* + } + $crate::user_safe!(@impl $name: $($ty),*); + }; + ( + $(#[$attr:meta])* + $vis:vis struct $name:ident($field_vis:vis $ty:ty); + ) => { + $(#[$attr])* + #[repr(transparent)] + $vis struct $name($field_vis $ty); + $crate::user_safe!(@impl $name: $ty); + }; + (@impl $name:ident: $($ty:ty),*) => { + const _: () = ::core::assert!( + ::core::mem::size_of::<$name>() == 0 $(+ $crate::usersafe::field::<$ty>())*, + ::core::concat!("a byte of `", stringify!($name), "` belongs to no field"), + ); + // SAFETY: every field is `UserSafe`, and the assertion above leaves no byte outside one. + unsafe impl $crate::UserSafe for $name {} + }; +} diff --git a/toyos-abi/src/virtio_sound.rs b/toyos-abi/src/virtio_sound.rs index b91fa860586..c5dbdcc83a5 100644 --- a/toyos-abi/src/virtio_sound.rs +++ b/toyos-abi/src/virtio_sound.rs @@ -122,45 +122,28 @@ const _: () = { assert!(EVENT_BUFS <= EVENT_QUEUE_SIZE as usize); }; -/// The device the kernel brought up, as the driver needs to see it. -/// -/// No physical address and no register window: a shared-memory token, the three -/// offsets inside the notification region that are the driver's whole write -/// surface, and what the device said about itself in its configuration space. -#[repr(C)] -#[derive(Clone, Copy)] -pub struct VirtioSoundInfo { - /// The shared region, mapped writable, laid out by the constants above. - pub dma: crate::RawHandle, - /// Byte offsets into the notification region — the offset space - /// [`device_reg_write`] names for this device, and the only one it has. +crate::user_safe! { + /// The device the kernel brought up, as the driver needs to see it. /// - /// [`device_reg_write`]: crate::syscall::device_reg_write - pub notify_control: u32, - pub notify_event: u32, - pub notify_tx: u32, - /// The device's configuration space, read once by the kernel, which decided - /// nothing with it. Which stream to open and at what rate is the driver's. - pub jacks: u32, - pub streams: u32, - pub chmaps: u32, -} - -/// Every byte belongs to a field: this crosses the boundary through `as_bytes`, -/// so a gap would publish whatever the kernel stack held. -const _: () = assert!(core::mem::size_of::() == 7 * 4); - -impl VirtioSoundInfo { - pub fn as_bytes(&self) -> &[u8] { - // SAFETY: `self` is a valid `&Self` (non-null, aligned, readable for - // `size_of::()` bytes), and the const assert above proves the - // `repr(C)` layout has no padding, so every byte the slice exposes is - // an initialized field, not a gap. - unsafe { - core::slice::from_raw_parts( - self as *const Self as *const u8, - core::mem::size_of::(), - ) - } + /// No physical address and no register window: a shared-memory token, the three + /// offsets inside the notification region that are the driver's whole write + /// surface, and what the device said about itself in its configuration space. + #[derive(Clone, Copy)] + pub struct VirtioSoundInfo { + /// The shared region, mapped writable, laid out by the constants above. + pub dma: crate::RawHandle, + /// Byte offsets into the notification region — the offset space + /// [`device_reg_write`] names for this device, and the only one it has. + /// + /// [`device_reg_write`]: crate::syscall::device_reg_write + pub notify_control: u32, + pub notify_event: u32, + pub notify_tx: u32, + /// The device's configuration space, read once by the kernel, which decided + /// nothing with it. Which stream to open and at what rate is the driver's. + pub jacks: u32, + pub streams: u32, + pub chmaps: u32, } } + diff --git a/toyos-userbound/src/span.rs b/toyos-userbound/src/span.rs index 2832d221c3c..e7ef60af548 100644 --- a/toyos-userbound/src/span.rs +++ b/toyos-userbound/src/span.rs @@ -96,22 +96,11 @@ pub const PAGE_4K: u64 = 4096; mod tests { use super::*; - /// Every `UserSafe` type, by the shape that decides this: size and align. - const TYPES: &[(&str, u64, u64)] = &[ - ("u32", 4, 4), - ("u64", 8, 8), - ("[u32; 2]", 8, 4), - ("[u64; 2]", 16, 8), - ("RawKeyEvent", 2, 1), - ("MouseEvent", 6, 2), - ("Stat", 24, 8), - ("SchedInfo", 24, 8), - ("FramebufferInfo", 32, 4), - ("SpawnArgs", 136, 8), - ("NamespaceBuild", 56, 8), - ("InboxSetup", 16, 8), - ("ProcessStats", 128, 8), - ]; + /// Every size and alignment a `repr(C)` value of integers up to 256 bytes + /// can have: the two numbers are all [`is_user_object`] reads of a type. + fn shapes() -> impl Iterator { + [1, 2, 4, 8].into_iter().flat_map(|align| (1..=256 / align).map(move |n| (n * align, align))) + } #[test] fn a_kernel_address_is_not_a_user_address() { @@ -135,7 +124,7 @@ mod tests { /// The straddle, for every type, at every offset that can produce one. #[test] fn no_type_may_cross_a_2_mib_boundary() { - for &(name, size, align) in TYPES { + for (size, align) in shapes() { for last in 1..size { let ptr = 4 * PAGE_2M - last; if !ptr.is_multiple_of(align) { @@ -143,32 +132,31 @@ mod tests { } assert!( !is_user_object(ptr, size, align), - "{name} at {ptr:#x} has {last} bytes below the boundary and {} above", - size - last + "{size}/{align} at {ptr:#x} has {last} bytes below the boundary" ); } - assert!(is_user_object(4 * PAGE_2M - size, size, align), "{name} ending at a boundary"); - assert!(is_user_object(4 * PAGE_2M, size, align), "{name} starting at a boundary"); + assert!(is_user_object(4 * PAGE_2M - size, size, align), "{size}/{align} ending at a boundary"); + assert!(is_user_object(4 * PAGE_2M, size, align), "{size}/{align} starting at a boundary"); } } #[test] fn an_object_is_refused_for_its_alignment_before_anything_else() { - for &(name, size, align) in TYPES { + for (size, align) in shapes() { for off in 1..align { - assert!(!is_user_object(PAGE_2M + off, size, align), "{name} at +{off}"); + assert!(!is_user_object(PAGE_2M + off, size, align), "{size}/{align} at +{off}"); } - assert!(is_user_object(PAGE_2M, size, align), "{name} at a page start"); + assert!(is_user_object(PAGE_2M, size, align), "{size}/{align} at a page start"); } } #[test] fn an_object_may_not_end_past_the_bound() { - for &(name, size, align) in TYPES { - assert!(is_user_object(USER_TOP - size, size, align), "{name} ending at the bound"); - assert!(!is_user_object(USER_TOP, size, align), "{name} at the bound"); - assert!(!is_user_object(USER_TOP + PAGE_2M, size, align), "{name} above the bound"); - assert!(!is_user_object(u64::MAX - size + 1, size, align), "{name} wrapping"); + for (size, align) in shapes() { + assert!(is_user_object(USER_TOP - size, size, align), "{size}/{align} ending at the bound"); + assert!(!is_user_object(USER_TOP, size, align), "{size}/{align} at the bound"); + assert!(!is_user_object(USER_TOP + PAGE_2M, size, align), "{size}/{align} above the bound"); + assert!(!is_user_object(u64::MAX - size + 1, size, align), "{size}/{align} wrapping"); } } diff --git a/toyos/src/device.rs b/toyos/src/device.rs index 19dd1060870..1e2138064de 100644 --- a/toyos/src/device.rs +++ b/toyos/src/device.rs @@ -13,9 +13,8 @@ use toyos_abi::RawHandle; /// SAFETY: each is `#[repr(C)]` over unsigned integers, arrays of them and /// `RawHandle`s (`repr(transparent)` over a `u32`), so none holds a pointer or a field with -/// an invalid bit pattern, and the padding-free layout is the `const` assertion -/// beside each declaration in `toyos-abi` — the one its `as_bytes` already -/// reads on to publish these bytes. +/// an invalid bit pattern, and each is declared through `user_safe!`, +/// which refuses a byte outside a field. macro_rules! device_info { ($($t:ty),* $(,)?) => { $(unsafe impl IpcPayload for $t {})* }; }